github.com/thanos-io/thanos@v0.32.5/test/e2e/compact_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package e2e_test
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/efficientgo/e2e"
    20  	e2edb "github.com/efficientgo/e2e/db"
    21  	e2emon "github.com/efficientgo/e2e/monitoring"
    22  	"github.com/efficientgo/e2e/monitoring/matchers"
    23  	"github.com/go-kit/log"
    24  	"github.com/oklog/ulid"
    25  	"github.com/prometheus/client_golang/prometheus"
    26  	"github.com/prometheus/client_golang/prometheus/promauto"
    27  	"github.com/prometheus/common/model"
    28  	"github.com/prometheus/prometheus/model/labels"
    29  	"github.com/prometheus/prometheus/model/timestamp"
    30  
    31  	"github.com/thanos-io/objstore"
    32  	"github.com/thanos-io/objstore/client"
    33  	"github.com/thanos-io/objstore/providers/s3"
    34  
    35  	"github.com/efficientgo/core/testutil"
    36  	"github.com/thanos-io/thanos/pkg/block"
    37  	"github.com/thanos-io/thanos/pkg/block/metadata"
    38  	"github.com/thanos-io/thanos/pkg/promclient"
    39  	"github.com/thanos-io/thanos/pkg/runutil"
    40  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    41  	"github.com/thanos-io/thanos/test/e2e/e2ethanos"
    42  )
    43  
    44  func isEmptyDir(name string) (bool, error) {
    45  	f, err := os.Open(name)
    46  	if err != nil {
    47  		return false, err
    48  	}
    49  	defer f.Close()
    50  
    51  	_, err = f.Readdirnames(1)
    52  	if err == io.EOF {
    53  		return true, nil
    54  	}
    55  	return false, err
    56  }
    57  
    58  type blockDesc struct {
    59  	series  []labels.Labels
    60  	extLset labels.Labels
    61  	mint    int64
    62  	maxt    int64
    63  
    64  	markedForNoCompact bool
    65  	hashFunc           metadata.HashFunc
    66  }
    67  
    68  func (b *blockDesc) Create(ctx context.Context, dir string, delay time.Duration, hf metadata.HashFunc, numSamples int) (ulid.ULID, error) {
    69  	if delay == 0*time.Second {
    70  		return e2eutil.CreateBlock(ctx, dir, b.series, numSamples, b.mint, b.maxt, b.extLset, 0, hf)
    71  	}
    72  	return e2eutil.CreateBlockWithBlockDelay(ctx, dir, b.series, numSamples, b.mint, b.maxt, delay, b.extLset, 0, hf)
    73  }
    74  
    75  func TestCompactWithStoreGateway(t *testing.T) {
    76  	testCompactWithStoreGateway(t, false)
    77  }
    78  
    79  func TestCompactWithStoreGatewayWithPenaltyDedup(t *testing.T) {
    80  	testCompactWithStoreGateway(t, true)
    81  }
    82  
    83  func testCompactWithStoreGateway(t *testing.T, penaltyDedup bool) {
    84  	t.Parallel()
    85  	logger := log.NewLogfmtLogger(os.Stdout)
    86  
    87  	justAfterConsistencyDelay := 30 * time.Minute
    88  	// Make sure to take realistic timestamp for start. This is to align blocks as if they would be aligned on Prometheus.
    89  	// To have deterministic compaction, let's have fixed date:
    90  	now, err := time.Parse(time.RFC3339, "2020-03-24T08:00:00Z")
    91  	testutil.Ok(t, err)
    92  
    93  	var blocksWithHashes []ulid.ULID
    94  
    95  	// Simulate real scenario, including more complex cases like overlaps if needed.
    96  	// TODO(bwplotka): Test delayed delete.
    97  	blocks := []blockDesc{
    98  		// Non overlapping blocks, not ready for compaction.
    99  		{
   100  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   101  			extLset: labels.FromStrings("case", "no-compaction", "replica", "1"),
   102  			mint:    timestamp.FromTime(now),
   103  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   104  		},
   105  		{
   106  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   107  			extLset: labels.FromStrings("case", "no-compaction", "replica", "1"),
   108  			mint:    timestamp.FromTime(now.Add(2 * time.Hour)),
   109  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   110  		},
   111  		{
   112  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   113  			extLset: labels.FromStrings("case", "no-compaction", "replica", "1"),
   114  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   115  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   116  		},
   117  	}
   118  	blocks = append(blocks,
   119  		// Non overlapping blocks, ready for compaction.
   120  		blockDesc{
   121  			series:   []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   122  			extLset:  labels.FromStrings("case", "compaction-ready", "replica", "1"),
   123  			mint:     timestamp.FromTime(now),
   124  			maxt:     timestamp.FromTime(now.Add(2 * time.Hour)),
   125  			hashFunc: metadata.SHA256Func,
   126  		},
   127  		blockDesc{
   128  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "3")},
   129  			extLset: labels.FromStrings("case", "compaction-ready", "replica", "1"),
   130  			mint:    timestamp.FromTime(now.Add(2 * time.Hour)),
   131  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   132  		},
   133  		blockDesc{
   134  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "4")},
   135  			extLset: labels.FromStrings("case", "compaction-ready", "replica", "1"),
   136  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   137  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   138  		},
   139  		blockDesc{
   140  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "5")},
   141  			extLset: labels.FromStrings("case", "compaction-ready", "replica", "1"),
   142  			mint:    timestamp.FromTime(now.Add(6 * time.Hour)),
   143  			maxt:    timestamp.FromTime(now.Add(8 * time.Hour)),
   144  		},
   145  		blockDesc{
   146  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "6")},
   147  			extLset: labels.FromStrings("case", "compaction-ready", "replica", "1"),
   148  			mint:    timestamp.FromTime(now.Add(8 * time.Hour)),
   149  			maxt:    timestamp.FromTime(now.Add(10 * time.Hour)),
   150  		},
   151  		// Non overlapping blocks, ready for compaction, with one blocked marked for no-compact (no-compact-mark.json)
   152  		blockDesc{
   153  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   154  			extLset: labels.FromStrings("case", "compaction-ready-one-block-marked-for-no-compact", "replica", "1"),
   155  			mint:    timestamp.FromTime(now),
   156  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   157  		},
   158  		blockDesc{
   159  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "3")},
   160  			extLset: labels.FromStrings("case", "compaction-ready-one-block-marked-for-no-compact", "replica", "1"),
   161  			mint:    timestamp.FromTime(now.Add(2 * time.Hour)),
   162  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   163  		},
   164  		blockDesc{
   165  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "4")},
   166  			extLset: labels.FromStrings("case", "compaction-ready-one-block-marked-for-no-compact", "replica", "1"),
   167  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   168  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   169  
   170  			markedForNoCompact: true,
   171  		},
   172  		blockDesc{
   173  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "5")},
   174  			extLset: labels.FromStrings("case", "compaction-ready-one-block-marked-for-no-compact", "replica", "1"),
   175  			mint:    timestamp.FromTime(now.Add(6 * time.Hour)),
   176  			maxt:    timestamp.FromTime(now.Add(8 * time.Hour)),
   177  		},
   178  		blockDesc{
   179  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "6")},
   180  			extLset: labels.FromStrings("case", "compaction-ready-one-block-marked-for-no-compact", "replica", "1"),
   181  			mint:    timestamp.FromTime(now.Add(8 * time.Hour)),
   182  			maxt:    timestamp.FromTime(now.Add(10 * time.Hour)),
   183  		},
   184  
   185  		// Non overlapping blocks, ready for compaction, only after deduplication.
   186  		blockDesc{
   187  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   188  			extLset: labels.FromStrings("case", "compaction-ready-after-dedup", "replica", "1"),
   189  			mint:    timestamp.FromTime(now),
   190  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   191  		},
   192  		blockDesc{
   193  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "3")},
   194  			extLset: labels.FromStrings("case", "compaction-ready-after-dedup", "replica", "1"),
   195  			mint:    timestamp.FromTime(now.Add(2 * time.Hour)),
   196  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   197  		},
   198  		blockDesc{
   199  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "4")},
   200  			extLset: labels.FromStrings("case", "compaction-ready-after-dedup", "replica", "1"),
   201  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   202  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   203  		},
   204  		blockDesc{
   205  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "5")},
   206  			extLset: labels.FromStrings("case", "compaction-ready-after-dedup", "replica", "2"),
   207  			mint:    timestamp.FromTime(now.Add(6 * time.Hour)),
   208  			maxt:    timestamp.FromTime(now.Add(8 * time.Hour)),
   209  		},
   210  		blockDesc{
   211  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "6")},
   212  			extLset: labels.FromStrings("case", "compaction-ready-after-dedup", "replica", "1"),
   213  			mint:    timestamp.FromTime(now.Add(8 * time.Hour)),
   214  			maxt:    timestamp.FromTime(now.Add(10 * time.Hour)),
   215  		},
   216  
   217  		// Replica partial overlapping blocks, not ready for compaction, among no-overlapping blocks.
   218  		// NOTE: We put a- in front to make sure this will be compacted as first one (:
   219  		blockDesc{
   220  			series: []labels.Labels{
   221  				labels.FromStrings("a", "1", "b", "1"),
   222  				labels.FromStrings("a", "1", "b", "2"),
   223  			},
   224  			extLset: labels.FromStrings("case", "a-partial-overlap-dedup-ready", "replica", "1"),
   225  			mint:    timestamp.FromTime(now),
   226  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   227  		},
   228  		blockDesc{
   229  			series: []labels.Labels{
   230  				labels.FromStrings("a", "1", "b", "2"),
   231  				labels.FromStrings("a", "1", "b", "3"),
   232  			},
   233  			extLset: labels.FromStrings("case", "a-partial-overlap-dedup-ready", "replica", "2"),
   234  			mint:    timestamp.FromTime(now.Add(1 * time.Hour)),
   235  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   236  		},
   237  		blockDesc{
   238  			series: []labels.Labels{
   239  				labels.FromStrings("a", "1", "b", "2"),
   240  				labels.FromStrings("a", "1", "b", "4"),
   241  			},
   242  			extLset: labels.FromStrings("case", "a-partial-overlap-dedup-ready", "replica", "3"),
   243  			mint:    timestamp.FromTime(now.Add(3 * time.Hour)),
   244  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   245  		},
   246  		// Extra.
   247  		blockDesc{
   248  			series: []labels.Labels{
   249  				labels.FromStrings("a", "1", "b", "2"),
   250  				labels.FromStrings("a", "1", "b", "5"),
   251  			},
   252  			extLset: labels.FromStrings("case", "a-partial-overlap-dedup-ready", "replica", "1"),
   253  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   254  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   255  		},
   256  
   257  		// Multi-Replica partial overlapping blocks, not ready for compaction, among no-overlapping blocks.
   258  		blockDesc{
   259  			series: []labels.Labels{
   260  				labels.FromStrings("a", "1", "b", "1"),
   261  				labels.FromStrings("a", "1", "b", "2"),
   262  			},
   263  			extLset: labels.FromStrings("case", "partial-multi-replica-overlap-dedup-ready", "rule_replica", "1", "replica", "1"),
   264  			mint:    timestamp.FromTime(now),
   265  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   266  		},
   267  		blockDesc{
   268  			series: []labels.Labels{
   269  				labels.FromStrings("a", "1", "b", "2"),
   270  				labels.FromStrings("a", "1", "b", "3"),
   271  			},
   272  			extLset: labels.FromStrings("case", "partial-multi-replica-overlap-dedup-ready", "rule_replica", "2", "replica", "1"),
   273  			mint:    timestamp.FromTime(now.Add(1 * time.Hour)),
   274  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   275  		},
   276  		blockDesc{
   277  			series: []labels.Labels{
   278  				labels.FromStrings("a", "1", "b", "2"),
   279  				labels.FromStrings("a", "1", "b", "4"),
   280  			},
   281  			// TODO(bwplotka): This is wrong, but let's fix in next PR. We should error out in this case as we should
   282  			// never support overlaps before we modify dedup labels. This probably means another check in fetcher.
   283  			extLset: labels.FromStrings("case", "partial-multi-replica-overlap-dedup-ready", "rule_replica", "1", "replica", "1"),
   284  			mint:    timestamp.FromTime(now.Add(1 * time.Hour)),
   285  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   286  		},
   287  		// Extra.
   288  		blockDesc{
   289  			series: []labels.Labels{
   290  				labels.FromStrings("a", "1", "b", "2"),
   291  				labels.FromStrings("a", "1", "b", "5"),
   292  			},
   293  			extLset: labels.FromStrings("case", "partial-multi-replica-overlap-dedup-ready", "rule_replica", "1", "replica", "1"),
   294  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   295  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   296  		},
   297  
   298  		// Replica full overlapping blocks, not ready for compaction, among no-overlapping blocks.
   299  		blockDesc{
   300  			series: []labels.Labels{
   301  				labels.FromStrings("a", "1", "b", "1"),
   302  				labels.FromStrings("a", "1", "b", "2"),
   303  			},
   304  			extLset: labels.FromStrings("case", "full-replica-overlap-dedup-ready", "replica", "1"),
   305  			mint:    timestamp.FromTime(now),
   306  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   307  		},
   308  		blockDesc{
   309  			series: []labels.Labels{
   310  				labels.FromStrings("a", "1", "b", "2"),
   311  				labels.FromStrings("a", "1", "b", "3"),
   312  			},
   313  			extLset: labels.FromStrings("case", "full-replica-overlap-dedup-ready", "replica", "2"),
   314  			mint:    timestamp.FromTime(now),
   315  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   316  		},
   317  		// Extra.
   318  		blockDesc{
   319  			series: []labels.Labels{
   320  				labels.FromStrings("a", "1", "b", "2"),
   321  				labels.FromStrings("a", "1", "b", "4"),
   322  			},
   323  			extLset: labels.FromStrings("case", "full-replica-overlap-dedup-ready", "replica", "1"),
   324  			mint:    timestamp.FromTime(now.Add(2 * time.Hour)),
   325  			maxt:    timestamp.FromTime(now.Add(4 * time.Hour)),
   326  		},
   327  		blockDesc{
   328  			series: []labels.Labels{
   329  				labels.FromStrings("a", "1", "b", "2"),
   330  				labels.FromStrings("a", "1", "b", "5"),
   331  			},
   332  			extLset: labels.FromStrings("case", "full-replica-overlap-dedup-ready", "replica", "1"),
   333  			mint:    timestamp.FromTime(now.Add(4 * time.Hour)),
   334  			maxt:    timestamp.FromTime(now.Add(6 * time.Hour)),
   335  		},
   336  	)
   337  
   338  	name := "e2e-test-compact"
   339  	if penaltyDedup {
   340  		name = "compact-dedup"
   341  	}
   342  	e, err := e2e.NewDockerEnvironment(name)
   343  	testutil.Ok(t, err)
   344  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   345  
   346  	dir := filepath.Join(e.SharedDir(), "tmp")
   347  	testutil.Ok(t, os.MkdirAll(dir, os.ModePerm))
   348  
   349  	const bucket = "compact-test"
   350  	m := e2edb.NewMinio(e, "minio", bucket, e2edb.WithMinioTLS())
   351  	testutil.Ok(t, e2e.StartAndWaitReady(m))
   352  
   353  	bkt, err := s3.NewBucketWithConfig(logger,
   354  		e2ethanos.NewS3Config(bucket, m.Endpoint("http"), m.Dir()), "test-feed")
   355  	testutil.Ok(t, err)
   356  
   357  	ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
   358  	t.Cleanup(cancel)
   359  
   360  	rawBlockIDs := map[ulid.ULID]struct{}{}
   361  	for _, b := range blocks {
   362  		id, err := b.Create(ctx, dir, justAfterConsistencyDelay, b.hashFunc, 120)
   363  		testutil.Ok(t, err)
   364  		testutil.Ok(t, runutil.Retry(time.Second, ctx.Done(), func() error {
   365  			return objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String())
   366  		}))
   367  
   368  		rawBlockIDs[id] = struct{}{}
   369  		if b.markedForNoCompact {
   370  			testutil.Ok(t, block.MarkForNoCompact(ctx, logger, bkt, id, metadata.ManualNoCompactReason, "why not", promauto.With(nil).NewCounter(prometheus.CounterOpts{})))
   371  		}
   372  
   373  		if b.hashFunc != metadata.NoneFunc {
   374  			blocksWithHashes = append(blocksWithHashes, id)
   375  		}
   376  	}
   377  	// Block that will be downsampled.
   378  	downsampledBase := blockDesc{
   379  		series: []labels.Labels{
   380  			labels.FromStrings("z", "1", "b", "2"),
   381  			labels.FromStrings("z", "1", "b", "5"),
   382  		},
   383  		extLset: labels.FromStrings("case", "block-about-to-be-downsampled"),
   384  		mint:    timestamp.FromTime(now),
   385  		maxt:    timestamp.FromTime(now.Add(10 * 24 * time.Hour)),
   386  	}
   387  	// New block that will be downsampled.
   388  	downsampledRawID, err := downsampledBase.Create(ctx, dir, justAfterConsistencyDelay, metadata.NoneFunc, 1200)
   389  	testutil.Ok(t, err)
   390  	testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, downsampledRawID.String()), downsampledRawID.String()))
   391  
   392  	{
   393  		// On top of that, add couple of other tricky cases with different meta.
   394  		malformedBase := blockDesc{
   395  			series:  []labels.Labels{labels.FromStrings("a", "1", "b", "2")},
   396  			extLset: labels.FromStrings("case", "malformed-things", "replica", "101"),
   397  			mint:    timestamp.FromTime(now),
   398  			maxt:    timestamp.FromTime(now.Add(2 * time.Hour)),
   399  		}
   400  
   401  		// New Partial block.
   402  		id, err := malformedBase.Create(ctx, dir, 0*time.Second, metadata.NoneFunc, 120)
   403  		testutil.Ok(t, err)
   404  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   405  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   406  
   407  		// New Partial block + deletion mark.
   408  		id, err = malformedBase.Create(ctx, dir, 0*time.Second, metadata.NoneFunc, 120)
   409  		testutil.Ok(t, err)
   410  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   411  		testutil.Ok(t, block.MarkForDeletion(ctx, logger, bkt, id, "", promauto.With(nil).NewCounter(prometheus.CounterOpts{})))
   412  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   413  
   414  		// Partial block after consistency delay.
   415  		id, err = malformedBase.Create(ctx, dir, justAfterConsistencyDelay, metadata.NoneFunc, 120)
   416  		testutil.Ok(t, err)
   417  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   418  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   419  
   420  		// Partial block after consistency delay + deletion mark.
   421  		id, err = malformedBase.Create(ctx, dir, justAfterConsistencyDelay, metadata.NoneFunc, 120)
   422  		testutil.Ok(t, err)
   423  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   424  		testutil.Ok(t, block.MarkForDeletion(ctx, logger, bkt, id, "", promauto.With(nil).NewCounter(prometheus.CounterOpts{})))
   425  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   426  
   427  		// Partial block after consistency delay + old deletion mark ready to be deleted.
   428  		id, err = malformedBase.Create(ctx, dir, justAfterConsistencyDelay, metadata.NoneFunc, 120)
   429  		testutil.Ok(t, err)
   430  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   431  		deletionMark, err := json.Marshal(metadata.DeletionMark{
   432  			ID: id,
   433  			// Deletion threshold is usually 2 days.
   434  			DeletionTime: time.Now().Add(-50 * time.Hour).Unix(),
   435  			Version:      metadata.DeletionMarkVersion1,
   436  		})
   437  		testutil.Ok(t, err)
   438  		testutil.Ok(t, bkt.Upload(ctx, path.Join(id.String(), metadata.DeletionMarkFilename), bytes.NewBuffer(deletionMark)))
   439  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   440  
   441  		// Partial block after delete threshold.
   442  		id, err = malformedBase.Create(ctx, dir, 50*time.Hour, metadata.NoneFunc, 120)
   443  		testutil.Ok(t, err)
   444  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   445  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   446  
   447  		// Partial block after delete threshold + deletion mark.
   448  		id, err = malformedBase.Create(ctx, dir, 50*time.Hour, metadata.NoneFunc, 120)
   449  		testutil.Ok(t, err)
   450  		testutil.Ok(t, os.Remove(path.Join(dir, id.String(), metadata.MetaFilename)))
   451  		testutil.Ok(t, block.MarkForDeletion(ctx, logger, bkt, id, "", promauto.With(nil).NewCounter(prometheus.CounterOpts{})))
   452  		testutil.Ok(t, objstore.UploadDir(ctx, logger, bkt, path.Join(dir, id.String()), id.String()))
   453  	}
   454  
   455  	bktConfig := client.BucketConfig{
   456  		Type:   client.S3,
   457  		Config: e2ethanos.NewS3Config(bucket, m.InternalEndpoint("http"), m.InternalDir()),
   458  	}
   459  
   460  	// Crank down the deletion mark delay since deduplication can miss blocks in the presence of replica labels it doesn't know about.
   461  	str := e2ethanos.NewStoreGW(e, "1", bktConfig, "", "", []string{"--ignore-deletion-marks-delay=2s"})
   462  	testutil.Ok(t, e2e.StartAndWaitReady(str))
   463  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+8)), "thanos_blocks_meta_synced"))
   464  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   465  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_modified"))
   466  
   467  	q := e2ethanos.NewQuerierBuilder(e, "1", str.InternalEndpoint("grpc")).Init()
   468  	testutil.Ok(t, e2e.StartAndWaitReady(q))
   469  
   470  	ctx, cancel = context.WithTimeout(context.Background(), 3*time.Minute)
   471  	t.Cleanup(cancel)
   472  
   473  	// Check if query detects current series, even if overlapped.
   474  	queryAndAssert(t, ctx, q.Endpoint("http"),
   475  		func() string {
   476  			return fmt.Sprintf(`count_over_time({a="1"}[13h] offset %ds)`, int64(time.Since(now.Add(12*time.Hour)).Seconds()))
   477  		},
   478  		time.Now,
   479  		promclient.QueryOptions{
   480  			Deduplicate: false, // This should be false, so that we can be sure deduplication was offline.
   481  		},
   482  		model.Vector{
   483  			{Value: 360, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "no-compaction", "replica": "1"}},
   484  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready", "replica": "1"}},
   485  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready", "replica": "1"}},
   486  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready", "replica": "1"}},
   487  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready", "replica": "1"}},
   488  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready", "replica": "1"}},
   489  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   490  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   491  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   492  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   493  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   494  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-after-dedup", "replica": "1"}},
   495  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-after-dedup", "replica": "1"}},
   496  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-after-dedup", "replica": "1"}},
   497  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-after-dedup", "replica": "2"}},
   498  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-after-dedup", "replica": "1"}},
   499  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   500  			{Value: 240, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   501  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   502  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready", "replica": "2"}},
   503  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "a-partial-overlap-dedup-ready", "replica": "2"}},
   504  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready", "replica": "3"}},
   505  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "a-partial-overlap-dedup-ready", "replica": "3"}},
   506  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   507  			{Value: 320, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   508  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   509  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   510  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "2"}},
   511  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "2"}},
   512  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   513  			{Value: 360, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   514  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready", "replica": "2"}},
   515  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "full-replica-overlap-dedup-ready", "replica": "2"}},
   516  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   517  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   518  		},
   519  	)
   520  	// Store view:
   521  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+8)), "thanos_blocks_meta_synced"))
   522  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   523  	testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_modified"))
   524  
   525  	expectedEndVector := model.Vector{
   526  		// NOTE(bwplotka): Even after deduplication some series has still replica labels. This is because those blocks did not overlap yet with anything.
   527  		// This is fine as querier deduplication will remove it if needed.
   528  		{Value: 360, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "no-compaction", "replica": "1"}},
   529  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready"}},
   530  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready"}},
   531  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready"}},
   532  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready"}},
   533  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready", "replica": "1"}},
   534  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-one-block-marked-for-no-compact"}},
   535  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-one-block-marked-for-no-compact"}},
   536  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   537  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   538  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   539  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-after-dedup"}},
   540  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-after-dedup"}},
   541  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-after-dedup"}},
   542  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-after-dedup"}},
   543  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-after-dedup", "replica": "1"}},
   544  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "a-partial-overlap-dedup-ready"}},
   545  		{Value: 360, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready"}},
   546  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   547  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "a-partial-overlap-dedup-ready"}},
   548  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "a-partial-overlap-dedup-ready"}},
   549  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   550  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "partial-multi-replica-overlap-dedup-ready"}},
   551  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   552  		{Value: 240, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready"}},
   553  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "partial-multi-replica-overlap-dedup-ready"}},
   554  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "partial-multi-replica-overlap-dedup-ready"}},
   555  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   556  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "full-replica-overlap-dedup-ready"}},
   557  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready"}},
   558  		{Value: 240, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   559  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "full-replica-overlap-dedup-ready"}},
   560  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   561  		{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   562  	}
   563  
   564  	if penaltyDedup {
   565  		expectedEndVector = model.Vector{
   566  			// NOTE(bwplotka): Even after deduplication some series has still replica labels. This is because those blocks did not overlap yet with anything.
   567  			// This is fine as querier deduplication will remove it if needed.
   568  			{Value: 360, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "no-compaction", "replica": "1"}},
   569  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready"}},
   570  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready"}},
   571  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready"}},
   572  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready"}},
   573  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready", "replica": "1"}},
   574  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-one-block-marked-for-no-compact"}},
   575  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-one-block-marked-for-no-compact"}},
   576  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   577  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   578  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-one-block-marked-for-no-compact", "replica": "1"}},
   579  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "compaction-ready-after-dedup"}},
   580  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "compaction-ready-after-dedup"}},
   581  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "compaction-ready-after-dedup"}},
   582  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "compaction-ready-after-dedup"}},
   583  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "6", "case": "compaction-ready-after-dedup", "replica": "1"}},
   584  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "a-partial-overlap-dedup-ready"}},
   585  			// If no penalty dedup enabled, the value should be 360.
   586  			{Value: 200, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready"}},
   587  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   588  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "a-partial-overlap-dedup-ready"}},
   589  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "a-partial-overlap-dedup-ready"}},
   590  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "a-partial-overlap-dedup-ready", "replica": "1"}},
   591  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "partial-multi-replica-overlap-dedup-ready"}},
   592  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   593  			// If no penalty dedup enabled, the value should be 240.
   594  			{Value: 195, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "partial-multi-replica-overlap-dedup-ready"}},
   595  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "partial-multi-replica-overlap-dedup-ready"}},
   596  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "partial-multi-replica-overlap-dedup-ready"}},
   597  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "partial-multi-replica-overlap-dedup-ready", "replica": "1", "rule_replica": "1"}},
   598  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "1", "case": "full-replica-overlap-dedup-ready"}},
   599  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready"}},
   600  			{Value: 240, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "2", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   601  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "3", "case": "full-replica-overlap-dedup-ready"}},
   602  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "4", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   603  			{Value: 120, Metric: map[model.LabelName]model.LabelValue{"a": "1", "b": "5", "case": "full-replica-overlap-dedup-ready", "replica": "1"}},
   604  		}
   605  	}
   606  
   607  	// No replica label with overlaps should halt compactor. This test is sequential
   608  	// because we do not want two Thanos Compact instances deleting the same partially
   609  	// uploaded blocks and blocks with deletion marks. We also check that Thanos Compactor
   610  	// deletes directories inside of a compaction group that do not belong there.
   611  	{
   612  		cFuture := e2ethanos.NewCompactorBuilder(e, "expect-to-halt")
   613  
   614  		// Precreate a directory. It should be deleted.
   615  		// In a hypothetical scenario, the directory could be a left-over from
   616  		// a compaction that had crashed.
   617  		testutil.Assert(t, len(blocksWithHashes) > 0)
   618  
   619  		m, err := block.DownloadMeta(ctx, logger, bkt, blocksWithHashes[0])
   620  		testutil.Ok(t, err)
   621  
   622  		randBlockDir := filepath.Join(cFuture.Dir(), "compact", m.Thanos.GroupKey(), "ITISAVERYRANDULIDFORTESTS0")
   623  		testutil.Ok(t, os.MkdirAll(randBlockDir, os.ModePerm))
   624  
   625  		f, err := os.Create(filepath.Join(randBlockDir, "index"))
   626  		testutil.Ok(t, err)
   627  		testutil.Ok(t, f.Close())
   628  
   629  		c := cFuture.Init(bktConfig, nil)
   630  		testutil.Ok(t, e2e.StartAndWaitReady(c))
   631  
   632  		// Expect compactor halted and one cleanup iteration to happen.
   633  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(1), "thanos_compact_halted"))
   634  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(1), "thanos_compact_block_cleanup_loops_total"))
   635  
   636  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+6)), "thanos_blocks_meta_synced"))
   637  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   638  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_modified"))
   639  
   640  		// The compact directory is still there.
   641  		empty, err := isEmptyDir(c.Dir())
   642  		testutil.Ok(t, err)
   643  		testutil.Equals(t, false, empty, "directory %e should not be empty", c.Dir())
   644  
   645  		// We expect no ops.
   646  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_iterations_total"))
   647  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_block_cleanup_failures_total"))
   648  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_blocks_marked_total"))
   649  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_group_compactions_total"))
   650  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_group_vertical_compactions_total"))
   651  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(1), "thanos_compact_group_compactions_failures_total"))
   652  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(2), "thanos_compact_group_compaction_runs_started_total"))
   653  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(1), "thanos_compact_group_compaction_runs_completed_total"))
   654  
   655  		// However, the blocks have been cleaned because that happens concurrently.
   656  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(2), "thanos_compact_aborted_partial_uploads_deletion_attempts_total"))
   657  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(2), "thanos_compact_blocks_cleaned_total"))
   658  
   659  		// Ensure bucket UI.
   660  		ensureGETStatusCode(t, http.StatusOK, "http://"+path.Join(c.Endpoint("http"), "global"))
   661  		ensureGETStatusCode(t, http.StatusOK, "http://"+path.Join(c.Endpoint("http"), "loaded"))
   662  
   663  		testutil.Ok(t, c.Stop())
   664  
   665  		_, err = os.Stat(randBlockDir)
   666  		testutil.NotOk(t, err)
   667  		testutil.Assert(t, os.IsNotExist(err))
   668  	}
   669  
   670  	// Sequential because we want to check that Thanos Compactor does not
   671  	// touch files it does not need to.
   672  	// Dedup enabled; compactor should work as expected.
   673  	{
   674  
   675  		cFuture := e2ethanos.NewCompactorBuilder(e, "working")
   676  
   677  		// Predownload block dirs with hashes. We should not try downloading them again.
   678  		for _, id := range blocksWithHashes {
   679  			m, err := block.DownloadMeta(ctx, logger, bkt, id)
   680  			testutil.Ok(t, err)
   681  
   682  			delete(m.Thanos.Labels, "replica")
   683  			testutil.Ok(t, block.Download(ctx, logger, bkt, id, filepath.Join(cFuture.Dir(), "compact", m.Thanos.GroupKey(), id.String())))
   684  		}
   685  
   686  		extArgs := []string{"--deduplication.replica-label=replica", "--deduplication.replica-label=rule_replica"}
   687  		if penaltyDedup {
   688  			extArgs = append(extArgs, "--deduplication.func=penalty")
   689  		}
   690  
   691  		// We expect 2x 4-block compaction, 2-block vertical compaction, 2x 3-block compaction.
   692  		c := cFuture.Init(bktConfig, nil, extArgs...)
   693  		testutil.Ok(t, e2e.StartAndWaitReady(c))
   694  
   695  		// NOTE: We cannot assert on intermediate `thanos_blocks_meta_` metrics as those are gauge and change dynamically due to many
   696  		// compaction groups. Wait for at least first compaction iteration (next is in 5m).
   697  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Greater(0), "thanos_compact_iterations_total"))
   698  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_blocks_cleaned_total"))
   699  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_block_cleanup_failures_total"))
   700  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(2*4+2+2*3+2), "thanos_compact_blocks_marked_total")) // 18.
   701  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_aborted_partial_uploads_deletion_attempts_total"))
   702  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(6), "thanos_compact_group_compactions_total"))
   703  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(3), "thanos_compact_group_vertical_compactions_total"))
   704  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_group_compactions_failures_total"))
   705  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(14), "thanos_compact_group_compaction_runs_started_total"))
   706  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(14), "thanos_compact_group_compaction_runs_completed_total"))
   707  
   708  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(2), "thanos_compact_downsample_total"))
   709  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_downsample_failures_total"))
   710  
   711  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(
   712  			len(rawBlockIDs)+8+
   713  				2+ // Downsampled one block into two new ones - 5m/1h.
   714  				6+ // 6 compactions, 6 newly added blocks.
   715  				-2, // Partial block removed.
   716  		)), "thanos_blocks_meta_synced"))
   717  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   718  
   719  		testutil.Ok(t, c.WaitSumMetrics(e2emon.Equals(0), "thanos_compact_halted"))
   720  
   721  		bucketMatcher, err := matchers.NewMatcher(matchers.MatchEqual, "bucket", bucket)
   722  		testutil.Ok(t, err)
   723  		operationMatcher, err := matchers.NewMatcher(matchers.MatchEqual, "operation", "get")
   724  		testutil.Ok(t, err)
   725  		testutil.Ok(t, c.WaitSumMetricsWithOptions(
   726  			e2emon.Between(0, 1000),
   727  			[]string{"thanos_objstore_bucket_operations_total"}, e2emon.WithLabelMatchers(
   728  				bucketMatcher,
   729  				operationMatcher,
   730  			),
   731  			e2emon.WaitMissingMetrics(),
   732  		))
   733  
   734  		// Make sure compactor does not modify anything else over time.
   735  		testutil.Ok(t, c.Stop())
   736  
   737  		ctx, cancel = context.WithTimeout(context.Background(), 3*time.Minute)
   738  		t.Cleanup(cancel)
   739  
   740  		// Check if query detects new blocks.
   741  		queryAndAssert(t, ctx, q.Endpoint("http"),
   742  			func() string {
   743  				return fmt.Sprintf(`count_over_time({a="1"}[13h] offset %ds)`, int64(time.Since(now.Add(12*time.Hour)).Seconds()))
   744  			},
   745  			time.Now,
   746  			promclient.QueryOptions{
   747  				Deduplicate: false, // This should be false, so that we can be sure deduplication was offline.
   748  			},
   749  			expectedEndVector,
   750  		)
   751  		// Store view:
   752  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+8+6-2+2)), "thanos_blocks_meta_synced"))
   753  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   754  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_modified"))
   755  	}
   756  
   757  	// dedup enabled; no delete delay; compactor should work and remove things as expected.
   758  	{
   759  		extArgs := []string{"--deduplication.replica-label=replica", "--deduplication.replica-label=rule_replica", "--delete-delay=0s"}
   760  		if penaltyDedup {
   761  			extArgs = append(extArgs, "--deduplication.func=penalty")
   762  		}
   763  		c := e2ethanos.NewCompactorBuilder(e, "working-dedup").Init(bktConfig, nil, extArgs...)
   764  		testutil.Ok(t, e2e.StartAndWaitReady(c))
   765  
   766  		// NOTE: We cannot assert on intermediate `thanos_blocks_meta_` metrics as those are gauge and change dynamically due to many
   767  		// compaction groups. Wait for at least first compaction iteration (next is in 5m).
   768  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Greater(0), []string{"thanos_compact_iterations_total"}, e2emon.WaitMissingMetrics()))
   769  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(18), []string{"thanos_compact_blocks_cleaned_total"}, e2emon.WaitMissingMetrics()))
   770  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_block_cleanup_failures_total"}, e2emon.WaitMissingMetrics()))
   771  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_blocks_marked_total"}, e2emon.WaitMissingMetrics()))
   772  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_aborted_partial_uploads_deletion_attempts_total"}, e2emon.WaitMissingMetrics()))
   773  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_group_compactions_total"}, e2emon.WaitMissingMetrics()))
   774  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_group_vertical_compactions_total"}, e2emon.WaitMissingMetrics()))
   775  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_group_compactions_failures_total"}, e2emon.WaitMissingMetrics()))
   776  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(7), []string{"thanos_compact_group_compaction_runs_started_total"}, e2emon.WaitMissingMetrics()))
   777  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(7), []string{"thanos_compact_group_compaction_runs_completed_total"}, e2emon.WaitMissingMetrics()))
   778  
   779  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_downsample_total"}, e2emon.WaitMissingMetrics()))
   780  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_downsample_failures_total"}, e2emon.WaitMissingMetrics()))
   781  
   782  		testutil.Ok(t, str.WaitSumMetricsWithOptions(e2emon.Equals(float64(len(rawBlockIDs)+8+6-18-2+2)), []string{"thanos_blocks_meta_synced"}, e2emon.WaitMissingMetrics()))
   783  		testutil.Ok(t, str.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_blocks_meta_sync_failures_total"}, e2emon.WaitMissingMetrics()))
   784  
   785  		testutil.Ok(t, c.WaitSumMetricsWithOptions(e2emon.Equals(0), []string{"thanos_compact_halted"}, e2emon.WaitMissingMetrics()))
   786  		// Make sure compactor does not modify anything else over time.
   787  		testutil.Ok(t, c.Stop())
   788  
   789  		ctx, cancel = context.WithTimeout(context.Background(), 3*time.Minute)
   790  		t.Cleanup(cancel)
   791  
   792  		// Check if query detects new blocks.
   793  		queryAndAssert(t, ctx, q.Endpoint("http"),
   794  			func() string {
   795  				return fmt.Sprintf(`count_over_time({a="1"}[13h] offset %ds)`, int64(time.Since(now.Add(12*time.Hour)).Seconds()))
   796  			},
   797  			time.Now,
   798  			promclient.QueryOptions{
   799  				Deduplicate: false, // This should be false, so that we can be sure deduplication was offline.
   800  			},
   801  			expectedEndVector,
   802  		)
   803  
   804  		// Store view:
   805  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+8-18+6-2+2)), "thanos_blocks_meta_synced"))
   806  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_sync_failures_total"))
   807  		testutil.Ok(t, str.WaitSumMetrics(e2emon.Equals(0), "thanos_blocks_meta_modified"))
   808  	}
   809  
   810  	// Ensure that querying downsampled blocks works. Then delete the raw block and try querying again.
   811  	{
   812  		ctx, cancel = context.WithTimeout(context.Background(), 3*time.Minute)
   813  		defer cancel()
   814  
   815  		// Just to have a consistent result.
   816  		checkQuery := func() string {
   817  			return `last_over_time({z="1"}[2h]) - last_over_time({z="1"}[2h])`
   818  		}
   819  
   820  		queryAndAssert(t, ctx, q.Endpoint("http"),
   821  			checkQuery,
   822  			func() time.Time { return now.Add(10 * 24 * time.Hour) },
   823  			promclient.QueryOptions{
   824  				Deduplicate: true,
   825  			},
   826  			model.Vector{
   827  				{Value: 0, Metric: map[model.LabelName]model.LabelValue{"b": "2", "case": "block-about-to-be-downsampled", "z": "1"}},
   828  				{Value: 0, Metric: map[model.LabelName]model.LabelValue{"b": "5", "case": "block-about-to-be-downsampled", "z": "1"}},
   829  			},
   830  		)
   831  
   832  		// Find out whether querying still works after deleting the raw data. After this,
   833  		// pre-aggregated sum/count should be used.
   834  		testutil.Ok(t, block.Delete(ctx, log.NewNopLogger(), bkt, downsampledRawID))
   835  
   836  		testutil.Ok(t, str.Stop())
   837  		testutil.Ok(t, e2e.StartAndWaitReady(str))
   838  		testutil.Ok(t, runutil.Retry(time.Second, ctx.Done(), func() error {
   839  			return str.WaitSumMetrics(e2emon.Equals(float64(len(rawBlockIDs)+8-18+6-2+2-1)), "thanos_blocks_meta_synced")
   840  		}))
   841  		testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(
   842  			matchers.MustNewMatcher(matchers.MatchEqual, "store_type", "store"),
   843  		)))
   844  
   845  		queryAndAssert(t, ctx, q.Endpoint("http"),
   846  			checkQuery,
   847  			func() time.Time { return now.Add(10 * 24 * time.Hour) },
   848  			promclient.QueryOptions{
   849  				Deduplicate:         true,
   850  				MaxSourceResolution: "1h",
   851  			},
   852  			model.Vector{
   853  				{Value: 0, Metric: map[model.LabelName]model.LabelValue{"b": "2", "case": "block-about-to-be-downsampled", "z": "1"}},
   854  				{Value: 0, Metric: map[model.LabelName]model.LabelValue{"b": "5", "case": "block-about-to-be-downsampled", "z": "1"}},
   855  			},
   856  		)
   857  	}
   858  }
   859  
   860  func ensureGETStatusCode(t testing.TB, code int, url string) {
   861  	t.Helper()
   862  
   863  	r, err := http.Get(url)
   864  	testutil.Ok(t, err)
   865  	testutil.Equals(t, code, r.StatusCode)
   866  }