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  }