github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/indexshipper/uploads/index_set.go (about) 1 package uploads 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "sync" 9 "time" 10 11 "github.com/go-kit/log" 12 "github.com/go-kit/log/level" 13 14 "github.com/grafana/loki/pkg/chunkenc" 15 "github.com/grafana/loki/pkg/storage/stores/indexshipper/index" 16 "github.com/grafana/loki/pkg/storage/stores/indexshipper/storage" 17 util_log "github.com/grafana/loki/pkg/util/log" 18 ) 19 20 type IndexSet interface { 21 Add(idx index.Index) 22 Upload(ctx context.Context) error 23 Cleanup(indexRetainPeriod time.Duration) error 24 ForEach(callback index.ForEachIndexCallback) error 25 Close() 26 } 27 28 // indexSet is a collection of multiple files created for a same table by various ingesters. 29 // All the public methods are concurrency safe and take care of mutexes to avoid any data race. 30 type indexSet struct { 31 storageIndexSet storage.IndexSet 32 tableName, userID string 33 logger log.Logger 34 35 index map[string]index.Index 36 indexMtx sync.RWMutex 37 38 indexUploadTime map[string]time.Time 39 indexUploadTimeMtx sync.RWMutex 40 } 41 42 func NewIndexSet(tableName, userID string, baseIndexSet storage.IndexSet, logger log.Logger) (IndexSet, error) { 43 if baseIndexSet.IsUserBasedIndexSet() && userID == "" { 44 return nil, fmt.Errorf("userID must not be empty") 45 } else if !baseIndexSet.IsUserBasedIndexSet() && userID != "" { 46 return nil, fmt.Errorf("userID must be empty") 47 } 48 49 is := indexSet{ 50 storageIndexSet: baseIndexSet, 51 tableName: tableName, 52 index: map[string]index.Index{}, 53 indexUploadTime: map[string]time.Time{}, 54 userID: userID, 55 logger: logger, 56 } 57 58 return &is, nil 59 } 60 61 func (t *indexSet) Add(idx index.Index) { 62 t.indexMtx.Lock() 63 defer t.indexMtx.Unlock() 64 65 t.index[idx.Name()] = idx 66 } 67 68 func (t *indexSet) ForEach(callback index.ForEachIndexCallback) error { 69 t.indexMtx.RLock() 70 defer t.indexMtx.RUnlock() 71 72 for _, idx := range t.index { 73 if err := callback(t.userID == "", idx); err != nil { 74 return err 75 } 76 } 77 78 return nil 79 } 80 81 // Upload uploads all the dbs which are never uploaded or have been modified since the last batch was uploaded. 82 func (t *indexSet) Upload(ctx context.Context) error { 83 t.indexMtx.RLock() 84 defer t.indexMtx.RUnlock() 85 86 level.Info(util_log.Logger).Log("msg", fmt.Sprintf("uploading table %s", t.tableName)) 87 88 for name, idx := range t.index { 89 // if the file is uploaded already do not upload it again. 90 t.indexUploadTimeMtx.RLock() 91 _, ok := t.indexUploadTime[name] 92 t.indexUploadTimeMtx.RUnlock() 93 94 if ok { 95 continue 96 } 97 98 if err := t.uploadIndex(ctx, idx); err != nil { 99 return err 100 } 101 102 t.indexUploadTimeMtx.Lock() 103 t.indexUploadTime[name] = time.Now() 104 t.indexUploadTimeMtx.Unlock() 105 } 106 107 level.Info(util_log.Logger).Log("msg", fmt.Sprintf("finished uploading table %s", t.tableName)) 108 109 return nil 110 } 111 112 // Close Closes references to all the indexes. 113 func (t *indexSet) Close() { 114 t.indexMtx.Lock() 115 defer t.indexMtx.Unlock() 116 117 for name, idx := range t.index { 118 if err := idx.Close(); err != nil { 119 level.Error(t.logger).Log("msg", fmt.Sprintf("failed to close index %s", name), "err", err) 120 } 121 } 122 123 t.index = map[string]index.Index{} 124 } 125 126 func (t *indexSet) uploadIndex(ctx context.Context, idx index.Index) error { 127 fileName := idx.Name() 128 level.Debug(t.logger).Log("msg", fmt.Sprintf("uploading index %s", fileName)) 129 130 idxPath := idx.Path() 131 132 filePath := fmt.Sprintf("%s%s", idxPath, tempFileSuffix) 133 f, err := os.Create(filePath) 134 if err != nil { 135 return err 136 } 137 138 defer func() { 139 if err := f.Close(); err != nil { 140 level.Error(util_log.Logger).Log("msg", "failed to close temp file", "path", filePath, "err", err) 141 } 142 143 if err := os.Remove(filePath); err != nil { 144 level.Error(util_log.Logger).Log("msg", "failed to remove temp file", "path", filePath, "err", err) 145 } 146 }() 147 148 compressedWriter := chunkenc.Gzip.GetWriter(f) 149 defer chunkenc.Gzip.PutWriter(compressedWriter) 150 151 idxReader, err := idx.Reader() 152 if err != nil { 153 return err 154 } 155 156 _, err = idxReader.Seek(0, 0) 157 if err != nil { 158 return err 159 } 160 161 _, err = io.Copy(compressedWriter, idxReader) 162 if err != nil { 163 return err 164 } 165 166 err = compressedWriter.Close() 167 if err != nil { 168 return err 169 } 170 171 // flush the file to disk and seek the file to the beginning. 172 if err := f.Sync(); err != nil { 173 return err 174 } 175 176 if _, err := f.Seek(0, 0); err != nil { 177 return err 178 } 179 180 return t.storageIndexSet.PutFile(ctx, t.tableName, t.userID, t.buildFileName(fileName), f) 181 } 182 183 // Cleanup removes indexes which are already uploaded and have been retained for period longer than indexRetainPeriod since they were uploaded. 184 func (t *indexSet) Cleanup(indexRetainPeriod time.Duration) error { 185 level.Info(util_log.Logger).Log("msg", fmt.Sprintf("cleaning up unwanted indexes from table %s", t.tableName)) 186 187 var filesToCleanup []string 188 cutoffTime := time.Now().Add(-indexRetainPeriod) 189 190 t.indexMtx.RLock() 191 192 for name := range t.index { 193 t.indexUploadTimeMtx.RLock() 194 indexUploadTime, ok := t.indexUploadTime[name] 195 t.indexUploadTimeMtx.RUnlock() 196 197 if ok && indexUploadTime.Before(cutoffTime) { 198 filesToCleanup = append(filesToCleanup, name) 199 } 200 } 201 202 t.indexMtx.RUnlock() 203 204 for i := range filesToCleanup { 205 level.Debug(util_log.Logger).Log("msg", fmt.Sprintf("dropping uploaded index %s from table %s", filesToCleanup[i], t.tableName)) 206 207 if err := t.removeIndex(filesToCleanup[i]); err != nil { 208 return err 209 } 210 } 211 212 return nil 213 } 214 215 // removeIndex closes the index and removes the file locally. 216 func (t *indexSet) removeIndex(name string) error { 217 t.indexMtx.Lock() 218 defer t.indexMtx.Unlock() 219 220 idx, ok := t.index[name] 221 if !ok { 222 return nil 223 } 224 225 err := idx.Close() 226 if err != nil { 227 return err 228 } 229 230 delete(t.index, name) 231 232 t.indexUploadTimeMtx.Lock() 233 delete(t.indexUploadTime, name) 234 t.indexUploadTimeMtx.Unlock() 235 236 return os.Remove(idx.Path()) 237 } 238 239 func (t *indexSet) buildFileName(indexName string) string { 240 return fmt.Sprintf("%s.gz", indexName) 241 }