github.com/thanos-io/thanos@v0.32.5/pkg/compact/compact_e2e_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package compact 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "os" 12 "path" 13 "path/filepath" 14 "sort" 15 "testing" 16 "time" 17 18 "github.com/go-kit/log" 19 "github.com/oklog/ulid" 20 "github.com/prometheus/client_golang/prometheus" 21 "github.com/prometheus/client_golang/prometheus/promauto" 22 promtest "github.com/prometheus/client_golang/prometheus/testutil" 23 "github.com/prometheus/prometheus/model/labels" 24 "github.com/prometheus/prometheus/storage" 25 "github.com/prometheus/prometheus/tsdb" 26 "github.com/thanos-io/objstore" 27 "github.com/thanos-io/objstore/objtesting" 28 29 "github.com/efficientgo/core/testutil" 30 "github.com/thanos-io/thanos/pkg/block" 31 "github.com/thanos-io/thanos/pkg/block/metadata" 32 "github.com/thanos-io/thanos/pkg/dedup" 33 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 34 ) 35 36 const fetcherConcurrency = 32 37 38 func TestSyncer_GarbageCollect_e2e(t *testing.T) { 39 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 40 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 41 defer cancel() 42 43 // Generate 10 source block metas and construct higher level blocks 44 // that are higher compactions of them. 45 var metas []*metadata.Meta 46 var ids []ulid.ULID 47 48 for i := 0; i < 10; i++ { 49 var m metadata.Meta 50 51 m.Version = 1 52 m.ULID = ulid.MustNew(uint64(i), nil) 53 m.Compaction.Sources = []ulid.ULID{m.ULID} 54 m.Compaction.Level = 1 55 56 ids = append(ids, m.ULID) 57 metas = append(metas, &m) 58 } 59 60 var m1 metadata.Meta 61 m1.Version = 1 62 m1.ULID = ulid.MustNew(100, nil) 63 m1.Compaction.Level = 2 64 m1.Compaction.Sources = ids[:4] 65 m1.Thanos.Downsample.Resolution = 0 66 67 var m2 metadata.Meta 68 m2.Version = 1 69 m2.ULID = ulid.MustNew(200, nil) 70 m2.Compaction.Level = 2 71 m2.Compaction.Sources = ids[4:8] // last two source IDs is not part of a level 2 block. 72 m2.Thanos.Downsample.Resolution = 0 73 74 var m3 metadata.Meta 75 m3.Version = 1 76 m3.ULID = ulid.MustNew(300, nil) 77 m3.Compaction.Level = 3 78 m3.Compaction.Sources = ids[:9] // last source ID is not part of level 3 block. 79 m3.Thanos.Downsample.Resolution = 0 80 81 var m4 metadata.Meta 82 m4.Version = 1 83 m4.ULID = ulid.MustNew(400, nil) 84 m4.Compaction.Level = 2 85 m4.Compaction.Sources = ids[9:] // covers the last block but is a different resolution. Must not trigger deletion. 86 m4.Thanos.Downsample.Resolution = 1000 87 88 // Create all blocks in the bucket. 89 for _, m := range append(metas, &m1, &m2, &m3, &m4) { 90 fmt.Println("create", m.ULID) 91 var buf bytes.Buffer 92 testutil.Ok(t, json.NewEncoder(&buf).Encode(&m)) 93 testutil.Ok(t, bkt.Upload(ctx, path.Join(m.ULID.String(), metadata.MetaFilename), &buf)) 94 } 95 96 duplicateBlocksFilter := block.NewDeduplicateFilter(fetcherConcurrency) 97 metaFetcher, err := block.NewMetaFetcher(nil, 32, objstore.WithNoopInstr(bkt), "", nil, []block.MetadataFilter{ 98 duplicateBlocksFilter, 99 }) 100 testutil.Ok(t, err) 101 102 blocksMarkedForDeletion := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 103 garbageCollectedBlocks := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 104 blockMarkedForNoCompact := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 105 ignoreDeletionMarkFilter := block.NewIgnoreDeletionMarkFilter(nil, nil, 48*time.Hour, fetcherConcurrency) 106 sy, err := NewMetaSyncer(nil, nil, bkt, metaFetcher, duplicateBlocksFilter, ignoreDeletionMarkFilter, blocksMarkedForDeletion, garbageCollectedBlocks) 107 testutil.Ok(t, err) 108 109 // Do one initial synchronization with the bucket. 110 testutil.Ok(t, sy.SyncMetas(ctx)) 111 testutil.Ok(t, sy.GarbageCollect(ctx)) 112 113 var rem []ulid.ULID 114 err = bkt.Iter(ctx, "", func(n string) error { 115 id := ulid.MustParse(n[:len(n)-1]) 116 deletionMarkFile := path.Join(id.String(), metadata.DeletionMarkFilename) 117 118 exists, err := bkt.Exists(ctx, deletionMarkFile) 119 if err != nil { 120 return err 121 } 122 if !exists { 123 rem = append(rem, id) 124 } 125 return nil 126 }) 127 testutil.Ok(t, err) 128 129 sort.Slice(rem, func(i, j int) bool { 130 return rem[i].Compare(rem[j]) < 0 131 }) 132 133 // Only the level 3 block, the last source block in both resolutions should be left. 134 testutil.Equals(t, []ulid.ULID{metas[9].ULID, m3.ULID, m4.ULID}, rem) 135 136 // After another sync the changes should also be reflected in the local groups. 137 testutil.Ok(t, sy.SyncMetas(ctx)) 138 testutil.Ok(t, sy.GarbageCollect(ctx)) 139 140 // Only the level 3 block, the last source block in both resolutions should be left. 141 grouper := NewDefaultGrouper(nil, bkt, false, false, nil, blocksMarkedForDeletion, garbageCollectedBlocks, blockMarkedForNoCompact, metadata.NoneFunc, 10, 10) 142 groups, err := grouper.Groups(sy.Metas()) 143 testutil.Ok(t, err) 144 145 testutil.Equals(t, "0@17241709254077376921", groups[0].Key()) 146 testutil.Equals(t, []ulid.ULID{metas[9].ULID, m3.ULID}, groups[0].IDs()) 147 testutil.Equals(t, "1000@17241709254077376921", groups[1].Key()) 148 testutil.Equals(t, []ulid.ULID{m4.ULID}, groups[1].IDs()) 149 }) 150 } 151 152 func MetricCount(c prometheus.Collector) int { 153 var ( 154 mCount int 155 mChan = make(chan prometheus.Metric) 156 done = make(chan struct{}) 157 ) 158 159 go func() { 160 for range mChan { 161 mCount++ 162 } 163 close(done) 164 }() 165 166 c.Collect(mChan) 167 close(mChan) 168 <-done 169 170 return mCount 171 } 172 173 func TestGroupCompactE2E(t *testing.T) { 174 testGroupCompactE2e(t, nil) 175 } 176 177 // Penalty based merger should get the same result as the blocks don't have overlap. 178 func TestGroupCompactPenaltyDedupE2E(t *testing.T) { 179 testGroupCompactE2e(t, dedup.NewChunkSeriesMerger()) 180 } 181 182 func testGroupCompactE2e(t *testing.T, mergeFunc storage.VerticalChunkSeriesMergeFunc) { 183 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 184 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 185 defer cancel() 186 187 // Create fresh, empty directory for actual test. 188 dir := t.TempDir() 189 190 logger := log.NewLogfmtLogger(os.Stderr) 191 192 reg := prometheus.NewRegistry() 193 194 ignoreDeletionMarkFilter := block.NewIgnoreDeletionMarkFilter(logger, objstore.WithNoopInstr(bkt), 48*time.Hour, fetcherConcurrency) 195 duplicateBlocksFilter := block.NewDeduplicateFilter(fetcherConcurrency) 196 noCompactMarkerFilter := NewGatherNoCompactionMarkFilter(logger, objstore.WithNoopInstr(bkt), 2) 197 metaFetcher, err := block.NewMetaFetcher(nil, 32, objstore.WithNoopInstr(bkt), "", nil, []block.MetadataFilter{ 198 ignoreDeletionMarkFilter, 199 duplicateBlocksFilter, 200 noCompactMarkerFilter, 201 }) 202 testutil.Ok(t, err) 203 204 blocksMarkedForDeletion := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 205 blocksMaredForNoCompact := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 206 garbageCollectedBlocks := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 207 sy, err := NewMetaSyncer(nil, nil, bkt, metaFetcher, duplicateBlocksFilter, ignoreDeletionMarkFilter, blocksMarkedForDeletion, garbageCollectedBlocks) 208 testutil.Ok(t, err) 209 210 comp, err := tsdb.NewLeveledCompactor(ctx, reg, logger, []int64{1000, 3000}, nil, mergeFunc) 211 testutil.Ok(t, err) 212 213 planner := NewPlanner(logger, []int64{1000, 3000}, noCompactMarkerFilter) 214 grouper := NewDefaultGrouper(logger, bkt, false, false, reg, blocksMarkedForDeletion, garbageCollectedBlocks, blocksMaredForNoCompact, metadata.NoneFunc, 10, 10) 215 bComp, err := NewBucketCompactor(logger, sy, grouper, planner, comp, dir, bkt, 2, true) 216 testutil.Ok(t, err) 217 218 // Compaction on empty should not fail. 219 testutil.Ok(t, bComp.Compact(ctx)) 220 testutil.Equals(t, 0.0, promtest.ToFloat64(sy.metrics.garbageCollectedBlocks)) 221 testutil.Equals(t, 0.0, promtest.ToFloat64(sy.metrics.blocksMarkedForDeletion)) 222 testutil.Equals(t, 0.0, promtest.ToFloat64(sy.metrics.garbageCollectionFailures)) 223 testutil.Equals(t, 0.0, promtest.ToFloat64(grouper.blocksMarkedForNoCompact)) 224 testutil.Equals(t, 0, MetricCount(grouper.compactions)) 225 testutil.Equals(t, 0, MetricCount(grouper.compactionRunsStarted)) 226 testutil.Equals(t, 0, MetricCount(grouper.compactionRunsCompleted)) 227 testutil.Equals(t, 0, MetricCount(grouper.compactionFailures)) 228 229 _, err = os.Stat(dir) 230 testutil.Assert(t, os.IsNotExist(err), "dir %s should be remove after compaction.", dir) 231 232 // Test label name with slash, regression: https://github.com/thanos-io/thanos/issues/1661. 233 extLabels := labels.Labels{{Name: "e1", Value: "1/weird"}} 234 extLabels2 := labels.Labels{{Name: "e1", Value: "1"}} 235 metas := createAndUpload(t, bkt, []blockgenSpec{ 236 { 237 numSamples: 100, mint: 500, maxt: 1000, extLset: extLabels, res: 124, 238 series: []labels.Labels{ 239 {{Name: "a", Value: "1"}}, 240 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}}, 241 {{Name: "a", Value: "3"}}, 242 {{Name: "a", Value: "4"}}, 243 }, 244 }, 245 { 246 numSamples: 100, mint: 2000, maxt: 3000, extLset: extLabels, res: 124, 247 series: []labels.Labels{ 248 {{Name: "a", Value: "3"}}, 249 {{Name: "a", Value: "4"}}, 250 {{Name: "a", Value: "5"}}, 251 {{Name: "a", Value: "6"}}, 252 }, 253 }, 254 // Mix order to make sure compactor is able to deduct min time / max time. 255 // Currently TSDB does not produces empty blocks (see: https://github.com/prometheus/tsdb/pull/374). However before v2.7.0 it was 256 // so we still want to mimick this case as close as possible. 257 { 258 mint: 1000, maxt: 2000, extLset: extLabels, res: 124, 259 // Empty block. 260 }, 261 // Due to TSDB compaction delay (not compacting fresh block), we need one more block to be pushed to trigger compaction. 262 { 263 numSamples: 100, mint: 3000, maxt: 4000, extLset: extLabels, res: 124, 264 series: []labels.Labels{ 265 {{Name: "a", Value: "7"}}, 266 }, 267 }, 268 // Extra block for "distraction" for different resolution and one for different labels. 269 { 270 numSamples: 100, mint: 5000, maxt: 6000, extLset: labels.Labels{{Name: "e1", Value: "2"}}, res: 124, 271 series: []labels.Labels{ 272 {{Name: "a", Value: "7"}}, 273 }, 274 }, 275 // Extra block for "distraction" for different resolution and one for different labels. 276 { 277 numSamples: 100, mint: 4000, maxt: 5000, extLset: extLabels, res: 0, 278 series: []labels.Labels{ 279 {{Name: "a", Value: "7"}}, 280 }, 281 }, 282 // Second group (extLabels2). 283 { 284 numSamples: 100, mint: 2000, maxt: 3000, extLset: extLabels2, res: 124, 285 series: []labels.Labels{ 286 {{Name: "a", Value: "3"}}, 287 {{Name: "a", Value: "4"}}, 288 {{Name: "a", Value: "6"}}, 289 }, 290 }, 291 { 292 numSamples: 100, mint: 0, maxt: 1000, extLset: extLabels2, res: 124, 293 series: []labels.Labels{ 294 {{Name: "a", Value: "1"}}, 295 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}}, 296 {{Name: "a", Value: "3"}}, 297 {{Name: "a", Value: "4"}}, 298 }, 299 }, 300 // Due to TSDB compaction delay (not compacting fresh block), we need one more block to be pushed to trigger compaction. 301 { 302 numSamples: 100, mint: 3000, maxt: 4000, extLset: extLabels2, res: 124, 303 series: []labels.Labels{ 304 {{Name: "a", Value: "7"}}, 305 }, 306 }, 307 }, []blockgenSpec{ 308 { 309 numSamples: 100, mint: 0, maxt: 499, extLset: extLabels, res: 124, 310 series: []labels.Labels{ 311 {{Name: "a", Value: "1"}}, 312 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}}, 313 {{Name: "a", Value: "3"}}, 314 {{Name: "a", Value: "4"}}, 315 }, 316 }, 317 }) 318 319 groupKey1 := metas[0].Thanos.GroupKey() 320 groupKey2 := metas[6].Thanos.GroupKey() 321 322 testutil.Ok(t, bComp.Compact(ctx)) 323 testutil.Equals(t, 5.0, promtest.ToFloat64(sy.metrics.garbageCollectedBlocks)) 324 testutil.Equals(t, 5.0, promtest.ToFloat64(sy.metrics.blocksMarkedForDeletion)) 325 testutil.Equals(t, 1.0, promtest.ToFloat64(grouper.blocksMarkedForNoCompact)) 326 testutil.Equals(t, 0.0, promtest.ToFloat64(sy.metrics.garbageCollectionFailures)) 327 testutil.Equals(t, 2, MetricCount(grouper.compactions)) 328 testutil.Equals(t, 2.0, promtest.ToFloat64(grouper.compactions.WithLabelValues(metas[0].Thanos.ResolutionString()))) 329 testutil.Equals(t, 0.0, promtest.ToFloat64(grouper.compactions.WithLabelValues(metas[5].Thanos.ResolutionString()))) 330 testutil.Equals(t, 2, MetricCount(grouper.compactionRunsStarted)) 331 testutil.Equals(t, 6.0, promtest.ToFloat64(grouper.compactionRunsStarted.WithLabelValues(metas[0].Thanos.ResolutionString()))) 332 testutil.Equals(t, 0.0, promtest.ToFloat64(grouper.compactionRunsStarted.WithLabelValues(metas[5].Thanos.ResolutionString()))) 333 testutil.Equals(t, 2, MetricCount(grouper.compactionRunsCompleted)) 334 testutil.Equals(t, 5.0, promtest.ToFloat64(grouper.compactionRunsCompleted.WithLabelValues(metas[0].Thanos.ResolutionString()))) 335 testutil.Equals(t, 0.0, promtest.ToFloat64(grouper.compactionRunsCompleted.WithLabelValues(metas[5].Thanos.ResolutionString()))) 336 testutil.Equals(t, 2, MetricCount(grouper.compactionFailures)) 337 testutil.Equals(t, 1.0, promtest.ToFloat64(grouper.compactionFailures.WithLabelValues(metas[0].Thanos.ResolutionString()))) 338 testutil.Equals(t, 0.0, promtest.ToFloat64(grouper.compactionFailures.WithLabelValues(metas[5].Thanos.ResolutionString()))) 339 340 _, err = os.Stat(dir) 341 testutil.Assert(t, os.IsNotExist(err), "dir %s should be remove after compaction.", dir) 342 343 // Check object storage. All blocks that were included in new compacted one should be removed. New compacted ones 344 // are present and looks as expected. 345 nonCompactedExpected := map[ulid.ULID]bool{ 346 metas[3].ULID: false, 347 metas[4].ULID: false, 348 metas[5].ULID: false, 349 metas[8].ULID: false, 350 metas[9].ULID: false, 351 } 352 others := map[string]metadata.Meta{} 353 testutil.Ok(t, bkt.Iter(ctx, "", func(n string) error { 354 id, ok := block.IsBlockDir(n) 355 if !ok { 356 return nil 357 } 358 359 if _, ok := nonCompactedExpected[id]; ok { 360 nonCompactedExpected[id] = true 361 return nil 362 } 363 364 meta, err := block.DownloadMeta(ctx, logger, bkt, id) 365 if err != nil { 366 return err 367 } 368 369 others[meta.Thanos.GroupKey()] = meta 370 return nil 371 })) 372 373 for id, found := range nonCompactedExpected { 374 testutil.Assert(t, found, "not found expected block %s", id.String()) 375 } 376 377 // We expect two compacted blocks only outside of what we expected in `nonCompactedExpected`. 378 testutil.Equals(t, 2, len(others)) 379 { 380 meta, ok := others[groupKey1] 381 testutil.Assert(t, ok, "meta not found") 382 383 testutil.Equals(t, int64(500), meta.MinTime) 384 testutil.Equals(t, int64(3000), meta.MaxTime) 385 testutil.Equals(t, uint64(6), meta.Stats.NumSeries) 386 testutil.Equals(t, uint64(2*4*100), meta.Stats.NumSamples) // Only 2 times 4*100 because one block was empty. 387 testutil.Equals(t, 2, meta.Compaction.Level) 388 testutil.Equals(t, []ulid.ULID{metas[0].ULID, metas[1].ULID, metas[2].ULID}, meta.Compaction.Sources) 389 390 // Check thanos meta. 391 testutil.Assert(t, labels.Equal(extLabels, labels.FromMap(meta.Thanos.Labels)), "ext labels does not match") 392 testutil.Equals(t, int64(124), meta.Thanos.Downsample.Resolution) 393 testutil.Assert(t, len(meta.Thanos.SegmentFiles) > 0, "compacted blocks have segment files set") 394 // Only one chunk will be generated in that block, so we won't set chunk size. 395 testutil.Assert(t, meta.Thanos.IndexStats.SeriesMaxSize > 0, "compacted blocks have index stats series max size set") 396 } 397 { 398 meta, ok := others[groupKey2] 399 testutil.Assert(t, ok, "meta not found") 400 401 testutil.Equals(t, int64(0), meta.MinTime) 402 testutil.Equals(t, int64(3000), meta.MaxTime) 403 testutil.Equals(t, uint64(5), meta.Stats.NumSeries) 404 testutil.Equals(t, uint64(2*4*100-100), meta.Stats.NumSamples) 405 testutil.Equals(t, 2, meta.Compaction.Level) 406 testutil.Equals(t, []ulid.ULID{metas[6].ULID, metas[7].ULID}, meta.Compaction.Sources) 407 408 // Check thanos meta. 409 testutil.Assert(t, labels.Equal(extLabels2, labels.FromMap(meta.Thanos.Labels)), "ext labels does not match") 410 testutil.Equals(t, int64(124), meta.Thanos.Downsample.Resolution) 411 testutil.Assert(t, len(meta.Thanos.SegmentFiles) > 0, "compacted blocks have segment files set") 412 // Only one chunk will be generated in that block, so we won't set chunk size. 413 testutil.Assert(t, meta.Thanos.IndexStats.SeriesMaxSize > 0, "compacted blocks have index stats series max size set") 414 } 415 }) 416 } 417 418 type blockgenSpec struct { 419 mint, maxt int64 420 series []labels.Labels 421 numSamples int 422 extLset labels.Labels 423 res int64 424 } 425 426 func createAndUpload(t testing.TB, bkt objstore.Bucket, blocks []blockgenSpec, blocksWithOutOfOrderChunks []blockgenSpec) (metas []*metadata.Meta) { 427 prepareDir := t.TempDir() 428 429 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 430 defer cancel() 431 432 for _, b := range blocks { 433 id, meta := createBlock(t, ctx, prepareDir, b) 434 metas = append(metas, meta) 435 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(prepareDir, id.String()), metadata.NoneFunc)) 436 } 437 for _, b := range blocksWithOutOfOrderChunks { 438 id, meta := createBlock(t, ctx, prepareDir, b) 439 440 err := e2eutil.PutOutOfOrderIndex(filepath.Join(prepareDir, id.String()), b.mint, b.maxt) 441 testutil.Ok(t, err) 442 443 metas = append(metas, meta) 444 testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(prepareDir, id.String()), metadata.NoneFunc)) 445 } 446 447 return metas 448 } 449 func createBlock(t testing.TB, ctx context.Context, prepareDir string, b blockgenSpec) (id ulid.ULID, meta *metadata.Meta) { 450 var err error 451 if b.numSamples == 0 { 452 id, err = e2eutil.CreateEmptyBlock(prepareDir, b.mint, b.maxt, b.extLset, b.res) 453 } else { 454 id, err = e2eutil.CreateBlock(ctx, prepareDir, b.series, b.numSamples, b.mint, b.maxt, b.extLset, b.res, metadata.NoneFunc) 455 } 456 testutil.Ok(t, err) 457 458 meta, err = metadata.ReadFromDir(filepath.Join(prepareDir, id.String())) 459 testutil.Ok(t, err) 460 return 461 } 462 463 // Regression test for #2459 issue. 464 func TestGarbageCollectDoesntCreateEmptyBlocksWithDeletionMarksOnly(t *testing.T) { 465 logger := log.NewLogfmtLogger(os.Stderr) 466 467 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 468 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 469 defer cancel() 470 471 // Generate two blocks, and then another block that covers both of them. 472 var metas []*metadata.Meta 473 var ids []ulid.ULID 474 475 for i := 0; i < 2; i++ { 476 var m metadata.Meta 477 478 m.Version = 1 479 m.ULID = ulid.MustNew(uint64(i), nil) 480 m.Compaction.Sources = []ulid.ULID{m.ULID} 481 m.Compaction.Level = 1 482 483 ids = append(ids, m.ULID) 484 metas = append(metas, &m) 485 } 486 487 var m1 metadata.Meta 488 m1.Version = 1 489 m1.ULID = ulid.MustNew(100, nil) 490 m1.Compaction.Level = 2 491 m1.Compaction.Sources = ids 492 m1.Thanos.Downsample.Resolution = 0 493 494 // Create all blocks in the bucket. 495 for _, m := range append(metas, &m1) { 496 fmt.Println("create", m.ULID) 497 var buf bytes.Buffer 498 testutil.Ok(t, json.NewEncoder(&buf).Encode(&m)) 499 testutil.Ok(t, bkt.Upload(ctx, path.Join(m.ULID.String(), metadata.MetaFilename), &buf)) 500 } 501 502 blocksMarkedForDeletion := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 503 garbageCollectedBlocks := promauto.With(nil).NewCounter(prometheus.CounterOpts{}) 504 ignoreDeletionMarkFilter := block.NewIgnoreDeletionMarkFilter(nil, objstore.WithNoopInstr(bkt), 48*time.Hour, fetcherConcurrency) 505 506 duplicateBlocksFilter := block.NewDeduplicateFilter(fetcherConcurrency) 507 metaFetcher, err := block.NewMetaFetcher(nil, 32, objstore.WithNoopInstr(bkt), "", nil, []block.MetadataFilter{ 508 ignoreDeletionMarkFilter, 509 duplicateBlocksFilter, 510 }) 511 testutil.Ok(t, err) 512 513 sy, err := NewMetaSyncer(nil, nil, bkt, metaFetcher, duplicateBlocksFilter, ignoreDeletionMarkFilter, blocksMarkedForDeletion, garbageCollectedBlocks) 514 testutil.Ok(t, err) 515 516 // Do one initial synchronization with the bucket. 517 testutil.Ok(t, sy.SyncMetas(ctx)) 518 testutil.Ok(t, sy.GarbageCollect(ctx)) 519 testutil.Equals(t, 2.0, promtest.ToFloat64(garbageCollectedBlocks)) 520 521 rem, err := listBlocksMarkedForDeletion(ctx, bkt) 522 testutil.Ok(t, err) 523 524 sort.Slice(rem, func(i, j int) bool { 525 return rem[i].Compare(rem[j]) < 0 526 }) 527 528 testutil.Equals(t, ids, rem) 529 530 // Delete source blocks. 531 for _, id := range ids { 532 testutil.Ok(t, block.Delete(ctx, logger, bkt, id)) 533 } 534 535 // After another garbage-collect, we should not find new blocks that are deleted with new deletion mark files. 536 testutil.Ok(t, sy.SyncMetas(ctx)) 537 testutil.Ok(t, sy.GarbageCollect(ctx)) 538 539 rem, err = listBlocksMarkedForDeletion(ctx, bkt) 540 testutil.Ok(t, err) 541 testutil.Equals(t, 0, len(rem)) 542 }) 543 } 544 545 func listBlocksMarkedForDeletion(ctx context.Context, bkt objstore.Bucket) ([]ulid.ULID, error) { 546 var rem []ulid.ULID 547 err := bkt.Iter(ctx, "", func(n string) error { 548 id := ulid.MustParse(n[:len(n)-1]) 549 deletionMarkFile := path.Join(id.String(), metadata.DeletionMarkFilename) 550 551 exists, err := bkt.Exists(ctx, deletionMarkFile) 552 if err != nil { 553 return err 554 } 555 if exists { 556 rem = append(rem, id) 557 } 558 return nil 559 }) 560 return rem, err 561 }