github.com/thanos-io/thanos@v0.32.5/pkg/compact/planner_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  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"testing"
    13  
    14  	"github.com/go-kit/log"
    15  	"github.com/oklog/ulid"
    16  	"github.com/pkg/errors"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"github.com/prometheus/client_golang/prometheus/promauto"
    19  	promtest "github.com/prometheus/client_golang/prometheus/testutil"
    20  	"github.com/prometheus/prometheus/tsdb"
    21  	"github.com/thanos-io/objstore"
    22  
    23  	"github.com/efficientgo/core/testutil"
    24  	"github.com/thanos-io/thanos/pkg/block"
    25  	"github.com/thanos-io/thanos/pkg/block/metadata"
    26  )
    27  
    28  type tsdbPlannerAdapter struct {
    29  	dir  string
    30  	comp tsdb.Compactor
    31  }
    32  
    33  func (p *tsdbPlannerAdapter) Plan(_ context.Context, metasByMinTime []*metadata.Meta, errChan chan error, _ any) ([]*metadata.Meta, error) {
    34  	// TSDB planning works based on the meta.json files in the given dir. Mock it up.
    35  	for _, meta := range metasByMinTime {
    36  		bdir := filepath.Join(p.dir, meta.ULID.String())
    37  		if err := os.MkdirAll(bdir, 0777); err != nil {
    38  			return nil, errors.Wrap(err, "create planning block dir")
    39  		}
    40  		if err := meta.WriteToDir(log.NewNopLogger(), bdir); err != nil {
    41  			return nil, errors.Wrap(err, "write planning meta file")
    42  		}
    43  	}
    44  	plan, err := p.comp.Plan(p.dir)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	var res []*metadata.Meta
    50  	for _, pdir := range plan {
    51  		meta, err := metadata.ReadFromDir(pdir)
    52  		if err != nil {
    53  			return nil, errors.Wrapf(err, "read meta from %s", pdir)
    54  		}
    55  		res = append(res, meta)
    56  	}
    57  	return res, nil
    58  }
    59  
    60  // Adapted from https://github.com/prometheus/prometheus/blob/6c56a1faaaad07317ff585bda75b99bdba0517ad/tsdb/compact_test.go#L167
    61  func TestPlanners_Plan_Compatibility(t *testing.T) {
    62  	ranges := []int64{
    63  		20,
    64  		60,
    65  		180,
    66  		540,
    67  		1620,
    68  	}
    69  
    70  	// This mimics our default ExponentialBlockRanges with min block size equals to 20.
    71  	tsdbComp, err := tsdb.NewLeveledCompactor(context.Background(), nil, nil, ranges, nil, nil)
    72  	testutil.Ok(t, err)
    73  	tsdbPlanner := &tsdbPlannerAdapter{comp: tsdbComp}
    74  	tsdbBasedPlanner := NewTSDBBasedPlanner(log.NewNopLogger(), ranges)
    75  
    76  	for _, c := range []struct {
    77  		name     string
    78  		metas    []*metadata.Meta
    79  		expected []*metadata.Meta
    80  	}{
    81  		{
    82  			name: "Outside range",
    83  			metas: []*metadata.Meta{
    84  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
    85  			},
    86  		},
    87  		{
    88  			name: "We should wait for four blocks of size 20 to appear before compacting.",
    89  			metas: []*metadata.Meta{
    90  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
    91  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
    92  			},
    93  		},
    94  		{
    95  			name: `We should wait for a next block of size 20 to appear before compacting
    96  		the existing ones. We have three, but we ignore the fresh one from WAl`,
    97  			metas: []*metadata.Meta{
    98  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
    99  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   100  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   101  			},
   102  		},
   103  		{
   104  			name: "Block to fill the entire parent range appeared – should be compacted",
   105  			metas: []*metadata.Meta{
   106  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   107  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   108  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   109  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   110  			},
   111  			expected: []*metadata.Meta{
   112  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   113  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   114  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   115  			},
   116  		},
   117  		{
   118  			name: "There are blocks to fill the entire 2nd parent range.",
   119  			metas: []*metadata.Meta{
   120  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 0, MaxTime: 60}},
   121  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 60, MaxTime: 120}},
   122  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 120, MaxTime: 180}},
   123  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(9, nil), MinTime: 180, MaxTime: 200}},
   124  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(10, nil), MinTime: 200, MaxTime: 220}},
   125  			},
   126  			expected: []*metadata.Meta{
   127  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 0, MaxTime: 60}},
   128  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 60, MaxTime: 120}},
   129  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 120, MaxTime: 180}},
   130  			},
   131  		},
   132  		{
   133  			name: `Block for the next parent range appeared with gap with size 20. Nothing will happen in the first one
   134  		anymore but we ignore fresh one still, so no compaction`,
   135  			metas: []*metadata.Meta{
   136  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   137  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   138  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   139  			},
   140  		},
   141  		{
   142  			name: `Block for the next parent range appeared, and we have a gap with size 20 between second and third block.
   143  		We will not get this missed gap anymore and we should compact just these two.`,
   144  			metas: []*metadata.Meta{
   145  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   146  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   147  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   148  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 80, MaxTime: 100}},
   149  			},
   150  			expected: []*metadata.Meta{
   151  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   152  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   153  			},
   154  		},
   155  		{
   156  			name: "We have 20, 20, 20, 60, 60 range blocks. '5' is marked as fresh one",
   157  			metas: []*metadata.Meta{
   158  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   159  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   160  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   161  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   162  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 120, MaxTime: 180}},
   163  			},
   164  			expected: []*metadata.Meta{
   165  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   166  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   167  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   168  			},
   169  		},
   170  		{
   171  			name: "There are blocks to fill the entire 2nd parent range, but there is a gap",
   172  			metas: []*metadata.Meta{
   173  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 0, MaxTime: 60}},
   174  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 120, MaxTime: 180}},
   175  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(9, nil), MinTime: 180, MaxTime: 200}},
   176  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(10, nil), MinTime: 200, MaxTime: 220}},
   177  			},
   178  			expected: []*metadata.Meta{
   179  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 0, MaxTime: 60}},
   180  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 120, MaxTime: 180}},
   181  			},
   182  		},
   183  		{
   184  			name: "We have 20, 60, 20, 60, 240 range blocks. We can compact 20 + 60 + 60",
   185  			metas: []*metadata.Meta{
   186  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   187  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   188  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 960, MaxTime: 980}}, // Fresh one.
   189  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 120, MaxTime: 180}},
   190  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 720, MaxTime: 960}},
   191  			},
   192  			expected: []*metadata.Meta{
   193  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   194  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   195  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 120, MaxTime: 180}},
   196  			},
   197  		},
   198  		{
   199  			name: "Do not select large blocks that have many tombstones when there is no fresh block",
   200  			metas: []*metadata.Meta{
   201  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 540, Stats: tsdb.BlockStats{
   202  					NumSeries:     10,
   203  					NumTombstones: 3,
   204  				}}},
   205  			},
   206  		},
   207  		{
   208  			name: "Select large blocks that have many tombstones when fresh appears",
   209  			metas: []*metadata.Meta{
   210  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 540, Stats: tsdb.BlockStats{
   211  					NumSeries:     10,
   212  					NumTombstones: 3,
   213  				}}},
   214  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 540, MaxTime: 560}},
   215  			},
   216  			expected: []*metadata.Meta{{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 540, Stats: tsdb.BlockStats{
   217  				NumSeries:     10,
   218  				NumTombstones: 3,
   219  			}}}},
   220  		},
   221  		{
   222  			name: "For small blocks, do not compact tombstones, even when fresh appears.",
   223  			metas: []*metadata.Meta{
   224  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 60, Stats: tsdb.BlockStats{
   225  					NumSeries:     10,
   226  					NumTombstones: 3,
   227  				}}},
   228  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 60, MaxTime: 80}},
   229  			},
   230  		},
   231  		{
   232  			name: `Regression test: we were stuck in a compact loop where we always recompacted
   233  		the same block when tombstones and series counts were zero`,
   234  			metas: []*metadata.Meta{
   235  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 540, Stats: tsdb.BlockStats{
   236  					NumSeries:     0,
   237  					NumTombstones: 0,
   238  				}}},
   239  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 540, MaxTime: 560}},
   240  			},
   241  		},
   242  		{
   243  			name: `Regression test: we were wrongly assuming that new block is fresh from WAL when its ULID is newest.
   244  		We need to actually look on max time instead.
   245  
   246  		With previous, wrong approach "8" block was ignored, so we were wrongly compacting 5 and 7 and introducing
   247  		block overlaps`,
   248  			metas: []*metadata.Meta{
   249  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 0, MaxTime: 360}},
   250  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 540, MaxTime: 560}}, // Fresh one.
   251  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 360, MaxTime: 420}},
   252  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 420, MaxTime: 540}},
   253  			},
   254  			expected: []*metadata.Meta{
   255  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 360, MaxTime: 420}},
   256  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 420, MaxTime: 540}},
   257  			},
   258  		},
   259  		// |--------------|
   260  		//               |----------------|
   261  		//                                |--------------|
   262  		{
   263  			name: "Overlapping blocks 1",
   264  			metas: []*metadata.Meta{
   265  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   266  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 19, MaxTime: 40}},
   267  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   268  			},
   269  			expected: []*metadata.Meta{
   270  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   271  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 19, MaxTime: 40}},
   272  			},
   273  		},
   274  		// |--------------|
   275  		//                |--------------|
   276  		//                        |--------------|
   277  		{
   278  			name: "Overlapping blocks 2",
   279  			metas: []*metadata.Meta{
   280  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   281  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   282  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 30, MaxTime: 50}},
   283  			},
   284  			expected: []*metadata.Meta{
   285  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   286  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 30, MaxTime: 50}},
   287  			},
   288  		},
   289  		// |--------------|
   290  		//         |---------------------|
   291  		//                       |--------------|
   292  		{
   293  			name: "Overlapping blocks 3",
   294  			metas: []*metadata.Meta{
   295  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   296  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 10, MaxTime: 40}},
   297  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 30, MaxTime: 50}},
   298  			},
   299  			expected: []*metadata.Meta{
   300  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   301  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 10, MaxTime: 40}},
   302  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 30, MaxTime: 50}},
   303  			},
   304  		},
   305  		// |--------------|
   306  		//               |--------------------------------|
   307  		//                |--------------|
   308  		//                               |--------------|
   309  		{
   310  			name: "Overlapping blocks 4",
   311  			metas: []*metadata.Meta{
   312  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 0, MaxTime: 360}},
   313  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 340, MaxTime: 560}},
   314  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 360, MaxTime: 420}},
   315  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 420, MaxTime: 540}},
   316  			},
   317  			expected: []*metadata.Meta{
   318  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 0, MaxTime: 360}},
   319  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 340, MaxTime: 560}},
   320  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 360, MaxTime: 420}},
   321  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(8, nil), MinTime: 420, MaxTime: 540}},
   322  			},
   323  		},
   324  		// |--------------|
   325  		//               |--------------|
   326  		//                                            |--------------|
   327  		//                                                          |--------------|
   328  		{
   329  			name: "Overlapping blocks 5",
   330  			metas: []*metadata.Meta{
   331  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 10}},
   332  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 9, MaxTime: 20}},
   333  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 30, MaxTime: 40}},
   334  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 39, MaxTime: 50}},
   335  			},
   336  			expected: []*metadata.Meta{
   337  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 10}},
   338  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 9, MaxTime: 20}},
   339  			},
   340  		},
   341  	} {
   342  		t.Run(c.name, func(t *testing.T) {
   343  			for _, e := range c.expected {
   344  				// Add here to avoid boilerplate.
   345  				e.Thanos.Labels = make(map[string]string)
   346  			}
   347  			for _, e := range c.metas {
   348  				// Add here to avoid boilerplate.
   349  				e.Thanos.Labels = make(map[string]string)
   350  			}
   351  
   352  			// For compatibility.
   353  			t.Run("tsdbPlannerAdapter", func(t *testing.T) {
   354  				dir, err := os.MkdirTemp("", "test-compact")
   355  				testutil.Ok(t, err)
   356  				defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
   357  
   358  				metasByMinTime := make([]*metadata.Meta, len(c.metas))
   359  				for i := range metasByMinTime {
   360  					metasByMinTime[i] = c.metas[i]
   361  				}
   362  				sort.Slice(metasByMinTime, func(i, j int) bool {
   363  					return metasByMinTime[i].MinTime < metasByMinTime[j].MinTime
   364  				})
   365  
   366  				tsdbPlanner.dir = dir
   367  				plan, err := tsdbPlanner.Plan(context.Background(), metasByMinTime, nil, nil)
   368  				testutil.Ok(t, err)
   369  				testutil.Equals(t, c.expected, plan)
   370  			})
   371  			t.Run("tsdbBasedPlanner", func(t *testing.T) {
   372  				metasByMinTime := make([]*metadata.Meta, len(c.metas))
   373  				for i := range metasByMinTime {
   374  					metasByMinTime[i] = c.metas[i]
   375  				}
   376  				sort.Slice(metasByMinTime, func(i, j int) bool {
   377  					return metasByMinTime[i].MinTime < metasByMinTime[j].MinTime
   378  				})
   379  
   380  				plan, err := tsdbBasedPlanner.Plan(context.Background(), metasByMinTime, nil, nil)
   381  				testutil.Ok(t, err)
   382  				testutil.Equals(t, c.expected, plan)
   383  			})
   384  		})
   385  	}
   386  }
   387  
   388  // Adapted form: https://github.com/prometheus/prometheus/blob/6c56a1faaaad07317ff585bda75b99bdba0517ad/tsdb/compact_test.go#L377
   389  func TestRangeWithFailedCompactionWontGetSelected(t *testing.T) {
   390  	ranges := []int64{
   391  		20,
   392  		60,
   393  		180,
   394  		540,
   395  		1620,
   396  	}
   397  
   398  	// This mimics our default ExponentialBlockRanges with min block size equals to 20.
   399  	tsdbComp, err := tsdb.NewLeveledCompactor(context.Background(), nil, nil, ranges, nil, nil)
   400  	testutil.Ok(t, err)
   401  	tsdbPlanner := &tsdbPlannerAdapter{comp: tsdbComp}
   402  	tsdbBasedPlanner := NewTSDBBasedPlanner(log.NewNopLogger(), ranges)
   403  
   404  	for _, c := range []struct {
   405  		metas []*metadata.Meta
   406  	}{
   407  		{
   408  			metas: []*metadata.Meta{
   409  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   410  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   411  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   412  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   413  			},
   414  		},
   415  		{
   416  			metas: []*metadata.Meta{
   417  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   418  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   419  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   420  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 80, MaxTime: 100}},
   421  			},
   422  		},
   423  		{
   424  			metas: []*metadata.Meta{
   425  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   426  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   427  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   428  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   429  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 120, MaxTime: 180}},
   430  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 180, MaxTime: 200}},
   431  			},
   432  		},
   433  	} {
   434  		t.Run("", func(t *testing.T) {
   435  			c.metas[1].Compaction.Failed = true
   436  			// For compatibility.
   437  			t.Run("tsdbPlannerAdapter", func(t *testing.T) {
   438  				dir, err := os.MkdirTemp("", "test-compact")
   439  				testutil.Ok(t, err)
   440  				defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
   441  
   442  				tsdbPlanner.dir = dir
   443  				plan, err := tsdbPlanner.Plan(context.Background(), c.metas, nil, nil)
   444  				testutil.Ok(t, err)
   445  				testutil.Equals(t, []*metadata.Meta(nil), plan)
   446  			})
   447  			t.Run("tsdbBasedPlanner", func(t *testing.T) {
   448  				plan, err := tsdbBasedPlanner.Plan(context.Background(), c.metas, nil, nil)
   449  				testutil.Ok(t, err)
   450  				testutil.Equals(t, []*metadata.Meta(nil), plan)
   451  			})
   452  		})
   453  	}
   454  }
   455  
   456  func TestTSDBBasedPlanner_PlanWithNoCompactMarks(t *testing.T) {
   457  	ranges := []int64{
   458  		20,
   459  		60,
   460  		180,
   461  		540,
   462  		1620,
   463  	}
   464  
   465  	g := &GatherNoCompactionMarkFilter{}
   466  	tsdbBasedPlanner := NewPlanner(log.NewNopLogger(), ranges, g)
   467  
   468  	for _, c := range []struct {
   469  		name           string
   470  		metas          []*metadata.Meta
   471  		noCompactMarks map[ulid.ULID]*metadata.NoCompactMark
   472  
   473  		expected []*metadata.Meta
   474  	}{
   475  		{
   476  			name: "Outside range and excluded",
   477  			metas: []*metadata.Meta{
   478  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   479  			},
   480  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   481  				ulid.MustNew(1, nil): {},
   482  			},
   483  		},
   484  		{
   485  			name: "Blocks to fill the entire parent, but with first one excluded.",
   486  			metas: []*metadata.Meta{
   487  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   488  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   489  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   490  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   491  			},
   492  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   493  				ulid.MustNew(1, nil): {},
   494  			},
   495  			expected: []*metadata.Meta{
   496  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   497  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   498  			},
   499  		},
   500  		{
   501  			name: "Blocks to fill the entire parent, but with second one excluded.",
   502  			metas: []*metadata.Meta{
   503  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   504  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   505  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   506  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   507  			},
   508  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   509  				ulid.MustNew(2, nil): {},
   510  			},
   511  		},
   512  		{
   513  			name: "Blocks to fill the entire parent, but with last one excluded.",
   514  			metas: []*metadata.Meta{
   515  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   516  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   517  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   518  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   519  			},
   520  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   521  				ulid.MustNew(4, nil): {},
   522  			},
   523  			expected: []*metadata.Meta{
   524  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   525  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   526  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   527  			},
   528  		},
   529  		{
   530  			name: "Blocks to fill the entire parent, but with last one fist excluded.",
   531  			metas: []*metadata.Meta{
   532  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   533  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   534  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   535  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   536  			},
   537  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   538  				ulid.MustNew(1, nil): {},
   539  				ulid.MustNew(4, nil): {},
   540  			},
   541  			expected: []*metadata.Meta{
   542  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   543  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   544  			},
   545  		},
   546  		{
   547  			name: "Blocks to fill the entire parent, but with all of them excluded.",
   548  			metas: []*metadata.Meta{
   549  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   550  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   551  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   552  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   553  			},
   554  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   555  				ulid.MustNew(1, nil): {},
   556  				ulid.MustNew(2, nil): {},
   557  				ulid.MustNew(3, nil): {},
   558  				ulid.MustNew(4, nil): {},
   559  			},
   560  		},
   561  		{
   562  			name: `Block for the next parent range appeared, and we have a gap with size 20 between second and third block.
   563  		Second block is excluded.`,
   564  			metas: []*metadata.Meta{
   565  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   566  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   567  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   568  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 80, MaxTime: 100}},
   569  			},
   570  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   571  				ulid.MustNew(2, nil): {},
   572  			},
   573  		},
   574  		{
   575  			name: "We have 20, 60, 20, 60, 240 range blocks. We could compact 20 + 60 + 60, but sixth 6th is excluded",
   576  			metas: []*metadata.Meta{
   577  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   578  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   579  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 960, MaxTime: 980}}, // Fresh one.
   580  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 120, MaxTime: 180}},
   581  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 720, MaxTime: 960}},
   582  			},
   583  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   584  				ulid.MustNew(6, nil): {},
   585  			},
   586  			expected: []*metadata.Meta{
   587  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   588  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   589  			},
   590  		},
   591  		{
   592  			name: "We have 20, 60, 20, 60, 240 range blocks. We could compact 20 + 60 + 60, but 4th is excluded",
   593  			metas: []*metadata.Meta{
   594  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   595  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   596  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 960, MaxTime: 980}}, // Fresh one.
   597  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 120, MaxTime: 180}},
   598  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 720, MaxTime: 960}},
   599  			},
   600  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   601  				ulid.MustNew(4, nil): {},
   602  			},
   603  		},
   604  		{
   605  			name: "Do not select large blocks that have many tombstones when fresh appears but are excluded",
   606  			metas: []*metadata.Meta{
   607  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 540, Stats: tsdb.BlockStats{
   608  					NumSeries:     10,
   609  					NumTombstones: 3,
   610  				}}},
   611  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 540, MaxTime: 560}},
   612  			},
   613  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   614  				ulid.MustNew(1, nil): {},
   615  			},
   616  		},
   617  		// |--------------|
   618  		//               |----------------|
   619  		//                                |--------------|
   620  		{
   621  			name: "Overlapping blocks 1, but one is excluded",
   622  			metas: []*metadata.Meta{
   623  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   624  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 19, MaxTime: 40}},
   625  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   626  			},
   627  			noCompactMarks: map[ulid.ULID]*metadata.NoCompactMark{
   628  				ulid.MustNew(1, nil): {},
   629  			},
   630  		},
   631  	} {
   632  		t.Run(c.name, func(t *testing.T) {
   633  			metasByMinTime := make([]*metadata.Meta, len(c.metas))
   634  			for i := range metasByMinTime {
   635  				metasByMinTime[i] = c.metas[i]
   636  			}
   637  			sort.Slice(metasByMinTime, func(i, j int) bool {
   638  				return metasByMinTime[i].MinTime < metasByMinTime[j].MinTime
   639  			})
   640  			g.noCompactMarkedMap = c.noCompactMarks
   641  			plan, err := tsdbBasedPlanner.Plan(context.Background(), metasByMinTime, nil, nil)
   642  			testutil.Ok(t, err)
   643  			testutil.Equals(t, c.expected, plan)
   644  		})
   645  	}
   646  }
   647  
   648  func TestLargeTotalIndexSizeFilter_Plan(t *testing.T) {
   649  	ranges := []int64{
   650  		20,
   651  		60,
   652  		180,
   653  		540,
   654  		1620,
   655  	}
   656  
   657  	bkt := objstore.NewInMemBucket()
   658  	g := &GatherNoCompactionMarkFilter{}
   659  
   660  	marked := promauto.With(nil).NewCounter(prometheus.CounterOpts{})
   661  	planner := WithLargeTotalIndexSizeFilter(NewPlanner(log.NewNopLogger(), ranges, g), bkt, 100, marked)
   662  	var lastMarkValue float64
   663  	for _, c := range []struct {
   664  		name  string
   665  		metas []*metadata.Meta
   666  
   667  		expected      []*metadata.Meta
   668  		expectedMarks float64
   669  	}{
   670  		{
   671  			name: "Outside range and excluded",
   672  			metas: []*metadata.Meta{
   673  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 100}}},
   674  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   675  			},
   676  			expectedMarks: 0,
   677  		},
   678  		{
   679  			name: "Blocks to fill the entire parent, but with first one too large.",
   680  			metas: []*metadata.Meta{
   681  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 41}}},
   682  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   683  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   684  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   685  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   686  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   687  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   688  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   689  			},
   690  			expectedMarks: 1,
   691  			expected: []*metadata.Meta{
   692  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   693  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   694  			},
   695  		},
   696  		{
   697  			name: "Blocks to fill the entire parent, but with second one too large.",
   698  			metas: []*metadata.Meta{
   699  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   700  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   701  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 41}}},
   702  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   703  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   704  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   705  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 20}}},
   706  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   707  			},
   708  			expectedMarks: 1,
   709  		},
   710  		{
   711  			name: "Blocks to fill the entire parent, but with last size exceeded (should not matter and not even marked).",
   712  			metas: []*metadata.Meta{
   713  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 10}}},
   714  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   715  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 10}}},
   716  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   717  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 10}}},
   718  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   719  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   720  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   721  			},
   722  			expected: []*metadata.Meta{
   723  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   724  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   725  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   726  			},
   727  		},
   728  		{
   729  			name: "Blocks to fill the entire parent, but with pre-last one and first too large.",
   730  			metas: []*metadata.Meta{
   731  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   732  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   733  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   734  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   735  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   736  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 50}},
   737  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   738  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 50, MaxTime: 60}},
   739  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   740  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 60, MaxTime: 80}},
   741  			},
   742  			expected: []*metadata.Meta{
   743  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   744  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 50}},
   745  			},
   746  			expectedMarks: 2,
   747  		},
   748  		{
   749  			name: `Block for the next parent range appeared, and we have a gap with size 20 between second and third block.
   750  		Second block is excluded.`,
   751  			metas: []*metadata.Meta{
   752  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   753  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   754  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   755  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   756  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   757  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 80}},
   758  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   759  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 80, MaxTime: 100}},
   760  			},
   761  			expectedMarks: 1,
   762  		},
   763  		{
   764  			name: "We have 20, 60, 20, 60, 240 range blocks. We could compact 20 + 60 + 60, but sixth 6th is excluded",
   765  			metas: []*metadata.Meta{
   766  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   767  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   768  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   769  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   770  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   771  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(5, nil), MinTime: 960, MaxTime: 980}}, // Fresh one.
   772  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   773  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(6, nil), MinTime: 120, MaxTime: 180}},
   774  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   775  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(7, nil), MinTime: 720, MaxTime: 960}},
   776  			},
   777  			expected: []*metadata.Meta{
   778  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 40}},
   779  				{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(4, nil), MinTime: 60, MaxTime: 120}},
   780  			},
   781  			expectedMarks: 1,
   782  		},
   783  		// |--------------|
   784  		//               |----------------|
   785  		//                                |--------------|
   786  		{
   787  			name: "Overlapping blocks 1, but total is too large",
   788  			metas: []*metadata.Meta{
   789  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 90}}},
   790  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(1, nil), MinTime: 0, MaxTime: 20}},
   791  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   792  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(2, nil), MinTime: 19, MaxTime: 40}},
   793  				{Thanos: metadata.Thanos{Files: []metadata.File{{RelPath: block.IndexFilename, SizeBytes: 30}}},
   794  					BlockMeta: tsdb.BlockMeta{Version: 1, ULID: ulid.MustNew(3, nil), MinTime: 40, MaxTime: 60}},
   795  			},
   796  			expectedMarks: 1,
   797  		},
   798  	} {
   799  		if !t.Run(c.name, func(t *testing.T) {
   800  			t.Run("from meta", func(t *testing.T) {
   801  				obj := bkt.Objects()
   802  				for o := range obj {
   803  					testutil.Ok(t, bkt.Delete(context.Background(), o))
   804  				}
   805  
   806  				metasByMinTime := make([]*metadata.Meta, len(c.metas))
   807  				for i := range metasByMinTime {
   808  					orig := c.metas[i]
   809  					m := &metadata.Meta{}
   810  					*m = *orig
   811  					metasByMinTime[i] = m
   812  				}
   813  				sort.Slice(metasByMinTime, func(i, j int) bool {
   814  					return metasByMinTime[i].MinTime < metasByMinTime[j].MinTime
   815  				})
   816  
   817  				plan, err := planner.Plan(context.Background(), metasByMinTime, nil, nil)
   818  				testutil.Ok(t, err)
   819  
   820  				for _, m := range plan {
   821  					// For less boilerplate.
   822  					m.Thanos = metadata.Thanos{}
   823  				}
   824  				testutil.Equals(t, c.expected, plan)
   825  				testutil.Equals(t, c.expectedMarks, promtest.ToFloat64(marked)-lastMarkValue)
   826  				lastMarkValue = promtest.ToFloat64(marked)
   827  			})
   828  			t.Run("from bkt", func(t *testing.T) {
   829  				obj := bkt.Objects()
   830  				for o := range obj {
   831  					testutil.Ok(t, bkt.Delete(context.Background(), o))
   832  				}
   833  
   834  				metasByMinTime := make([]*metadata.Meta, len(c.metas))
   835  				for i := range metasByMinTime {
   836  					orig := c.metas[i]
   837  					m := &metadata.Meta{}
   838  					*m = *orig
   839  					metasByMinTime[i] = m
   840  				}
   841  				sort.Slice(metasByMinTime, func(i, j int) bool {
   842  					return metasByMinTime[i].MinTime < metasByMinTime[j].MinTime
   843  				})
   844  
   845  				for _, m := range metasByMinTime {
   846  					testutil.Ok(t, bkt.Upload(context.Background(), filepath.Join(m.ULID.String(), block.IndexFilename), bytes.NewReader(make([]byte, m.Thanos.Files[0].SizeBytes))))
   847  					m.Thanos = metadata.Thanos{}
   848  				}
   849  
   850  				plan, err := planner.Plan(context.Background(), metasByMinTime, nil, nil)
   851  				testutil.Ok(t, err)
   852  				testutil.Equals(t, c.expected, plan)
   853  				testutil.Equals(t, c.expectedMarks, promtest.ToFloat64(marked)-lastMarkValue)
   854  
   855  				lastMarkValue = promtest.ToFloat64(marked)
   856  			})
   857  
   858  		}) {
   859  			return
   860  		}
   861  	}
   862  }