1// Copyright (c) The Thanos Authors.
2// Licensed under the Apache License 2.0.
3
4package compact
5
6import (
7	"bytes"
8	"context"
9	"io/ioutil"
10	"os"
11	"path/filepath"
12	"sort"
13	"testing"
14
15	"github.com/go-kit/kit/log"
16	"github.com/oklog/ulid"
17	"github.com/pkg/errors"
18	"github.com/prometheus/client_golang/prometheus"
19	"github.com/prometheus/client_golang/prometheus/promauto"
20	promtest "github.com/prometheus/client_golang/prometheus/testutil"
21	"github.com/prometheus/prometheus/tsdb"
22	"github.com/thanos-io/thanos/pkg/block"
23	"github.com/thanos-io/thanos/pkg/block/metadata"
24	"github.com/thanos-io/thanos/pkg/objstore"
25	"github.com/thanos-io/thanos/pkg/testutil"
26)
27
28type tsdbPlannerAdapter struct {
29	dir  string
30	comp tsdb.Compactor
31}
32
33func (p *tsdbPlannerAdapter) Plan(_ context.Context, metasByMinTime []*metadata.Meta) ([]*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
61func 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)
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 := ioutil.TempDir("", "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)
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)
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
389func 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)
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 := ioutil.TempDir("", "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)
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)
449				testutil.Ok(t, err)
450				testutil.Equals(t, []*metadata.Meta(nil), plan)
451			})
452		})
453	}
454}
455
456func 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)
642			testutil.Ok(t, err)
643			testutil.Equals(t, c.expected, plan)
644		})
645	}
646}
647
648func 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					delete(obj, 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)
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					delete(obj, 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)
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}
863