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 }