github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/shipper/index/table_manager.go (about) 1 package index 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 "sync" 12 "time" 13 14 "github.com/go-kit/log/level" 15 "github.com/prometheus/client_golang/prometheus" 16 "go.etcd.io/bbolt" 17 18 "github.com/grafana/loki/pkg/storage/chunk/client/local" 19 chunk_util "github.com/grafana/loki/pkg/storage/chunk/client/util" 20 "github.com/grafana/loki/pkg/storage/stores/indexshipper" 21 shipper_index "github.com/grafana/loki/pkg/storage/stores/indexshipper/index" 22 "github.com/grafana/loki/pkg/storage/stores/series/index" 23 util_log "github.com/grafana/loki/pkg/util/log" 24 ) 25 26 type Config struct { 27 Uploader string 28 IndexDir string 29 DBRetainPeriod time.Duration 30 MakePerTenantBuckets bool 31 } 32 33 type TableManager struct { 34 cfg Config 35 indexShipper Shipper 36 37 metrics *metrics 38 tables map[string]*Table 39 tablesMtx sync.RWMutex 40 41 ctx context.Context 42 cancel context.CancelFunc 43 wg sync.WaitGroup 44 } 45 46 type Shipper interface { 47 AddIndex(tableName, userID string, index shipper_index.Index) error 48 ForEach(ctx context.Context, tableName, userID string, callback shipper_index.ForEachIndexCallback) error 49 } 50 51 func NewTableManager(cfg Config, indexShipper Shipper, registerer prometheus.Registerer) (*TableManager, error) { 52 err := chunk_util.EnsureDirectory(cfg.IndexDir) 53 if err != nil { 54 return nil, err 55 } 56 57 ctx, cancel := context.WithCancel(context.Background()) 58 tm := TableManager{ 59 cfg: cfg, 60 indexShipper: indexShipper, 61 metrics: newMetrics(registerer), 62 ctx: ctx, 63 cancel: cancel, 64 } 65 66 tables, err := tm.loadTables() 67 if err != nil { 68 return nil, err 69 } 70 71 tm.tables = tables 72 go tm.loop() 73 return &tm, nil 74 } 75 76 func (tm *TableManager) loop() { 77 tm.wg.Add(1) 78 defer tm.wg.Done() 79 80 tm.handoverIndexesToShipper(false) 81 82 syncTicker := time.NewTicker(indexshipper.UploadInterval) 83 defer syncTicker.Stop() 84 85 for { 86 select { 87 case <-syncTicker.C: 88 tm.handoverIndexesToShipper(false) 89 case <-tm.ctx.Done(): 90 return 91 } 92 } 93 } 94 95 func (tm *TableManager) Stop() { 96 level.Info(util_log.Logger).Log("msg", "stopping table manager") 97 98 tm.cancel() 99 tm.wg.Wait() 100 101 tm.handoverIndexesToShipper(true) 102 } 103 104 func (tm *TableManager) ForEach(ctx context.Context, tableName string, callback func(boltdb *bbolt.DB) error) error { 105 table, ok := tm.getTable(tableName) 106 if !ok { 107 return nil 108 } 109 110 return table.ForEach(ctx, callback) 111 } 112 113 func (tm *TableManager) getTable(tableName string) (*Table, bool) { 114 tm.tablesMtx.RLock() 115 defer tm.tablesMtx.RUnlock() 116 table, ok := tm.tables[tableName] 117 return table, ok 118 } 119 120 func (tm *TableManager) BatchWrite(ctx context.Context, batch index.WriteBatch) error { 121 boltWriteBatch, ok := batch.(*local.BoltWriteBatch) 122 if !ok { 123 return errors.New("invalid write batch") 124 } 125 126 for tableName, tableWrites := range boltWriteBatch.Writes { 127 table, err := tm.getOrCreateTable(tableName) 128 if err != nil { 129 return err 130 } 131 132 err = table.Write(ctx, tableWrites) 133 if err != nil { 134 return err 135 } 136 } 137 138 return nil 139 } 140 141 func (tm *TableManager) getOrCreateTable(tableName string) (*Table, error) { 142 table, ok := tm.getTable(tableName) 143 144 if !ok { 145 tm.tablesMtx.Lock() 146 defer tm.tablesMtx.Unlock() 147 148 table, ok = tm.tables[tableName] 149 if !ok { 150 var err error 151 table, err = NewTable(filepath.Join(tm.cfg.IndexDir, tableName), tm.cfg.Uploader, tm.indexShipper, tm.cfg.MakePerTenantBuckets) 152 if err != nil { 153 return nil, err 154 } 155 156 tm.tables[tableName] = table 157 } 158 } 159 160 return table, nil 161 } 162 163 func (tm *TableManager) handoverIndexesToShipper(force bool) { 164 tm.tablesMtx.RLock() 165 defer tm.tablesMtx.RUnlock() 166 167 level.Info(util_log.Logger).Log("msg", "handing over indexes to shipper") 168 169 for _, table := range tm.tables { 170 err := table.HandoverIndexesToShipper(force) 171 if err != nil { 172 // continue handing over other tables while skipping cleanup for a failed one. 173 level.Error(util_log.Logger).Log("msg", "failed to handover index", "table", table.name, "err", err) 174 continue 175 } 176 177 err = table.Snapshot() 178 if err != nil { 179 // we do not want to stop handing over of index due to failures in snapshotting them so logging just the error here. 180 level.Error(util_log.Logger).Log("msg", "failed to snapshot table for reads", "table", table.name, "err", err) 181 } 182 } 183 } 184 185 func (tm *TableManager) loadTables() (map[string]*Table, error) { 186 localTables := make(map[string]*Table) 187 filesInfo, err := ioutil.ReadDir(tm.cfg.IndexDir) 188 if err != nil { 189 return nil, err 190 } 191 192 // regex matching table name patters, i.e prefix+period_number 193 re, err := regexp.Compile(`.+[0-9]+$`) 194 if err != nil { 195 return nil, err 196 } 197 198 for _, fileInfo := range filesInfo { 199 if !re.MatchString(fileInfo.Name()) { 200 continue 201 } 202 203 // since we are moving to keeping files for same table in a folder, if current element is a file we need to move it inside a directory with the same name 204 // i.e file index_123 would be moved to path index_123/index_123. 205 if !fileInfo.IsDir() { 206 level.Info(util_log.Logger).Log("msg", fmt.Sprintf("found a legacy file %s, moving it to folder with same name", fileInfo.Name())) 207 filePath := filepath.Join(tm.cfg.IndexDir, fileInfo.Name()) 208 209 // create a folder with .temp suffix since we can't create a directory with same name as file. 210 tempDirPath := filePath + ".temp" 211 if err := chunk_util.EnsureDirectory(tempDirPath); err != nil { 212 return nil, err 213 } 214 215 // move the file to temp dir. 216 if err := os.Rename(filePath, filepath.Join(tempDirPath, fileInfo.Name())); err != nil { 217 return nil, err 218 } 219 220 // rename the directory to name of the file 221 if err := os.Rename(tempDirPath, filePath); err != nil { 222 return nil, err 223 } 224 } 225 226 level.Info(util_log.Logger).Log("msg", fmt.Sprintf("loading table %s", fileInfo.Name())) 227 table, err := LoadTable(filepath.Join(tm.cfg.IndexDir, fileInfo.Name()), tm.cfg.Uploader, tm.indexShipper, tm.cfg.MakePerTenantBuckets, tm.metrics) 228 if err != nil { 229 return nil, err 230 } 231 232 if table == nil { 233 // if table is nil it means it has no files in it so remove the folder for that table. 234 err := os.Remove(filepath.Join(tm.cfg.IndexDir, fileInfo.Name())) 235 if err != nil { 236 level.Error(util_log.Logger).Log("msg", "failed to remove empty table folder", "table", fileInfo.Name(), "err", err) 237 } 238 continue 239 } 240 241 // handover indexes to shipper since we won't modify them anymore. 242 err = table.HandoverIndexesToShipper(true) 243 if err != nil { 244 return nil, err 245 } 246 247 // Queries are only done against table snapshots so it's important we snapshot as soon as the table is loaded. 248 err = table.Snapshot() 249 if err != nil { 250 return nil, err 251 } 252 253 localTables[fileInfo.Name()] = table 254 } 255 256 return localTables, nil 257 }