github.com/thanos-io/thanos@v0.32.5/pkg/store/bucket_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package store 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/binary" 10 "fmt" 11 "io" 12 "math" 13 "math/rand" 14 "os" 15 "path" 16 "path/filepath" 17 "regexp" 18 "sort" 19 "strconv" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/cespare/xxhash" 26 "github.com/go-kit/log" 27 "github.com/gogo/protobuf/proto" 28 "github.com/gogo/protobuf/types" 29 "github.com/leanovate/gopter" 30 "github.com/leanovate/gopter/gen" 31 "github.com/leanovate/gopter/prop" 32 "github.com/oklog/ulid" 33 "github.com/prometheus/client_golang/prometheus" 34 promtest "github.com/prometheus/client_golang/prometheus/testutil" 35 "github.com/prometheus/prometheus/model/labels" 36 "github.com/prometheus/prometheus/model/relabel" 37 "github.com/prometheus/prometheus/model/timestamp" 38 "github.com/prometheus/prometheus/storage" 39 "github.com/prometheus/prometheus/tsdb" 40 "github.com/prometheus/prometheus/tsdb/chunkenc" 41 "github.com/prometheus/prometheus/tsdb/encoding" 42 "github.com/prometheus/prometheus/tsdb/index" 43 "go.uber.org/atomic" 44 "golang.org/x/exp/slices" 45 46 "github.com/thanos-io/objstore" 47 "github.com/thanos-io/objstore/providers/filesystem" 48 49 "github.com/efficientgo/core/testutil" 50 51 "github.com/thanos-io/thanos/pkg/block" 52 "github.com/thanos-io/thanos/pkg/block/indexheader" 53 "github.com/thanos-io/thanos/pkg/block/metadata" 54 "github.com/thanos-io/thanos/pkg/compact" 55 "github.com/thanos-io/thanos/pkg/compact/downsample" 56 "github.com/thanos-io/thanos/pkg/gate" 57 "github.com/thanos-io/thanos/pkg/pool" 58 storecache "github.com/thanos-io/thanos/pkg/store/cache" 59 "github.com/thanos-io/thanos/pkg/store/hintspb" 60 "github.com/thanos-io/thanos/pkg/store/labelpb" 61 "github.com/thanos-io/thanos/pkg/store/storepb" 62 storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil" 63 "github.com/thanos-io/thanos/pkg/testutil/custom" 64 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 65 ) 66 67 var emptyRelabelConfig = make([]*relabel.Config, 0) 68 69 func TestBucketBlock_Property(t *testing.T) { 70 parameters := gopter.DefaultTestParameters() 71 parameters.Rng.Seed(2000) 72 parameters.MinSuccessfulTests = 20000 73 properties := gopter.NewProperties(parameters) 74 75 set := newBucketBlockSet(labels.Labels{}) 76 77 type resBlock struct { 78 mint, maxt int64 79 window int64 80 } 81 // This input resembles a typical production-level block layout 82 // in remote object storage. 83 input := []resBlock{ 84 {window: downsample.ResLevel0, mint: 0, maxt: 100}, 85 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 86 // Compaction level 2 begins but not downsampling (8 hour block length). 87 {window: downsample.ResLevel0, mint: 200, maxt: 600}, 88 {window: downsample.ResLevel0, mint: 600, maxt: 1000}, 89 // Compaction level 3 begins, Some of it is downsampled but still retained (48 hour block length). 90 {window: downsample.ResLevel0, mint: 1000, maxt: 1750}, 91 {window: downsample.ResLevel1, mint: 1000, maxt: 1750}, 92 // Compaction level 4 begins, different downsampling levels cover the same (336 hour block length). 93 {window: downsample.ResLevel0, mint: 1750, maxt: 7000}, 94 {window: downsample.ResLevel1, mint: 1750, maxt: 7000}, 95 {window: downsample.ResLevel2, mint: 1750, maxt: 7000}, 96 // Compaction level 4 already happened, raw samples have been deleted. 97 {window: downsample.ResLevel0, mint: 7000, maxt: 14000}, 98 {window: downsample.ResLevel1, mint: 7000, maxt: 14000}, 99 // Compaction level 4 already happened, raw and downsample res level 1 samples have been deleted. 100 {window: downsample.ResLevel2, mint: 14000, maxt: 21000}, 101 } 102 103 for _, in := range input { 104 var m metadata.Meta 105 m.Thanos.Downsample.Resolution = in.window 106 m.MinTime = in.mint 107 m.MaxTime = in.maxt 108 109 testutil.Ok(t, set.add(&bucketBlock{meta: &m})) 110 } 111 112 properties.Property("getFor always gets at least some data in range", prop.ForAllNoShrink( 113 func(low, high, maxResolution int64) bool { 114 // Bogus case. 115 if low >= high { 116 return true 117 } 118 119 res := set.getFor(low, high, maxResolution, nil) 120 121 // The data that we get must all encompass our requested range. 122 if len(res) == 1 && (res[0].meta.Thanos.Downsample.Resolution > maxResolution || 123 res[0].meta.MinTime > low) { 124 return false 125 } else if len(res) > 1 { 126 mint := int64(21001) 127 for i := 0; i < len(res)-1; i++ { 128 if res[i].meta.Thanos.Downsample.Resolution > maxResolution { 129 return false 130 } 131 if res[i+1].meta.MinTime != res[i].meta.MaxTime { 132 return false 133 } 134 if res[i].meta.MinTime < mint { 135 mint = res[i].meta.MinTime 136 } 137 } 138 if res[len(res)-1].meta.MinTime < mint { 139 mint = res[len(res)-1].meta.MinTime 140 } 141 if low < mint { 142 return false 143 } 144 145 } 146 return true 147 }, gen.Int64Range(0, 21000), gen.Int64Range(0, 21000), gen.Int64Range(0, 60*60*1000)), 148 ) 149 150 properties.Property("getFor always gets all data in range", prop.ForAllNoShrink( 151 func(low, high int64) bool { 152 // Bogus case. 153 if low >= high { 154 return true 155 } 156 157 maxResolution := downsample.ResLevel2 158 res := set.getFor(low, high, maxResolution, nil) 159 160 // The data that we get must all encompass our requested range. 161 if len(res) == 1 && (res[0].meta.Thanos.Downsample.Resolution > maxResolution || 162 res[0].meta.MinTime > low || res[0].meta.MaxTime < high) { 163 return false 164 } else if len(res) > 1 { 165 mint := int64(21001) 166 maxt := int64(0) 167 for i := 0; i < len(res)-1; i++ { 168 if res[i+1].meta.MinTime != res[i].meta.MaxTime { 169 return false 170 } 171 if res[i].meta.MinTime < mint { 172 mint = res[i].meta.MinTime 173 } 174 if res[i].meta.MaxTime > maxt { 175 maxt = res[i].meta.MaxTime 176 } 177 } 178 if res[len(res)-1].meta.MinTime < mint { 179 mint = res[len(res)-1].meta.MinTime 180 } 181 if res[len(res)-1].meta.MaxTime > maxt { 182 maxt = res[len(res)-1].meta.MaxTime 183 } 184 if low < mint { 185 return false 186 } 187 if high > maxt { 188 return false 189 } 190 191 } 192 return true 193 }, gen.Int64Range(0, 21000), gen.Int64Range(0, 21000)), 194 ) 195 196 properties.TestingRun(t) 197 } 198 199 func TestBucketFilterExtLabelsMatchers(t *testing.T) { 200 defer custom.TolerantVerifyLeak(t) 201 202 dir := t.TempDir() 203 bkt, err := filesystem.NewBucket(dir) 204 testutil.Ok(t, err) 205 defer func() { testutil.Ok(t, bkt.Close()) }() 206 207 blockID := ulid.MustNew(1, nil) 208 meta := &metadata.Meta{ 209 BlockMeta: tsdb.BlockMeta{ULID: blockID}, 210 Thanos: metadata.Thanos{ 211 Labels: map[string]string{ 212 "a": "b", 213 "c": "d", 214 }, 215 }, 216 } 217 b, _ := newBucketBlock(context.Background(), log.NewNopLogger(), newBucketStoreMetrics(nil), meta, bkt, path.Join(dir, blockID.String()), nil, nil, nil, nil, nil, nil) 218 ms := []*labels.Matcher{ 219 {Type: labels.MatchNotEqual, Name: "a", Value: "b"}, 220 } 221 res, _ := b.FilterExtLabelsMatchers(ms) 222 testutil.Equals(t, len(res), 0) 223 224 ms = []*labels.Matcher{ 225 {Type: labels.MatchNotEqual, Name: "a", Value: "a"}, 226 } 227 _, ok := b.FilterExtLabelsMatchers(ms) 228 testutil.Equals(t, ok, false) 229 230 ms = []*labels.Matcher{ 231 {Type: labels.MatchNotEqual, Name: "a", Value: "a"}, 232 {Type: labels.MatchNotEqual, Name: "c", Value: "d"}, 233 } 234 res, _ = b.FilterExtLabelsMatchers(ms) 235 testutil.Equals(t, len(res), 0) 236 237 ms = []*labels.Matcher{ 238 {Type: labels.MatchNotEqual, Name: "a2", Value: "a"}, 239 } 240 res, _ = b.FilterExtLabelsMatchers(ms) 241 testutil.Equals(t, len(res), 1) 242 testutil.Equals(t, res, ms) 243 } 244 245 func TestBucketBlock_matchLabels(t *testing.T) { 246 defer custom.TolerantVerifyLeak(t) 247 248 dir := t.TempDir() 249 250 bkt, err := filesystem.NewBucket(dir) 251 testutil.Ok(t, err) 252 defer func() { testutil.Ok(t, bkt.Close()) }() 253 254 blockID := ulid.MustNew(1, nil) 255 meta := &metadata.Meta{ 256 BlockMeta: tsdb.BlockMeta{ULID: blockID}, 257 Thanos: metadata.Thanos{ 258 Labels: map[string]string{ 259 "a": "b", 260 "c": "d", 261 }, 262 }, 263 } 264 265 b, err := newBucketBlock(context.Background(), log.NewNopLogger(), newBucketStoreMetrics(nil), meta, bkt, path.Join(dir, blockID.String()), nil, nil, nil, nil, nil, nil) 266 testutil.Ok(t, err) 267 268 cases := []struct { 269 in []*labels.Matcher 270 match bool 271 }{ 272 { 273 in: []*labels.Matcher{}, 274 match: true, 275 }, 276 { 277 in: []*labels.Matcher{ 278 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 279 {Type: labels.MatchEqual, Name: "c", Value: "d"}, 280 }, 281 match: true, 282 }, 283 { 284 in: []*labels.Matcher{ 285 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 286 {Type: labels.MatchEqual, Name: "c", Value: "b"}, 287 }, 288 match: false, 289 }, 290 { 291 in: []*labels.Matcher{ 292 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 293 {Type: labels.MatchEqual, Name: "e", Value: "f"}, 294 }, 295 match: false, 296 }, 297 { 298 in: []*labels.Matcher{ 299 {Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: blockID.String()}, 300 }, 301 match: true, 302 }, 303 { 304 in: []*labels.Matcher{ 305 {Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: "xxx"}, 306 }, 307 match: false, 308 }, 309 { 310 in: []*labels.Matcher{ 311 {Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: blockID.String()}, 312 {Type: labels.MatchEqual, Name: "c", Value: "b"}, 313 }, 314 match: false, 315 }, 316 { 317 in: []*labels.Matcher{ 318 {Type: labels.MatchNotEqual, Name: "", Value: "x"}, 319 }, 320 match: true, 321 }, 322 { 323 in: []*labels.Matcher{ 324 {Type: labels.MatchNotEqual, Name: "", Value: "d"}, 325 }, 326 match: true, 327 }, 328 } 329 for _, c := range cases { 330 ok := b.matchRelabelLabels(c.in) 331 testutil.Equals(t, c.match, ok) 332 } 333 334 // Ensure block's labels in the meta have not been manipulated. 335 testutil.Equals(t, map[string]string{ 336 "a": "b", 337 "c": "d", 338 }, meta.Thanos.Labels) 339 } 340 341 func TestBucketBlockSet_addGet(t *testing.T) { 342 defer custom.TolerantVerifyLeak(t) 343 344 set := newBucketBlockSet(labels.Labels{}) 345 346 type resBlock struct { 347 mint, maxt int64 348 window int64 349 } 350 // Input is expected to be sorted. It is sorted in addBlock. 351 input := []resBlock{ 352 // Blocks from 0 to 100 with raw resolution. 353 {window: downsample.ResLevel0, mint: 0, maxt: 100}, 354 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 355 {window: downsample.ResLevel0, mint: 100, maxt: 200}, // Same overlap. 356 {window: downsample.ResLevel0, mint: 200, maxt: 299}, // Short overlap. 357 {window: downsample.ResLevel0, mint: 200, maxt: 300}, 358 {window: downsample.ResLevel0, mint: 300, maxt: 400}, 359 {window: downsample.ResLevel0, mint: 300, maxt: 600}, // Long overlap. 360 {window: downsample.ResLevel0, mint: 400, maxt: 500}, 361 // Lower resolution data not covering last block. 362 {window: downsample.ResLevel1, mint: 0, maxt: 100}, 363 {window: downsample.ResLevel1, mint: 100, maxt: 200}, 364 {window: downsample.ResLevel1, mint: 200, maxt: 300}, 365 {window: downsample.ResLevel1, mint: 300, maxt: 400}, 366 // Lower resolution data only covering middle blocks. 367 {window: downsample.ResLevel2, mint: 100, maxt: 200}, 368 {window: downsample.ResLevel2, mint: 200, maxt: 300}, 369 } 370 371 for _, in := range input { 372 var m metadata.Meta 373 m.Thanos.Downsample.Resolution = in.window 374 m.MinTime = in.mint 375 m.MaxTime = in.maxt 376 377 testutil.Ok(t, set.add(&bucketBlock{meta: &m})) 378 } 379 380 for _, c := range []struct { 381 mint, maxt int64 382 maxResolution int64 383 res []resBlock 384 }{ 385 { 386 mint: -100, 387 maxt: 1000, 388 maxResolution: 0, 389 res: []resBlock{ 390 {window: downsample.ResLevel0, mint: 0, maxt: 100}, 391 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 392 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 393 {window: downsample.ResLevel0, mint: 200, maxt: 299}, 394 {window: downsample.ResLevel0, mint: 200, maxt: 300}, 395 {window: downsample.ResLevel0, mint: 300, maxt: 400}, 396 {window: downsample.ResLevel0, mint: 300, maxt: 600}, 397 {window: downsample.ResLevel0, mint: 400, maxt: 500}, 398 }, 399 }, { 400 mint: 100, 401 maxt: 400, 402 maxResolution: downsample.ResLevel1 - 1, 403 res: []resBlock{ 404 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 405 {window: downsample.ResLevel0, mint: 100, maxt: 200}, 406 {window: downsample.ResLevel0, mint: 200, maxt: 299}, 407 {window: downsample.ResLevel0, mint: 200, maxt: 300}, 408 {window: downsample.ResLevel0, mint: 300, maxt: 400}, 409 {window: downsample.ResLevel0, mint: 300, maxt: 600}, 410 // Block intervals are half-open: [b.MinTime, b.MaxTime), so 400-500 contains single sample. 411 {window: downsample.ResLevel0, mint: 400, maxt: 500}, 412 }, 413 }, { 414 mint: 100, 415 maxt: 500, 416 maxResolution: downsample.ResLevel1, 417 res: []resBlock{ 418 {window: downsample.ResLevel1, mint: 100, maxt: 200}, 419 {window: downsample.ResLevel1, mint: 200, maxt: 300}, 420 {window: downsample.ResLevel1, mint: 300, maxt: 400}, 421 {window: downsample.ResLevel0, mint: 300, maxt: 600}, 422 {window: downsample.ResLevel0, mint: 400, maxt: 500}, 423 }, 424 }, { 425 mint: 0, 426 maxt: 500, 427 maxResolution: downsample.ResLevel2, 428 res: []resBlock{ 429 {window: downsample.ResLevel1, mint: 0, maxt: 100}, 430 {window: downsample.ResLevel2, mint: 100, maxt: 200}, 431 {window: downsample.ResLevel2, mint: 200, maxt: 300}, 432 {window: downsample.ResLevel1, mint: 300, maxt: 400}, 433 {window: downsample.ResLevel0, mint: 300, maxt: 600}, 434 {window: downsample.ResLevel0, mint: 400, maxt: 500}, 435 }, 436 }, 437 } { 438 t.Run("", func(t *testing.T) { 439 var exp []*bucketBlock 440 for _, b := range c.res { 441 var m metadata.Meta 442 m.Thanos.Downsample.Resolution = b.window 443 m.MinTime = b.mint 444 m.MaxTime = b.maxt 445 exp = append(exp, &bucketBlock{meta: &m}) 446 } 447 testutil.Equals(t, exp, set.getFor(c.mint, c.maxt, c.maxResolution, nil)) 448 }) 449 } 450 } 451 452 func TestBucketBlockSet_remove(t *testing.T) { 453 defer custom.TolerantVerifyLeak(t) 454 455 set := newBucketBlockSet(labels.Labels{}) 456 457 type resBlock struct { 458 id ulid.ULID 459 mint, maxt int64 460 } 461 input := []resBlock{ 462 {id: ulid.MustNew(1, nil), mint: 0, maxt: 100}, 463 {id: ulid.MustNew(2, nil), mint: 100, maxt: 200}, 464 {id: ulid.MustNew(3, nil), mint: 200, maxt: 300}, 465 } 466 467 for _, in := range input { 468 var m metadata.Meta 469 m.ULID = in.id 470 m.MinTime = in.mint 471 m.MaxTime = in.maxt 472 testutil.Ok(t, set.add(&bucketBlock{meta: &m})) 473 } 474 set.remove(input[1].id) 475 res := set.getFor(0, 300, 0, nil) 476 477 testutil.Equals(t, 2, len(res)) 478 testutil.Equals(t, input[0].id, res[0].meta.ULID) 479 testutil.Equals(t, input[2].id, res[1].meta.ULID) 480 } 481 482 func TestBucketBlockSet_labelMatchers(t *testing.T) { 483 defer custom.TolerantVerifyLeak(t) 484 485 set := newBucketBlockSet(labels.FromStrings("a", "b", "c", "d")) 486 487 cases := []struct { 488 in []*labels.Matcher 489 res []*labels.Matcher 490 match bool 491 }{ 492 { 493 in: []*labels.Matcher{}, 494 res: []*labels.Matcher{}, 495 match: true, 496 }, 497 { 498 in: []*labels.Matcher{ 499 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 500 {Type: labels.MatchEqual, Name: "c", Value: "d"}, 501 }, 502 res: []*labels.Matcher{}, 503 match: true, 504 }, 505 { 506 in: []*labels.Matcher{ 507 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 508 {Type: labels.MatchEqual, Name: "c", Value: "b"}, 509 }, 510 match: false, 511 }, 512 { 513 in: []*labels.Matcher{ 514 {Type: labels.MatchEqual, Name: "a", Value: "b"}, 515 {Type: labels.MatchEqual, Name: "e", Value: "f"}, 516 }, 517 res: []*labels.Matcher{ 518 {Type: labels.MatchEqual, Name: "e", Value: "f"}, 519 }, 520 match: true, 521 }, 522 // Those are matchers mentioned here: https://github.com/prometheus/prometheus/pull/3578#issuecomment-351653555 523 // We want to provide explicit tests that says when Thanos supports its and when not. We don't support it here in 524 // external labelset level. 525 { 526 in: []*labels.Matcher{ 527 {Type: labels.MatchNotEqual, Name: "", Value: "x"}, 528 }, 529 res: []*labels.Matcher{ 530 {Type: labels.MatchNotEqual, Name: "", Value: "x"}, 531 }, 532 match: true, 533 }, 534 { 535 in: []*labels.Matcher{ 536 {Type: labels.MatchNotEqual, Name: "", Value: "d"}, 537 }, 538 res: []*labels.Matcher{ 539 {Type: labels.MatchNotEqual, Name: "", Value: "d"}, 540 }, 541 match: true, 542 }, 543 } 544 for _, c := range cases { 545 res, ok := set.labelMatchers(c.in...) 546 testutil.Equals(t, c.match, ok) 547 testutil.Equals(t, c.res, res) 548 } 549 } 550 551 func TestGapBasedPartitioner_Partition(t *testing.T) { 552 defer custom.TolerantVerifyLeak(t) 553 554 const maxGapSize = 1024 * 512 555 556 for _, c := range []struct { 557 input [][2]int 558 expected []Part 559 }{ 560 { 561 input: [][2]int{{1, 10}}, 562 expected: []Part{{Start: 1, End: 10, ElemRng: [2]int{0, 1}}}, 563 }, 564 { 565 input: [][2]int{{1, 2}, {3, 5}, {7, 10}}, 566 expected: []Part{{Start: 1, End: 10, ElemRng: [2]int{0, 3}}}, 567 }, 568 { 569 input: [][2]int{ 570 {1, 2}, 571 {3, 5}, 572 {20, 30}, 573 {maxGapSize + 31, maxGapSize + 32}, 574 }, 575 expected: []Part{ 576 {Start: 1, End: 30, ElemRng: [2]int{0, 3}}, 577 {Start: maxGapSize + 31, End: maxGapSize + 32, ElemRng: [2]int{3, 4}}, 578 }, 579 }, 580 // Overlapping ranges. 581 { 582 input: [][2]int{ 583 {1, 30}, 584 {1, 4}, 585 {3, 28}, 586 {maxGapSize + 31, maxGapSize + 32}, 587 {maxGapSize + 31, maxGapSize + 40}, 588 }, 589 expected: []Part{ 590 {Start: 1, End: 30, ElemRng: [2]int{0, 3}}, 591 {Start: maxGapSize + 31, End: maxGapSize + 40, ElemRng: [2]int{3, 5}}, 592 }, 593 }, 594 { 595 input: [][2]int{ 596 // Mimick AllPostingsKey, where range specified whole range. 597 {1, 15}, 598 {1, maxGapSize + 100}, 599 {maxGapSize + 31, maxGapSize + 40}, 600 }, 601 expected: []Part{{Start: 1, End: maxGapSize + 100, ElemRng: [2]int{0, 3}}}, 602 }, 603 } { 604 res := gapBasedPartitioner{maxGapSize: maxGapSize}.Partition(len(c.input), func(i int) (uint64, uint64) { 605 return uint64(c.input[i][0]), uint64(c.input[i][1]) 606 }) 607 testutil.Equals(t, c.expected, res) 608 } 609 } 610 611 func TestBucketStoreConfig_validate(t *testing.T) { 612 tests := map[string]struct { 613 config *BucketStore 614 expected error 615 }{ 616 "should pass on valid config": { 617 config: &BucketStore{ 618 blockSyncConcurrency: 1, 619 }, 620 expected: nil, 621 }, 622 "should fail on blockSyncConcurrency < 1": { 623 config: &BucketStore{ 624 blockSyncConcurrency: 0, 625 }, 626 expected: errBlockSyncConcurrencyNotValid, 627 }, 628 } 629 630 for testName, testData := range tests { 631 t.Run(testName, func(t *testing.T) { 632 testutil.Equals(t, testData.expected, testData.config.validate()) 633 }) 634 } 635 } 636 637 func TestBucketStore_Info(t *testing.T) { 638 defer custom.TolerantVerifyLeak(t) 639 640 ctx, cancel := context.WithCancel(context.Background()) 641 defer cancel() 642 643 dir := t.TempDir() 644 645 chunkPool, err := NewDefaultChunkBytesPool(2e5) 646 testutil.Ok(t, err) 647 648 bucketStore, err := NewBucketStore( 649 nil, 650 nil, 651 dir, 652 NewChunksLimiterFactory(0), 653 NewSeriesLimiterFactory(0), 654 NewBytesLimiterFactory(0), 655 NewGapBasedPartitioner(PartitionerMaxGapSize), 656 20, 657 true, 658 DefaultPostingOffsetInMemorySampling, 659 false, 660 false, 661 0, 662 WithChunkPool(chunkPool), 663 WithFilterConfig(allowAllFilterConf), 664 ) 665 testutil.Ok(t, err) 666 defer func() { testutil.Ok(t, bucketStore.Close()) }() 667 668 resp, err := bucketStore.Info(ctx, &storepb.InfoRequest{}) 669 testutil.Ok(t, err) 670 671 testutil.Equals(t, storepb.StoreType_STORE, resp.StoreType) 672 testutil.Equals(t, int64(math.MaxInt64), resp.MinTime) 673 testutil.Equals(t, int64(math.MinInt64), resp.MaxTime) 674 testutil.Equals(t, []labelpb.ZLabelSet(nil), resp.LabelSets) 675 testutil.Equals(t, []labelpb.ZLabel(nil), resp.Labels) 676 } 677 678 type recorder struct { 679 mtx sync.Mutex 680 objstore.Bucket 681 682 getRangeTouched []string 683 getTouched []string 684 } 685 686 func (r *recorder) Get(ctx context.Context, name string) (io.ReadCloser, error) { 687 r.mtx.Lock() 688 defer r.mtx.Unlock() 689 690 r.getTouched = append(r.getTouched, name) 691 return r.Bucket.Get(ctx, name) 692 } 693 694 func (r *recorder) GetRange(ctx context.Context, name string, off, length int64) (io.ReadCloser, error) { 695 r.mtx.Lock() 696 defer r.mtx.Unlock() 697 698 r.getRangeTouched = append(r.getRangeTouched, name) 699 return r.Bucket.GetRange(ctx, name, off, length) 700 } 701 702 func TestBucketStore_Sharding(t *testing.T) { 703 ctx := context.Background() 704 logger := log.NewNopLogger() 705 706 dir := t.TempDir() 707 708 bkt := objstore.NewInMemBucket() 709 series := []labels.Labels{labels.FromStrings("a", "1", "b", "1")} 710 711 id1, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc) 712 testutil.Ok(t, err) 713 testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id1.String()), metadata.NoneFunc)) 714 715 id2, err := e2eutil.CreateBlock(ctx, dir, series, 10, 1000, 2000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc) 716 testutil.Ok(t, err) 717 testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id2.String()), metadata.NoneFunc)) 718 719 id3, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "b"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc) 720 testutil.Ok(t, err) 721 testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id3.String()), metadata.NoneFunc)) 722 723 id4, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r2"}}, 0, metadata.NoneFunc) 724 testutil.Ok(t, err) 725 testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id4.String()), metadata.NoneFunc)) 726 727 if ok := t.Run("new_runs", func(t *testing.T) { 728 testSharding(t, "", bkt, id1, id2, id3, id4) 729 }); !ok { 730 return 731 } 732 733 dir2 := t.TempDir() 734 735 t.Run("reuse_disk", func(t *testing.T) { 736 testSharding(t, dir2, bkt, id1, id2, id3, id4) 737 }) 738 } 739 740 func testSharding(t *testing.T, reuseDisk string, bkt objstore.Bucket, all ...ulid.ULID) { 741 var cached []ulid.ULID 742 743 logger := log.NewLogfmtLogger(os.Stderr) 744 for _, sc := range []struct { 745 name string 746 relabel string 747 expectedIDs []ulid.ULID 748 expectedAdvLabels []labelpb.ZLabelSet 749 }{ 750 { 751 name: "no sharding", 752 expectedIDs: all, 753 expectedAdvLabels: []labelpb.ZLabelSet{ 754 { 755 Labels: []labelpb.ZLabel{ 756 {Name: "cluster", Value: "a"}, 757 {Name: "region", Value: "r1"}, 758 }, 759 }, 760 { 761 Labels: []labelpb.ZLabel{ 762 {Name: "cluster", Value: "a"}, 763 {Name: "region", Value: "r2"}, 764 }, 765 }, 766 { 767 Labels: []labelpb.ZLabel{ 768 {Name: "cluster", Value: "b"}, 769 {Name: "region", Value: "r1"}, 770 }, 771 }, 772 { 773 Labels: []labelpb.ZLabel{ 774 {Name: CompatibilityTypeLabelName, Value: "store"}, 775 }, 776 }, 777 }, 778 }, 779 { 780 name: "drop cluster=a sources", 781 relabel: ` 782 - action: drop 783 regex: "a" 784 source_labels: 785 - cluster 786 `, 787 expectedIDs: []ulid.ULID{all[2]}, 788 expectedAdvLabels: []labelpb.ZLabelSet{ 789 { 790 Labels: []labelpb.ZLabel{ 791 {Name: "cluster", Value: "b"}, 792 {Name: "region", Value: "r1"}, 793 }, 794 }, 795 { 796 Labels: []labelpb.ZLabel{ 797 {Name: CompatibilityTypeLabelName, Value: "store"}, 798 }, 799 }, 800 }, 801 }, 802 { 803 name: "keep only cluster=a sources", 804 relabel: ` 805 - action: keep 806 regex: "a" 807 source_labels: 808 - cluster 809 `, 810 expectedIDs: []ulid.ULID{all[0], all[1], all[3]}, 811 expectedAdvLabels: []labelpb.ZLabelSet{ 812 { 813 Labels: []labelpb.ZLabel{ 814 {Name: "cluster", Value: "a"}, 815 {Name: "region", Value: "r1"}, 816 }, 817 }, 818 { 819 Labels: []labelpb.ZLabel{ 820 {Name: "cluster", Value: "a"}, 821 {Name: "region", Value: "r2"}, 822 }, 823 }, 824 { 825 Labels: []labelpb.ZLabel{ 826 {Name: CompatibilityTypeLabelName, Value: "store"}, 827 }, 828 }, 829 }, 830 }, 831 { 832 name: "keep only cluster=a without .*2 region sources", 833 relabel: ` 834 - action: keep 835 regex: "a" 836 source_labels: 837 - cluster 838 - action: drop 839 regex: ".*2" 840 source_labels: 841 - region 842 `, 843 expectedIDs: []ulid.ULID{all[0], all[1]}, 844 expectedAdvLabels: []labelpb.ZLabelSet{ 845 { 846 Labels: []labelpb.ZLabel{ 847 {Name: "cluster", Value: "a"}, 848 {Name: "region", Value: "r1"}, 849 }, 850 }, 851 { 852 Labels: []labelpb.ZLabel{ 853 {Name: CompatibilityTypeLabelName, Value: "store"}, 854 }, 855 }, 856 }, 857 }, 858 { 859 name: "drop all", 860 relabel: ` 861 - action: drop 862 regex: "a" 863 source_labels: 864 - cluster 865 - action: drop 866 regex: "r1" 867 source_labels: 868 - region 869 `, 870 expectedIDs: []ulid.ULID{}, 871 expectedAdvLabels: []labelpb.ZLabelSet{}, 872 }, 873 } { 874 t.Run(sc.name, func(t *testing.T) { 875 dir := reuseDisk 876 877 if dir == "" { 878 dir = t.TempDir() 879 } 880 relabelConf, err := block.ParseRelabelConfig([]byte(sc.relabel), block.SelectorSupportedRelabelActions) 881 testutil.Ok(t, err) 882 883 rec := &recorder{Bucket: bkt} 884 metaFetcher, err := block.NewMetaFetcher(logger, 20, objstore.WithNoopInstr(bkt), dir, nil, []block.MetadataFilter{ 885 block.NewTimePartitionMetaFilter(allowAllFilterConf.MinTime, allowAllFilterConf.MaxTime), 886 block.NewLabelShardedMetaFilter(relabelConf), 887 }) 888 testutil.Ok(t, err) 889 890 bucketStore, err := NewBucketStore( 891 objstore.WithNoopInstr(rec), 892 metaFetcher, 893 dir, 894 NewChunksLimiterFactory(0), 895 NewSeriesLimiterFactory(0), 896 NewBytesLimiterFactory(0), 897 NewGapBasedPartitioner(PartitionerMaxGapSize), 898 20, 899 true, 900 DefaultPostingOffsetInMemorySampling, 901 false, 902 false, 903 0, 904 WithLogger(logger), 905 WithFilterConfig(allowAllFilterConf), 906 ) 907 testutil.Ok(t, err) 908 defer func() { testutil.Ok(t, bucketStore.Close()) }() 909 910 testutil.Ok(t, bucketStore.InitialSync(context.Background())) 911 912 // Check "stored" blocks. 913 ids := make([]ulid.ULID, 0, len(bucketStore.blocks)) 914 for id := range bucketStore.blocks { 915 ids = append(ids, id) 916 } 917 sort.Slice(ids, func(i, j int) bool { 918 return ids[i].Compare(ids[j]) < 0 919 }) 920 testutil.Equals(t, sc.expectedIDs, ids) 921 922 // Check Info endpoint. 923 resp, err := bucketStore.Info(context.Background(), &storepb.InfoRequest{}) 924 testutil.Ok(t, err) 925 926 testutil.Equals(t, storepb.StoreType_STORE, resp.StoreType) 927 testutil.Equals(t, []labelpb.ZLabel(nil), resp.Labels) 928 testutil.Equals(t, sc.expectedAdvLabels, resp.LabelSets) 929 930 // Make sure we don't download files we did not expect to. 931 // Regression test: https://github.com/thanos-io/thanos/issues/1664 932 933 // Sort records. We load blocks concurrently so operations might be not ordered. 934 sort.Strings(rec.getRangeTouched) 935 936 // With binary header nothing should be downloaded fully. 937 testutil.Equals(t, []string(nil), rec.getTouched) 938 if reuseDisk != "" { 939 testutil.Equals(t, expectedTouchedBlockOps(all, sc.expectedIDs, cached), rec.getRangeTouched) 940 cached = sc.expectedIDs 941 return 942 } 943 944 testutil.Equals(t, expectedTouchedBlockOps(all, sc.expectedIDs, nil), rec.getRangeTouched) 945 }) 946 } 947 } 948 949 func expectedTouchedBlockOps(all, expected, cached []ulid.ULID) []string { 950 var ops []string 951 for _, id := range all { 952 blockCached := false 953 for _, fid := range cached { 954 if id.Compare(fid) == 0 { 955 blockCached = true 956 break 957 } 958 } 959 if blockCached { 960 continue 961 } 962 963 found := false 964 for _, fid := range expected { 965 if id.Compare(fid) == 0 { 966 found = true 967 break 968 } 969 } 970 971 if found { 972 ops = append(ops, 973 // To create binary header we touch part of index few times. 974 path.Join(id.String(), block.IndexFilename), // Version. 975 path.Join(id.String(), block.IndexFilename), // TOC. 976 path.Join(id.String(), block.IndexFilename), // Symbols. 977 path.Join(id.String(), block.IndexFilename), // PostingOffsets. 978 ) 979 } 980 } 981 sort.Strings(ops) 982 return ops 983 } 984 985 // Regression tests against: https://github.com/thanos-io/thanos/issues/1983. 986 func TestReadIndexCache_LoadSeries(t *testing.T) { 987 bkt := objstore.NewInMemBucket() 988 989 s := newBucketStoreMetrics(nil) 990 b := &bucketBlock{ 991 meta: &metadata.Meta{ 992 BlockMeta: tsdb.BlockMeta{ 993 ULID: ulid.MustNew(1, nil), 994 }, 995 }, 996 bkt: bkt, 997 logger: log.NewNopLogger(), 998 metrics: s, 999 indexCache: noopCache{}, 1000 } 1001 1002 buf := encoding.Encbuf{} 1003 buf.PutByte(0) 1004 buf.PutByte(0) 1005 buf.PutUvarint(10) 1006 buf.PutString("aaaaaaaaaa") 1007 buf.PutUvarint(10) 1008 buf.PutString("bbbbbbbbbb") 1009 buf.PutUvarint(10) 1010 buf.PutString("cccccccccc") 1011 testutil.Ok(t, bkt.Upload(context.Background(), filepath.Join(b.meta.ULID.String(), block.IndexFilename), bytes.NewReader(buf.Get()))) 1012 1013 r := bucketIndexReader{ 1014 block: b, 1015 stats: &queryStats{}, 1016 loadedSeries: map[storage.SeriesRef][]byte{}, 1017 } 1018 1019 // Success with no refetches. 1020 testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 2, 100, NewBytesLimiterFactory(0)(nil))) 1021 testutil.Equals(t, map[storage.SeriesRef][]byte{ 1022 2: []byte("aaaaaaaaaa"), 1023 13: []byte("bbbbbbbbbb"), 1024 24: []byte("cccccccccc"), 1025 }, r.loadedSeries) 1026 testutil.Equals(t, float64(0), promtest.ToFloat64(s.seriesRefetches)) 1027 1028 // Success with 2 refetches. 1029 r.loadedSeries = map[storage.SeriesRef][]byte{} 1030 testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 2, 15, NewBytesLimiterFactory(0)(nil))) 1031 testutil.Equals(t, map[storage.SeriesRef][]byte{ 1032 2: []byte("aaaaaaaaaa"), 1033 13: []byte("bbbbbbbbbb"), 1034 24: []byte("cccccccccc"), 1035 }, r.loadedSeries) 1036 testutil.Equals(t, float64(2), promtest.ToFloat64(s.seriesRefetches)) 1037 1038 // Success with refetch on first element. 1039 r.loadedSeries = map[storage.SeriesRef][]byte{} 1040 testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2}, false, 2, 5, NewBytesLimiterFactory(0)(nil))) 1041 testutil.Equals(t, map[storage.SeriesRef][]byte{ 1042 2: []byte("aaaaaaaaaa"), 1043 }, r.loadedSeries) 1044 testutil.Equals(t, float64(3), promtest.ToFloat64(s.seriesRefetches)) 1045 1046 buf.Reset() 1047 buf.PutByte(0) 1048 buf.PutByte(0) 1049 buf.PutUvarint(10) 1050 buf.PutString("aaaaaaa") 1051 testutil.Ok(t, bkt.Upload(context.Background(), filepath.Join(b.meta.ULID.String(), block.IndexFilename), bytes.NewReader(buf.Get()))) 1052 1053 // Fail, but no recursion at least. 1054 testutil.NotOk(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 1, 15, NewBytesLimiterFactory(0)(nil))) 1055 } 1056 1057 func TestBucketIndexReader_ExpandedPostings(t *testing.T) { 1058 tb := testutil.NewTB(t) 1059 1060 tmpDir := t.TempDir() 1061 1062 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 1063 testutil.Ok(tb, err) 1064 defer func() { testutil.Ok(tb, bkt.Close()) }() 1065 1066 id := uploadTestBlock(tb, tmpDir, bkt, 500) 1067 1068 r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling) 1069 testutil.Ok(tb, err) 1070 1071 benchmarkExpandedPostings(tb, bkt, id, r, 500) 1072 } 1073 1074 func BenchmarkBucketIndexReader_ExpandedPostings(b *testing.B) { 1075 tb := testutil.NewTB(b) 1076 1077 tmpDir := b.TempDir() 1078 1079 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 1080 testutil.Ok(tb, err) 1081 defer func() { testutil.Ok(tb, bkt.Close()) }() 1082 1083 id := uploadTestBlock(tb, tmpDir, bkt, 50e5) 1084 r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling) 1085 testutil.Ok(tb, err) 1086 1087 benchmarkExpandedPostings(tb, bkt, id, r, 50e5) 1088 } 1089 1090 func uploadTestBlock(t testing.TB, tmpDir string, bkt objstore.Bucket, series int) ulid.ULID { 1091 headOpts := tsdb.DefaultHeadOptions() 1092 headOpts.ChunkDirRoot = tmpDir 1093 headOpts.ChunkRange = 1000 1094 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 1095 testutil.Ok(t, err) 1096 defer func() { 1097 testutil.Ok(t, h.Close()) 1098 }() 1099 1100 logger := log.NewNopLogger() 1101 1102 appendTestData(t, h.Appender(context.Background()), series) 1103 1104 testutil.Ok(t, os.MkdirAll(filepath.Join(tmpDir, "tmp"), os.ModePerm)) 1105 id := createBlockFromHead(t, filepath.Join(tmpDir, "tmp"), h) 1106 1107 _, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(tmpDir, "tmp", id.String()), metadata.Thanos{ 1108 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 1109 Downsample: metadata.ThanosDownsample{Resolution: 0}, 1110 Source: metadata.TestSource, 1111 }, nil) 1112 testutil.Ok(t, err) 1113 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, "tmp", id.String()), metadata.NoneFunc)) 1114 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, "tmp", id.String()), metadata.NoneFunc)) 1115 1116 return id 1117 } 1118 1119 func appendTestData(t testing.TB, app storage.Appender, series int) { 1120 addSeries := func(l labels.Labels) { 1121 _, err := app.Append(0, l, 0, 0) 1122 testutil.Ok(t, err) 1123 } 1124 1125 series = series / 5 1126 uniq := 0 1127 for n := 0; n < 10; n++ { 1128 for i := 0; i < series/10; i++ { 1129 addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "foo", "uniq", fmt.Sprintf("%08d", uniq))) 1130 // Have some series that won't be matched, to properly test inverted matches. 1131 addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+1))) 1132 addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "0_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+2))) 1133 addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "1_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+3))) 1134 addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "2_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "foo", "uniq", fmt.Sprintf("%08d", uniq+4))) 1135 uniq += 5 1136 } 1137 } 1138 testutil.Ok(t, app.Commit()) 1139 } 1140 1141 func createBlockFromHead(t testing.TB, dir string, head *tsdb.Head) ulid.ULID { 1142 compactor, err := tsdb.NewLeveledCompactor(context.Background(), nil, log.NewNopLogger(), []int64{1000000}, nil, nil) 1143 testutil.Ok(t, err) 1144 testutil.Ok(t, os.MkdirAll(dir, 0777)) 1145 1146 // Add +1 millisecond to block maxt because block intervals are half-open: [b.MinTime, b.MaxTime). 1147 // Because of this block intervals are always +1 than the total samples it includes. 1148 ulid, err := compactor.Write(dir, head, head.MinTime(), head.MaxTime()+1, nil) 1149 testutil.Ok(t, err) 1150 return ulid 1151 } 1152 1153 // Very similar benchmark to ths: https://github.com/prometheus/prometheus/blob/1d1732bc25cc4b47f513cb98009a4eb91879f175/tsdb/querier_bench_test.go#L82, 1154 // but with postings results check when run as test. 1155 func benchmarkExpandedPostings( 1156 t testutil.TB, 1157 bkt objstore.BucketReader, 1158 id ulid.ULID, 1159 r indexheader.Reader, 1160 series int, 1161 ) { 1162 n1 := labels.MustNewMatcher(labels.MatchEqual, "n", "1"+storetestutil.LabelLongSuffix) 1163 jFoo := labels.MustNewMatcher(labels.MatchEqual, "j", "foo") 1164 jNotFoo := labels.MustNewMatcher(labels.MatchNotEqual, "j", "foo") 1165 iStar := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.*$") 1166 iPlus := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.+$") 1167 i1Plus := labels.MustNewMatcher(labels.MatchRegexp, "i", "^1.+$") 1168 iEmptyRe := labels.MustNewMatcher(labels.MatchRegexp, "i", "^$") 1169 iNotEmpty := labels.MustNewMatcher(labels.MatchNotEqual, "i", "") 1170 iNot2 := labels.MustNewMatcher(labels.MatchNotEqual, "i", "2"+storetestutil.LabelLongSuffix) 1171 iNot2Star := labels.MustNewMatcher(labels.MatchNotRegexp, "i", "^2.*$") 1172 iRegexSet := labels.MustNewMatcher(labels.MatchRegexp, "i", "0"+storetestutil.LabelLongSuffix+"|1"+storetestutil.LabelLongSuffix+"|2"+storetestutil.LabelLongSuffix) 1173 bigValueSetSize := series / 10 1174 if bigValueSetSize > 50000 { 1175 bigValueSetSize = 50000 1176 } 1177 bigValueSet := make([]string, 0, bigValueSetSize) 1178 for i := 0; i < series; i += series / bigValueSetSize { 1179 bigValueSet = append(bigValueSet, fmt.Sprintf("%08d", i)) 1180 } 1181 bigValueSetSize = len(bigValueSet) 1182 rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle(len(bigValueSet), func(i, j int) { 1183 bigValueSet[i], bigValueSet[j] = bigValueSet[j], bigValueSet[i] 1184 }) 1185 iRegexBigValueSet := labels.MustNewMatcher(labels.MatchRegexp, "uniq", strings.Join(bigValueSet, "|")) 1186 1187 series = series / 5 1188 cases := []struct { 1189 name string 1190 matchers []*labels.Matcher 1191 1192 expectedLen int 1193 }{ 1194 {`n="1"`, []*labels.Matcher{n1}, int(float64(series) * 0.2)}, 1195 {`n="1",j="foo"`, []*labels.Matcher{n1, jFoo}, int(float64(series) * 0.1)}, 1196 {`j="foo",n="1"`, []*labels.Matcher{jFoo, n1}, int(float64(series) * 0.1)}, 1197 {`n="1",j!="foo"`, []*labels.Matcher{n1, jNotFoo}, int(float64(series) * 0.1)}, 1198 {`i=~".*"`, []*labels.Matcher{iStar}, 5 * series}, 1199 {`i=~".+"`, []*labels.Matcher{iPlus}, 5 * series}, 1200 {`i=~""`, []*labels.Matcher{iEmptyRe}, 0}, 1201 {`i!=""`, []*labels.Matcher{iNotEmpty}, 5 * series}, 1202 {`n="1",i=~".*",j="foo"`, []*labels.Matcher{n1, iStar, jFoo}, int(float64(series) * 0.1)}, 1203 {`n="1",i=~".*",i!="2",j="foo"`, []*labels.Matcher{n1, iStar, iNot2, jFoo}, int(float64(series)/10 - 1)}, 1204 {`n="1",i!=""`, []*labels.Matcher{n1, iNotEmpty}, int(float64(series) * 0.2)}, 1205 {`n="1",i!="",j="foo"`, []*labels.Matcher{n1, iNotEmpty, jFoo}, int(float64(series) * 0.1)}, 1206 {`n="1",i=~".+",j="foo"`, []*labels.Matcher{n1, iPlus, jFoo}, int(float64(series) * 0.1)}, 1207 {`n="1",i=~"1.+",j="foo"`, []*labels.Matcher{n1, i1Plus, jFoo}, int(float64(series) * 0.011111)}, 1208 {`n="1",i=~".+",i!="2",j="foo"`, []*labels.Matcher{n1, iPlus, iNot2, jFoo}, int(float64(series)/10 - 1)}, 1209 {`n="1",i=~".+",i!~"2.*",j="foo"`, []*labels.Matcher{n1, iPlus, iNot2Star, jFoo}, int(1 + float64(series)*0.088888)}, 1210 {`n="1",i=~".+",i=~".+",i=~".+",i=~".+",i=~".+",j="foo"`, []*labels.Matcher{n1, iPlus, iPlus, iPlus, iPlus, iPlus, jFoo}, int(float64(series) * 0.1)}, 1211 {`i=~"0|1|2"`, []*labels.Matcher{iRegexSet}, 150}, // 50 series for "1", 50 for "2" and 50 for "3". 1212 {`uniq=~"9|random-shuffled-values|1"`, []*labels.Matcher{iRegexBigValueSet}, bigValueSetSize}, 1213 } 1214 1215 for _, c := range cases { 1216 t.Run(c.name, func(t testutil.TB) { 1217 b := &bucketBlock{ 1218 logger: log.NewNopLogger(), 1219 metrics: newBucketStoreMetrics(nil), 1220 indexHeaderReader: r, 1221 indexCache: noopCache{}, 1222 bkt: bkt, 1223 meta: &metadata.Meta{BlockMeta: tsdb.BlockMeta{ULID: id}}, 1224 partitioner: NewGapBasedPartitioner(PartitionerMaxGapSize), 1225 } 1226 1227 indexr := newBucketIndexReader(b) 1228 1229 t.ResetTimer() 1230 for i := 0; i < t.N(); i++ { 1231 p, err := indexr.ExpandedPostings(context.Background(), newSortedMatchers(c.matchers), NewBytesLimiterFactory(0)(nil)) 1232 testutil.Ok(t, err) 1233 testutil.Equals(t, c.expectedLen, len(p)) 1234 } 1235 }) 1236 } 1237 } 1238 1239 func TestExpandedPostingsEmptyPostings(t *testing.T) { 1240 tmpDir := t.TempDir() 1241 1242 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 1243 testutil.Ok(t, err) 1244 defer func() { testutil.Ok(t, bkt.Close()) }() 1245 1246 id := uploadTestBlock(t, tmpDir, bkt, 100) 1247 1248 r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling) 1249 testutil.Ok(t, err) 1250 b := &bucketBlock{ 1251 logger: log.NewNopLogger(), 1252 metrics: newBucketStoreMetrics(nil), 1253 indexHeaderReader: r, 1254 indexCache: noopCache{}, 1255 bkt: bkt, 1256 meta: &metadata.Meta{BlockMeta: tsdb.BlockMeta{ULID: id}}, 1257 partitioner: NewGapBasedPartitioner(PartitionerMaxGapSize), 1258 } 1259 1260 indexr := newBucketIndexReader(b) 1261 matcher1 := labels.MustNewMatcher(labels.MatchEqual, "j", "foo") 1262 // Match nothing. 1263 matcher2 := labels.MustNewMatcher(labels.MatchRegexp, "i", "500.*") 1264 ps, err := indexr.ExpandedPostings(context.Background(), newSortedMatchers([]*labels.Matcher{matcher1, matcher2}), NewBytesLimiterFactory(0)(nil)) 1265 testutil.Ok(t, err) 1266 testutil.Equals(t, len(ps), 0) 1267 // Make sure even if a matcher doesn't match any postings, we still cache empty expanded postings. 1268 testutil.Equals(t, 1, indexr.stats.cachedPostingsCompressions) 1269 } 1270 1271 func TestBucketSeries(t *testing.T) { 1272 tb := testutil.NewTB(t) 1273 storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) { 1274 benchBucketSeries(t, chunkenc.ValFloat, false, samplesPerSeries, series, 1) 1275 }) 1276 } 1277 1278 func TestBucketHistogramSeries(t *testing.T) { 1279 tb := testutil.NewTB(t) 1280 storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) { 1281 benchBucketSeries(t, chunkenc.ValHistogram, false, samplesPerSeries, series, 1) 1282 }) 1283 } 1284 1285 func TestBucketSkipChunksSeries(t *testing.T) { 1286 tb := testutil.NewTB(t) 1287 storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) { 1288 benchBucketSeries(t, chunkenc.ValFloat, true, samplesPerSeries, series, 1) 1289 }) 1290 } 1291 1292 func BenchmarkBucketSeries(b *testing.B) { 1293 tb := testutil.NewTB(b) 1294 // 10e6 samples = ~1736 days with 15s scrape 1295 storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) { 1296 benchBucketSeries(t, chunkenc.ValFloat, false, samplesPerSeries, series, 1/100e6, 1/10e4, 1) 1297 }) 1298 } 1299 1300 func BenchmarkBucketSkipChunksSeries(b *testing.B) { 1301 tb := testutil.NewTB(b) 1302 // 10e6 samples = ~1736 days with 15s scrape 1303 storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) { 1304 benchBucketSeries(t, chunkenc.ValFloat, true, samplesPerSeries, series, 1/100e6, 1/10e4, 1) 1305 }) 1306 } 1307 1308 func benchBucketSeries(t testutil.TB, sampleType chunkenc.ValueType, skipChunk bool, samplesPerSeries, totalSeries int, requestedRatios ...float64) { 1309 const numOfBlocks = 4 1310 1311 tmpDir := t.TempDir() 1312 1313 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 1314 testutil.Ok(t, err) 1315 defer func() { testutil.Ok(t, bkt.Close()) }() 1316 1317 var ( 1318 logger = log.NewNopLogger() 1319 series []*storepb.Series 1320 random = rand.New(rand.NewSource(120)) 1321 ) 1322 1323 extLset := labels.Labels{{Name: "ext1", Value: "1"}} 1324 thanosMeta := metadata.Thanos{ 1325 Labels: extLset.Map(), 1326 Downsample: metadata.ThanosDownsample{Resolution: 0}, 1327 Source: metadata.TestSource, 1328 } 1329 1330 blockDir := filepath.Join(tmpDir, "tmp") 1331 1332 samplesPerSeriesPerBlock := samplesPerSeries / numOfBlocks 1333 if samplesPerSeriesPerBlock == 0 { 1334 samplesPerSeriesPerBlock = 1 1335 } 1336 1337 seriesPerBlock := totalSeries / numOfBlocks 1338 if seriesPerBlock == 0 { 1339 seriesPerBlock = 1 1340 } 1341 1342 // Create 4 blocks. Each will have seriesPerBlock number of series that have samplesPerSeriesPerBlock samples. 1343 // Timestamp will be counted for each new series and new sample, so each each series will have unique timestamp. 1344 // This allows to pick time range that will correspond to number of series picked 1:1. 1345 for bi := 0; bi < numOfBlocks; bi++ { 1346 head, _ := storetestutil.CreateHeadWithSeries(t, bi, storetestutil.HeadGenOptions{ 1347 TSDBDir: filepath.Join(tmpDir, fmt.Sprintf("%d", bi)), 1348 SamplesPerSeries: samplesPerSeriesPerBlock, 1349 Series: seriesPerBlock, 1350 PrependLabels: extLset, 1351 Random: random, 1352 SkipChunks: t.IsBenchmark() || skipChunk, 1353 SampleType: sampleType, 1354 }) 1355 id := createBlockFromHead(t, blockDir, head) 1356 testutil.Ok(t, head.Close()) 1357 1358 // Histogram chunks are represented differently in memory and on disk. In order to 1359 // have a precise comparison, we need to use the on-disk representation as the expected value 1360 // instead of the in-memory one. 1361 diskBlock, err := tsdb.OpenBlock(logger, path.Join(blockDir, id.String()), nil) 1362 testutil.Ok(t, err) 1363 series = append(series, storetestutil.ReadSeriesFromBlock(t, diskBlock, extLset, skipChunk)...) 1364 1365 meta, err := metadata.InjectThanos(logger, filepath.Join(blockDir, id.String()), thanosMeta, nil) 1366 testutil.Ok(t, err) 1367 1368 testutil.Ok(t, meta.WriteToDir(logger, filepath.Join(blockDir, id.String()))) 1369 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc)) 1370 } 1371 1372 ibkt := objstore.WithNoopInstr(bkt) 1373 f, err := block.NewRawMetaFetcher(logger, ibkt) 1374 testutil.Ok(t, err) 1375 1376 chunkPool, err := pool.NewBucketedBytes(chunkBytesPoolMinSize, chunkBytesPoolMaxSize, 2, 1e9) // 1GB. 1377 testutil.Ok(t, err) 1378 1379 st, err := NewBucketStore( 1380 ibkt, 1381 f, 1382 tmpDir, 1383 NewChunksLimiterFactory(0), 1384 NewSeriesLimiterFactory(0), 1385 NewBytesLimiterFactory(0), 1386 NewGapBasedPartitioner(PartitionerMaxGapSize), 1387 1, 1388 false, 1389 DefaultPostingOffsetInMemorySampling, 1390 false, 1391 false, 1392 0, 1393 WithLogger(logger), 1394 WithChunkPool(chunkPool), 1395 ) 1396 testutil.Ok(t, err) 1397 1398 if !t.IsBenchmark() { 1399 st.chunkPool = &mockedPool{parent: st.chunkPool} 1400 } 1401 1402 testutil.Ok(t, st.SyncBlocks(context.Background())) 1403 1404 var bCases []*storetestutil.SeriesCase 1405 for _, p := range requestedRatios { 1406 expectedSamples := int(p * float64(totalSeries*samplesPerSeries)) 1407 if expectedSamples == 0 { 1408 expectedSamples = 1 1409 } 1410 seriesCut := int(p * float64(numOfBlocks*seriesPerBlock)) 1411 if seriesCut == 0 { 1412 seriesCut = 1 1413 } else if seriesCut == 1 { 1414 seriesCut = expectedSamples / samplesPerSeriesPerBlock 1415 } 1416 1417 matchersCase := []*labels.Matcher{ 1418 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 1419 labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"), 1420 labels.MustNewMatcher(labels.MatchEqual, "j", "0"), 1421 labels.MustNewMatcher(labels.MatchNotEqual, "j", "0"), 1422 labels.MustNewMatcher(labels.MatchRegexp, "j", "(0|1)"), 1423 labels.MustNewMatcher(labels.MatchRegexp, "j", "0|1"), 1424 labels.MustNewMatcher(labels.MatchNotRegexp, "j", "(0|1)"), 1425 labels.MustNewMatcher(labels.MatchNotRegexp, "j", "0|1"), 1426 } 1427 1428 for _, lm := range matchersCase { 1429 var expectedSeries []*storepb.Series 1430 m, err := storepb.PromMatchersToMatchers(lm) 1431 testutil.Ok(t, err) 1432 1433 // seriesCut does not cut chunks properly, but those are assured against for non benchmarks only, where we use 100% case only. 1434 for _, s := range series[:seriesCut] { 1435 for _, label := range s.Labels { 1436 if label.Name == lm.Name && lm.Matches(label.Value) { 1437 expectedSeries = append(expectedSeries, s) 1438 break 1439 } 1440 } 1441 } 1442 bCases = append(bCases, &storetestutil.SeriesCase{ 1443 Name: fmt.Sprintf("%dof%d[%s]", expectedSamples, totalSeries*samplesPerSeries, lm.String()), 1444 Req: &storepb.SeriesRequest{ 1445 MinTime: 0, 1446 MaxTime: int64(expectedSamples) - 1, 1447 Matchers: m, 1448 SkipChunks: skipChunk, 1449 }, 1450 ExpectedSeries: expectedSeries, 1451 }) 1452 } 1453 } 1454 storetestutil.TestServerSeries(t, st, bCases...) 1455 1456 if !t.IsBenchmark() { 1457 if !skipChunk { 1458 // TODO(bwplotka): This is wrong negative for large number of samples (1mln). Investigate. 1459 testutil.Equals(t, 0, int(st.chunkPool.(*mockedPool).balance.Load())) 1460 st.chunkPool.(*mockedPool).gets.Store(0) 1461 } 1462 1463 for _, b := range st.blocks { 1464 // NOTE(bwplotka): It is 4 x 1.0 for 100mln samples. Kind of make sense: long series. 1465 testutil.Equals(t, 0.0, promtest.ToFloat64(b.metrics.seriesRefetches)) 1466 } 1467 } 1468 } 1469 1470 var _ = fakePool{} 1471 1472 type fakePool struct{} 1473 1474 func (m fakePool) Get(sz int) (*[]byte, error) { 1475 b := make([]byte, 0, sz) 1476 return &b, nil 1477 } 1478 1479 func (m fakePool) Put(_ *[]byte) {} 1480 1481 type mockedPool struct { 1482 parent pool.Bytes 1483 balance atomic.Uint64 1484 gets atomic.Uint64 1485 } 1486 1487 func (m *mockedPool) Get(sz int) (*[]byte, error) { 1488 b, err := m.parent.Get(sz) 1489 if err != nil { 1490 return nil, err 1491 } 1492 m.balance.Add(uint64(cap(*b))) 1493 m.gets.Add(uint64(1)) 1494 return b, nil 1495 } 1496 1497 func (m *mockedPool) Put(b *[]byte) { 1498 m.balance.Sub(uint64(cap(*b))) 1499 m.parent.Put(b) 1500 } 1501 1502 // Regression test against: https://github.com/thanos-io/thanos/issues/2147. 1503 func TestBucketSeries_OneBlock_InMemIndexCacheSegfault(t *testing.T) { 1504 tmpDir := t.TempDir() 1505 1506 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 1507 testutil.Ok(t, err) 1508 defer func() { testutil.Ok(t, bkt.Close()) }() 1509 1510 logger := log.NewLogfmtLogger(os.Stderr) 1511 thanosMeta := metadata.Thanos{ 1512 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 1513 Downsample: metadata.ThanosDownsample{Resolution: 0}, 1514 Source: metadata.TestSource, 1515 } 1516 1517 chunkPool, err := pool.NewBucketedBytes(chunkBytesPoolMinSize, chunkBytesPoolMaxSize, 2, 100e7) 1518 testutil.Ok(t, err) 1519 1520 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{ 1521 MaxItemSize: 3000, 1522 // This is the exact size of cache needed for our *single request*. 1523 // This is limited in order to make sure we test evictions. 1524 MaxSize: 8889, 1525 }) 1526 testutil.Ok(t, err) 1527 1528 var b1 *bucketBlock 1529 1530 const numSeries = 100 1531 headOpts := tsdb.DefaultHeadOptions() 1532 headOpts.ChunkDirRoot = tmpDir 1533 headOpts.ChunkRange = 1 1534 1535 // Create 4 blocks. Each will have numSeriesPerBlock number of series that have 1 sample only. 1536 // Timestamp will be counted for each new series, so each series will have unique timestamp. 1537 // This allows to pick time range that will correspond to number of series picked 1:1. 1538 { 1539 // Block 1. 1540 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 1541 testutil.Ok(t, err) 1542 defer func() { testutil.Ok(t, h.Close()) }() 1543 1544 app := h.Appender(context.Background()) 1545 1546 for i := 0; i < numSeries; i++ { 1547 ts := int64(i) 1548 lbls := labels.FromStrings("foo", "bar", "b", "1", "i", fmt.Sprintf("%07d%s", ts, storetestutil.LabelLongSuffix)) 1549 1550 _, err := app.Append(0, lbls, ts, 0) 1551 testutil.Ok(t, err) 1552 } 1553 testutil.Ok(t, app.Commit()) 1554 1555 blockDir := filepath.Join(tmpDir, "tmp") 1556 id := createBlockFromHead(t, blockDir, h) 1557 1558 meta, err := metadata.InjectThanos(log.NewNopLogger(), filepath.Join(blockDir, id.String()), thanosMeta, nil) 1559 testutil.Ok(t, err) 1560 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc)) 1561 1562 b1 = &bucketBlock{ 1563 indexCache: indexCache, 1564 logger: logger, 1565 metrics: newBucketStoreMetrics(nil), 1566 bkt: bkt, 1567 meta: meta, 1568 partitioner: NewGapBasedPartitioner(PartitionerMaxGapSize), 1569 chunkObjs: []string{filepath.Join(id.String(), "chunks", "000001")}, 1570 chunkPool: chunkPool, 1571 estimatedMaxSeriesSize: EstimatedMaxSeriesSize, 1572 estimatedMaxChunkSize: EstimatedMaxChunkSize, 1573 } 1574 b1.indexHeaderReader, err = indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, b1.meta.ULID, DefaultPostingOffsetInMemorySampling) 1575 testutil.Ok(t, err) 1576 } 1577 1578 var b2 *bucketBlock 1579 { 1580 // Block 2, do not load this block yet. 1581 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 1582 testutil.Ok(t, err) 1583 defer func() { testutil.Ok(t, h.Close()) }() 1584 1585 app := h.Appender(context.Background()) 1586 1587 for i := 0; i < numSeries; i++ { 1588 ts := int64(i) 1589 lbls := labels.FromStrings("foo", "bar", "b", "2", "i", fmt.Sprintf("%07d%s", ts, storetestutil.LabelLongSuffix)) 1590 1591 _, err := app.Append(0, lbls, ts, 0) 1592 testutil.Ok(t, err) 1593 } 1594 testutil.Ok(t, app.Commit()) 1595 1596 blockDir := filepath.Join(tmpDir, "tmp2") 1597 id := createBlockFromHead(t, blockDir, h) 1598 1599 meta, err := metadata.InjectThanos(log.NewNopLogger(), filepath.Join(blockDir, id.String()), thanosMeta, nil) 1600 testutil.Ok(t, err) 1601 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc)) 1602 1603 b2 = &bucketBlock{ 1604 indexCache: indexCache, 1605 logger: logger, 1606 metrics: newBucketStoreMetrics(nil), 1607 bkt: bkt, 1608 meta: meta, 1609 partitioner: NewGapBasedPartitioner(PartitionerMaxGapSize), 1610 chunkObjs: []string{filepath.Join(id.String(), "chunks", "000001")}, 1611 chunkPool: chunkPool, 1612 estimatedMaxSeriesSize: EstimatedMaxSeriesSize, 1613 estimatedMaxChunkSize: EstimatedMaxChunkSize, 1614 } 1615 b2.indexHeaderReader, err = indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, b2.meta.ULID, DefaultPostingOffsetInMemorySampling) 1616 testutil.Ok(t, err) 1617 } 1618 1619 store := &BucketStore{ 1620 bkt: objstore.WithNoopInstr(bkt), 1621 logger: logger, 1622 indexCache: indexCache, 1623 indexReaderPool: indexheader.NewReaderPool(log.NewNopLogger(), false, 0, indexheader.NewReaderPoolMetrics(nil)), 1624 metrics: newBucketStoreMetrics(nil), 1625 blockSets: map[uint64]*bucketBlockSet{ 1626 labels.Labels{{Name: "ext1", Value: "1"}}.Hash(): {blocks: [][]*bucketBlock{{b1, b2}}}, 1627 }, 1628 blocks: map[ulid.ULID]*bucketBlock{ 1629 b1.meta.ULID: b1, 1630 b2.meta.ULID: b2, 1631 }, 1632 queryGate: gate.NewNoop(), 1633 chunksLimiterFactory: NewChunksLimiterFactory(0), 1634 seriesLimiterFactory: NewSeriesLimiterFactory(0), 1635 bytesLimiterFactory: NewBytesLimiterFactory(0), 1636 } 1637 1638 t.Run("invoke series for one block. Fill the cache on the way.", func(t *testing.T) { 1639 srv := newStoreSeriesServer(context.Background()) 1640 testutil.Ok(t, store.Series(&storepb.SeriesRequest{ 1641 MinTime: 0, 1642 MaxTime: int64(numSeries) - 1, 1643 Matchers: []storepb.LabelMatcher{ 1644 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1645 {Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"}, 1646 // This bug shows only when we use lot's of symbols for matching. 1647 {Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""}, 1648 }, 1649 }, srv)) 1650 testutil.Equals(t, 0, len(srv.Warnings)) 1651 testutil.Equals(t, numSeries, len(srv.SeriesSet)) 1652 }) 1653 t.Run("invoke series for second block. This should revoke previous cache.", func(t *testing.T) { 1654 srv := newStoreSeriesServer(context.Background()) 1655 testutil.Ok(t, store.Series(&storepb.SeriesRequest{ 1656 MinTime: 0, 1657 MaxTime: int64(numSeries) - 1, 1658 Matchers: []storepb.LabelMatcher{ 1659 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1660 {Type: storepb.LabelMatcher_EQ, Name: "b", Value: "2"}, 1661 // This bug shows only when we use lot's of symbols for matching. 1662 {Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""}, 1663 }, 1664 }, srv)) 1665 testutil.Equals(t, 0, len(srv.Warnings)) 1666 testutil.Equals(t, numSeries, len(srv.SeriesSet)) 1667 }) 1668 t.Run("remove second block. Cache stays. Ask for first again.", func(t *testing.T) { 1669 testutil.Ok(t, store.removeBlock(b2.meta.ULID)) 1670 1671 srv := newStoreSeriesServer(context.Background()) 1672 testutil.Ok(t, store.Series(&storepb.SeriesRequest{ 1673 MinTime: 0, 1674 MaxTime: int64(numSeries) - 1, 1675 Matchers: []storepb.LabelMatcher{ 1676 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1677 {Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"}, 1678 // This bug shows only when we use lot's of symbols for matching. 1679 {Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""}, 1680 }, 1681 }, srv)) 1682 testutil.Equals(t, 0, len(srv.Warnings)) 1683 testutil.Equals(t, numSeries, len(srv.SeriesSet)) 1684 }) 1685 } 1686 1687 func TestSeries_RequestAndResponseHints(t *testing.T) { 1688 tb, store, seriesSet1, seriesSet2, block1, block2, close := setupStoreForHintsTest(t) 1689 defer close() 1690 1691 testCases := []*storetestutil.SeriesCase{ 1692 { 1693 Name: "querying a range containing 1 block should return 1 block in the response hints", 1694 Req: &storepb.SeriesRequest{ 1695 MinTime: 0, 1696 MaxTime: 1, 1697 Matchers: []storepb.LabelMatcher{ 1698 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1699 }, 1700 }, 1701 ExpectedSeries: seriesSet1, 1702 ExpectedHints: []hintspb.SeriesResponseHints{ 1703 { 1704 QueriedBlocks: []hintspb.Block{ 1705 {Id: block1.String()}, 1706 }, 1707 }, 1708 }, 1709 }, 1710 { 1711 Name: "querying a range containing multiple blocks should return multiple blocks in the response hints", 1712 Req: &storepb.SeriesRequest{ 1713 MinTime: 0, 1714 MaxTime: 3, 1715 Matchers: []storepb.LabelMatcher{ 1716 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1717 }, 1718 }, 1719 ExpectedSeries: append(append([]*storepb.Series{}, seriesSet1...), seriesSet2...), 1720 ExpectedHints: []hintspb.SeriesResponseHints{ 1721 { 1722 QueriedBlocks: []hintspb.Block{ 1723 {Id: block1.String()}, 1724 {Id: block2.String()}, 1725 }, 1726 }, 1727 }, 1728 }, 1729 { 1730 Name: "querying a range containing multiple blocks but filtering a specific block should query only the requested block", 1731 Req: &storepb.SeriesRequest{ 1732 MinTime: 0, 1733 MaxTime: 3, 1734 Matchers: []storepb.LabelMatcher{ 1735 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1736 }, 1737 Hints: mustMarshalAny(&hintspb.SeriesRequestHints{ 1738 BlockMatchers: []storepb.LabelMatcher{ 1739 {Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()}, 1740 }, 1741 }), 1742 }, 1743 ExpectedSeries: seriesSet1, 1744 ExpectedHints: []hintspb.SeriesResponseHints{ 1745 { 1746 QueriedBlocks: []hintspb.Block{ 1747 {Id: block1.String()}, 1748 }, 1749 }, 1750 }, 1751 }, 1752 { 1753 Name: "Query Stats Enabled", 1754 Req: &storepb.SeriesRequest{ 1755 MinTime: 0, 1756 MaxTime: 3, 1757 Matchers: []storepb.LabelMatcher{ 1758 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1759 }, 1760 Hints: mustMarshalAny(&hintspb.SeriesRequestHints{ 1761 BlockMatchers: []storepb.LabelMatcher{ 1762 {Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()}, 1763 }, 1764 EnableQueryStats: true, 1765 }), 1766 }, 1767 ExpectedSeries: seriesSet1, 1768 ExpectedHints: []hintspb.SeriesResponseHints{ 1769 { 1770 QueriedBlocks: []hintspb.Block{ 1771 {Id: block1.String()}, 1772 }, 1773 QueryStats: &hintspb.QueryStats{ 1774 BlocksQueried: 1, 1775 PostingsTouched: 1, 1776 PostingsFetched: 1, 1777 SeriesTouched: 2, 1778 SeriesFetched: 2, 1779 ChunksTouched: 2, 1780 ChunksFetched: 2, 1781 MergedSeriesCount: 2, 1782 MergedChunksCount: 2, 1783 }, 1784 }, 1785 }, 1786 HintsCompareFunc: func(t testutil.TB, expected, actual hintspb.SeriesResponseHints) { 1787 testutil.Equals(t, expected.QueriedBlocks, actual.QueriedBlocks) 1788 testutil.Equals(t, expected.QueryStats.BlocksQueried, actual.QueryStats.BlocksQueried) 1789 testutil.Equals(t, expected.QueryStats.PostingsTouched, actual.QueryStats.PostingsTouched) 1790 testutil.Equals(t, expected.QueryStats.PostingsFetched, actual.QueryStats.PostingsFetched) 1791 testutil.Equals(t, expected.QueryStats.SeriesTouched, actual.QueryStats.SeriesTouched) 1792 testutil.Equals(t, expected.QueryStats.SeriesFetched, actual.QueryStats.SeriesFetched) 1793 testutil.Equals(t, expected.QueryStats.ChunksTouched, actual.QueryStats.ChunksTouched) 1794 testutil.Equals(t, expected.QueryStats.ChunksFetched, actual.QueryStats.ChunksFetched) 1795 testutil.Equals(t, expected.QueryStats.MergedSeriesCount, actual.QueryStats.MergedSeriesCount) 1796 testutil.Equals(t, expected.QueryStats.MergedChunksCount, actual.QueryStats.MergedChunksCount) 1797 }, 1798 }, 1799 } 1800 1801 storetestutil.TestServerSeries(tb, store, testCases...) 1802 } 1803 1804 func TestSeries_ErrorUnmarshallingRequestHints(t *testing.T) { 1805 tb := testutil.NewTB(t) 1806 1807 tmpDir := t.TempDir() 1808 1809 bktDir := filepath.Join(tmpDir, "bkt") 1810 bkt, err := filesystem.NewBucket(bktDir) 1811 testutil.Ok(t, err) 1812 defer func() { testutil.Ok(t, bkt.Close()) }() 1813 1814 var ( 1815 logger = log.NewNopLogger() 1816 instrBkt = objstore.WithNoopInstr(bkt) 1817 ) 1818 1819 // Instance a real bucket store we'll use to query the series. 1820 fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil) 1821 testutil.Ok(tb, err) 1822 1823 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{}) 1824 testutil.Ok(tb, err) 1825 1826 store, err := NewBucketStore( 1827 instrBkt, 1828 fetcher, 1829 tmpDir, 1830 NewChunksLimiterFactory(10000/MaxSamplesPerChunk), 1831 NewSeriesLimiterFactory(0), 1832 NewBytesLimiterFactory(0), 1833 NewGapBasedPartitioner(PartitionerMaxGapSize), 1834 10, 1835 false, 1836 DefaultPostingOffsetInMemorySampling, 1837 true, 1838 false, 1839 0, 1840 WithLogger(logger), 1841 WithIndexCache(indexCache), 1842 ) 1843 testutil.Ok(tb, err) 1844 defer func() { testutil.Ok(t, store.Close()) }() 1845 1846 testutil.Ok(tb, store.SyncBlocks(context.Background())) 1847 1848 // Create a request with invalid hints (uses response hints instead of request hints). 1849 req := &storepb.SeriesRequest{ 1850 MinTime: 0, 1851 MaxTime: 3, 1852 Matchers: []storepb.LabelMatcher{ 1853 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 1854 }, 1855 Hints: mustMarshalAny(&hintspb.SeriesResponseHints{}), 1856 } 1857 1858 srv := newStoreSeriesServer(context.Background()) 1859 err = store.Series(req, srv) 1860 testutil.NotOk(t, err) 1861 testutil.Equals(t, true, regexp.MustCompile(".*unmarshal series request hints.*").MatchString(err.Error())) 1862 } 1863 1864 func TestSeries_BlockWithMultipleChunks(t *testing.T) { 1865 tb := testutil.NewTB(t) 1866 1867 tmpDir := t.TempDir() 1868 1869 // Create a block with 1 series but an high number of samples, 1870 // so that they will span across multiple chunks. 1871 headOpts := tsdb.DefaultHeadOptions() 1872 headOpts.ChunkDirRoot = filepath.Join(tmpDir, "block") 1873 headOpts.ChunkRange = 10000000000 1874 1875 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 1876 testutil.Ok(t, err) 1877 defer func() { testutil.Ok(t, h.Close()) }() 1878 1879 series := labels.FromStrings("__name__", "test") 1880 for ts := int64(0); ts < 10000; ts++ { 1881 // Appending a single sample is very unoptimised, but guarantees each chunk is always MaxSamplesPerChunk 1882 // (except the last one, which could be smaller). 1883 app := h.Appender(context.Background()) 1884 _, err := app.Append(0, series, ts, float64(ts)) 1885 testutil.Ok(t, err) 1886 testutil.Ok(t, app.Commit()) 1887 } 1888 1889 blk := createBlockFromHead(t, headOpts.ChunkDirRoot, h) 1890 1891 thanosMeta := metadata.Thanos{ 1892 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 1893 Downsample: metadata.ThanosDownsample{Resolution: 0}, 1894 Source: metadata.TestSource, 1895 } 1896 1897 _, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil) 1898 testutil.Ok(t, err) 1899 1900 // Create a bucket and upload the block there. 1901 bktDir := filepath.Join(tmpDir, "bucket") 1902 bkt, err := filesystem.NewBucket(bktDir) 1903 testutil.Ok(t, err) 1904 defer func() { testutil.Ok(t, bkt.Close()) }() 1905 1906 instrBkt := objstore.WithNoopInstr(bkt) 1907 logger := log.NewNopLogger() 1908 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc)) 1909 1910 // Instance a real bucket store we'll use to query the series. 1911 fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil) 1912 testutil.Ok(tb, err) 1913 1914 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{}) 1915 testutil.Ok(tb, err) 1916 1917 store, err := NewBucketStore( 1918 instrBkt, 1919 fetcher, 1920 tmpDir, 1921 NewChunksLimiterFactory(100000/MaxSamplesPerChunk), 1922 NewSeriesLimiterFactory(0), 1923 NewBytesLimiterFactory(0), 1924 NewGapBasedPartitioner(PartitionerMaxGapSize), 1925 10, 1926 false, 1927 DefaultPostingOffsetInMemorySampling, 1928 true, 1929 false, 1930 0, 1931 WithLogger(logger), 1932 WithIndexCache(indexCache), 1933 ) 1934 testutil.Ok(tb, err) 1935 testutil.Ok(tb, store.SyncBlocks(context.Background())) 1936 1937 tests := map[string]struct { 1938 reqMinTime int64 1939 reqMaxTime int64 1940 expectedSamples int 1941 }{ 1942 "query the entire block": { 1943 reqMinTime: math.MinInt64, 1944 reqMaxTime: math.MaxInt64, 1945 expectedSamples: 10000, 1946 }, 1947 "query the beginning of the block": { 1948 reqMinTime: 0, 1949 reqMaxTime: 100, 1950 expectedSamples: MaxSamplesPerChunk, 1951 }, 1952 "query the middle of the block": { 1953 reqMinTime: 4000, 1954 reqMaxTime: 4050, 1955 expectedSamples: MaxSamplesPerChunk, 1956 }, 1957 "query the end of the block": { 1958 reqMinTime: 9800, 1959 reqMaxTime: 10000, 1960 expectedSamples: (MaxSamplesPerChunk * 2) + (10000 % MaxSamplesPerChunk), 1961 }, 1962 } 1963 1964 for testName, testData := range tests { 1965 t.Run(testName, func(t *testing.T) { 1966 req := &storepb.SeriesRequest{ 1967 MinTime: testData.reqMinTime, 1968 MaxTime: testData.reqMaxTime, 1969 Matchers: []storepb.LabelMatcher{ 1970 {Type: storepb.LabelMatcher_EQ, Name: "__name__", Value: "test"}, 1971 }, 1972 } 1973 1974 srv := newStoreSeriesServer(context.Background()) 1975 err = store.Series(req, srv) 1976 testutil.Ok(t, err) 1977 testutil.Assert(t, len(srv.SeriesSet) == 1) 1978 1979 // Count the number of samples in the returned chunks. 1980 numSamples := 0 1981 for _, rawChunk := range srv.SeriesSet[0].Chunks { 1982 decodedChunk, err := chunkenc.FromData(chunkenc.EncXOR, rawChunk.Raw.Data) 1983 testutil.Ok(t, err) 1984 1985 numSamples += decodedChunk.NumSamples() 1986 } 1987 1988 testutil.Assert(t, testData.expectedSamples == numSamples, "expected: %d, actual: %d", testData.expectedSamples, numSamples) 1989 }) 1990 } 1991 } 1992 1993 func TestSeries_SeriesSortedWithoutReplicaLabels(t *testing.T) { 1994 tests := map[string]struct { 1995 series [][]labels.Labels 1996 replicaLabels []string 1997 expectedSeries []labels.Labels 1998 }{ 1999 "use TSDB label as replica label": { 2000 series: [][]labels.Labels{ 2001 { 2002 labels.FromStrings("a", "1", "replica", "1", "z", "1"), 2003 labels.FromStrings("a", "1", "replica", "1", "z", "2"), 2004 labels.FromStrings("a", "1", "replica", "2", "z", "1"), 2005 labels.FromStrings("a", "1", "replica", "2", "z", "2"), 2006 labels.FromStrings("a", "2", "replica", "1", "z", "1"), 2007 labels.FromStrings("a", "2", "replica", "2", "z", "1"), 2008 }, 2009 { 2010 labels.FromStrings("a", "1", "replica", "3", "z", "1"), 2011 labels.FromStrings("a", "1", "replica", "3", "z", "2"), 2012 labels.FromStrings("a", "2", "replica", "3", "z", "1"), 2013 }, 2014 }, 2015 replicaLabels: []string{"replica"}, 2016 expectedSeries: []labels.Labels{ 2017 labels.FromStrings("a", "1", "ext1", "0", "z", "1"), 2018 labels.FromStrings("a", "1", "ext1", "0", "z", "2"), 2019 labels.FromStrings("a", "1", "ext1", "1", "z", "1"), 2020 labels.FromStrings("a", "1", "ext1", "1", "z", "2"), 2021 labels.FromStrings("a", "2", "ext1", "0", "z", "1"), 2022 labels.FromStrings("a", "2", "ext1", "1", "z", "1"), 2023 }, 2024 }, 2025 "use external label as replica label": { 2026 series: [][]labels.Labels{ 2027 { 2028 labels.FromStrings("a", "1", "replica", "1", "z", "1"), 2029 labels.FromStrings("a", "1", "replica", "1", "z", "2"), 2030 labels.FromStrings("a", "1", "replica", "2", "z", "1"), 2031 labels.FromStrings("a", "1", "replica", "2", "z", "2"), 2032 }, 2033 { 2034 labels.FromStrings("a", "1", "replica", "1", "z", "1"), 2035 labels.FromStrings("a", "1", "replica", "1", "z", "2"), 2036 }, 2037 }, 2038 replicaLabels: []string{"ext1"}, 2039 expectedSeries: []labels.Labels{ 2040 labels.FromStrings("a", "1", "replica", "1", "z", "1"), 2041 labels.FromStrings("a", "1", "replica", "1", "z", "2"), 2042 labels.FromStrings("a", "1", "replica", "2", "z", "1"), 2043 labels.FromStrings("a", "1", "replica", "2", "z", "2"), 2044 }, 2045 }, 2046 } 2047 2048 for testName, testData := range tests { 2049 t.Run(testName, func(t *testing.T) { 2050 tb := testutil.NewTB(t) 2051 2052 tmpDir := t.TempDir() 2053 2054 bktDir := filepath.Join(tmpDir, "bucket") 2055 bkt, err := filesystem.NewBucket(bktDir) 2056 testutil.Ok(t, err) 2057 defer testutil.Ok(t, bkt.Close()) 2058 2059 instrBkt := objstore.WithNoopInstr(bkt) 2060 logger := log.NewNopLogger() 2061 2062 for i, series := range testData.series { 2063 replicaVal := strconv.Itoa(i) 2064 head := uploadSeriesToBucket(t, bkt, replicaVal, filepath.Join(tmpDir, replicaVal), series) 2065 defer testutil.Ok(t, head.Close()) 2066 } 2067 2068 // Instance a real bucket store we'll use to query the series. 2069 fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil) 2070 testutil.Ok(tb, err) 2071 2072 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{}) 2073 testutil.Ok(tb, err) 2074 2075 store, err := NewBucketStore( 2076 instrBkt, 2077 fetcher, 2078 tmpDir, 2079 NewChunksLimiterFactory(100000/MaxSamplesPerChunk), 2080 NewSeriesLimiterFactory(0), 2081 NewBytesLimiterFactory(0), 2082 NewGapBasedPartitioner(PartitionerMaxGapSize), 2083 10, 2084 false, 2085 DefaultPostingOffsetInMemorySampling, 2086 true, 2087 false, 2088 0, 2089 WithLogger(logger), 2090 WithIndexCache(indexCache), 2091 ) 2092 testutil.Ok(tb, err) 2093 testutil.Ok(tb, store.SyncBlocks(context.Background())) 2094 2095 req := &storepb.SeriesRequest{ 2096 MinTime: math.MinInt, 2097 MaxTime: math.MaxInt64, 2098 Matchers: []storepb.LabelMatcher{ 2099 {Type: storepb.LabelMatcher_RE, Name: "a", Value: ".+"}, 2100 }, 2101 WithoutReplicaLabels: testData.replicaLabels, 2102 } 2103 2104 srv := newStoreSeriesServer(context.Background()) 2105 err = store.Series(req, srv) 2106 testutil.Ok(t, err) 2107 testutil.Assert(t, len(srv.SeriesSet) == len(testData.expectedSeries)) 2108 2109 var response []labels.Labels 2110 for _, respSeries := range srv.SeriesSet { 2111 promLabels := labelpb.ZLabelsToPromLabels(respSeries.Labels) 2112 response = append(response, promLabels) 2113 } 2114 2115 testutil.Equals(t, testData.expectedSeries, response) 2116 }) 2117 } 2118 } 2119 2120 func uploadSeriesToBucket(t *testing.T, bkt *filesystem.Bucket, replica string, path string, series []labels.Labels) *tsdb.Head { 2121 headOpts := tsdb.DefaultHeadOptions() 2122 headOpts.ChunkDirRoot = filepath.Join(path, "block") 2123 2124 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 2125 testutil.Ok(t, err) 2126 2127 for _, s := range series { 2128 for ts := int64(0); ts < 100; ts++ { 2129 // Appending a single sample is very unoptimised, but guarantees each chunk is always MaxSamplesPerChunk 2130 // (except the last one, which could be smaller). 2131 app := h.Appender(context.Background()) 2132 _, err := app.Append(0, s, ts, float64(ts)) 2133 testutil.Ok(t, err) 2134 testutil.Ok(t, app.Commit()) 2135 } 2136 } 2137 2138 blk := storetestutil.CreateBlockFromHead(t, headOpts.ChunkDirRoot, h) 2139 2140 thanosMeta := metadata.Thanos{ 2141 Labels: labels.Labels{{Name: "ext1", Value: replica}}.Map(), 2142 Downsample: metadata.ThanosDownsample{Resolution: 0}, 2143 Source: metadata.TestSource, 2144 } 2145 2146 _, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil) 2147 testutil.Ok(t, err) 2148 2149 testutil.Ok(t, block.Upload(context.Background(), log.NewNopLogger(), bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc)) 2150 testutil.Ok(t, err) 2151 2152 return h 2153 } 2154 2155 func mustMarshalAny(pb proto.Message) *types.Any { 2156 out, err := types.MarshalAny(pb) 2157 if err != nil { 2158 panic(err) 2159 } 2160 return out 2161 } 2162 2163 func TestBigEndianPostingsCount(t *testing.T) { 2164 const count = 1000 2165 raw := make([]byte, count*4) 2166 2167 for ix := 0; ix < count; ix++ { 2168 binary.BigEndian.PutUint32(raw[4*ix:], rand.Uint32()) 2169 } 2170 2171 p := newBigEndianPostings(raw) 2172 testutil.Equals(t, count, p.length()) 2173 2174 c := 0 2175 for p.Next() { 2176 c++ 2177 } 2178 testutil.Equals(t, count, c) 2179 } 2180 2181 func createBlockWithOneSeriesWithStep(t testutil.TB, dir string, lbls labels.Labels, blockIndex, totalSamples int, random *rand.Rand, step int64) ulid.ULID { 2182 headOpts := tsdb.DefaultHeadOptions() 2183 headOpts.ChunkDirRoot = dir 2184 headOpts.ChunkRange = int64(totalSamples) * step 2185 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 2186 testutil.Ok(t, err) 2187 defer func() { testutil.Ok(t, h.Close()) }() 2188 2189 app := h.Appender(context.Background()) 2190 2191 ts := int64(blockIndex * totalSamples) 2192 ref, err := app.Append(0, lbls, ts, random.Float64()) 2193 testutil.Ok(t, err) 2194 for i := 1; i < totalSamples; i++ { 2195 _, err := app.Append(ref, nil, ts+step*int64(i), random.Float64()) 2196 testutil.Ok(t, err) 2197 } 2198 testutil.Ok(t, app.Commit()) 2199 2200 return createBlockFromHead(t, dir, h) 2201 } 2202 2203 func setupStoreForHintsTest(t *testing.T) (testutil.TB, *BucketStore, []*storepb.Series, []*storepb.Series, ulid.ULID, ulid.ULID, func()) { 2204 tb := testutil.NewTB(t) 2205 2206 closers := []func(){} 2207 2208 tmpDir := t.TempDir() 2209 2210 bktDir := filepath.Join(tmpDir, "bkt") 2211 bkt, err := filesystem.NewBucket(bktDir) 2212 testutil.Ok(t, err) 2213 closers = append(closers, func() { testutil.Ok(t, bkt.Close()) }) 2214 2215 var ( 2216 logger = log.NewNopLogger() 2217 instrBkt = objstore.WithNoopInstr(bkt) 2218 random = rand.New(rand.NewSource(120)) 2219 ) 2220 2221 extLset := labels.Labels{{Name: "ext1", Value: "1"}} 2222 // Inject the Thanos meta to each block in the storage. 2223 thanosMeta := metadata.Thanos{ 2224 Labels: extLset.Map(), 2225 Downsample: metadata.ThanosDownsample{Resolution: 0}, 2226 Source: metadata.TestSource, 2227 } 2228 2229 // Create TSDB blocks. 2230 head, seriesSet1 := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{ 2231 TSDBDir: filepath.Join(tmpDir, "0"), 2232 SamplesPerSeries: 1, 2233 Series: 2, 2234 PrependLabels: extLset, 2235 Random: random, 2236 }) 2237 block1 := createBlockFromHead(t, bktDir, head) 2238 testutil.Ok(t, head.Close()) 2239 head2, seriesSet2 := storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{ 2240 TSDBDir: filepath.Join(tmpDir, "1"), 2241 SamplesPerSeries: 1, 2242 Series: 2, 2243 PrependLabels: extLset, 2244 Random: random, 2245 }) 2246 block2 := createBlockFromHead(t, bktDir, head2) 2247 testutil.Ok(t, head2.Close()) 2248 2249 for _, blockID := range []ulid.ULID{block1, block2} { 2250 _, err := metadata.InjectThanos(logger, filepath.Join(bktDir, blockID.String()), thanosMeta, nil) 2251 testutil.Ok(t, err) 2252 } 2253 2254 // Instance a real bucket store we'll use to query back the series. 2255 fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil) 2256 testutil.Ok(tb, err) 2257 2258 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{}) 2259 testutil.Ok(tb, err) 2260 2261 store, err := NewBucketStore( 2262 instrBkt, 2263 fetcher, 2264 tmpDir, 2265 NewChunksLimiterFactory(10000/MaxSamplesPerChunk), 2266 NewSeriesLimiterFactory(0), 2267 NewBytesLimiterFactory(0), 2268 NewGapBasedPartitioner(PartitionerMaxGapSize), 2269 10, 2270 false, 2271 DefaultPostingOffsetInMemorySampling, 2272 true, 2273 false, 2274 0, 2275 WithLogger(logger), 2276 WithIndexCache(indexCache), 2277 ) 2278 testutil.Ok(tb, err) 2279 testutil.Ok(tb, store.SyncBlocks(context.Background())) 2280 2281 closers = append(closers, func() { testutil.Ok(t, store.Close()) }) 2282 2283 return tb, store, seriesSet1, seriesSet2, block1, block2, func() { 2284 for _, close := range closers { 2285 close() 2286 } 2287 } 2288 } 2289 2290 func TestLabelNamesAndValuesHints(t *testing.T) { 2291 _, store, seriesSet1, seriesSet2, block1, block2, close := setupStoreForHintsTest(t) 2292 defer close() 2293 2294 type labelNamesValuesCase struct { 2295 name string 2296 2297 labelNamesReq *storepb.LabelNamesRequest 2298 expectedNames []string 2299 expectedNamesHints hintspb.LabelNamesResponseHints 2300 2301 labelValuesReq *storepb.LabelValuesRequest 2302 expectedValues []string 2303 expectedValuesHints hintspb.LabelValuesResponseHints 2304 } 2305 2306 testCases := []labelNamesValuesCase{ 2307 { 2308 name: "querying a range containing 1 block should return 1 block in the labels hints", 2309 2310 labelNamesReq: &storepb.LabelNamesRequest{ 2311 Start: 0, 2312 End: 1, 2313 }, 2314 expectedNames: labelNamesFromSeriesSet(seriesSet1), 2315 expectedNamesHints: hintspb.LabelNamesResponseHints{ 2316 QueriedBlocks: []hintspb.Block{ 2317 {Id: block1.String()}, 2318 }, 2319 }, 2320 2321 labelValuesReq: &storepb.LabelValuesRequest{ 2322 Label: "ext1", 2323 Start: 0, 2324 End: 1, 2325 }, 2326 expectedValues: []string{"1"}, 2327 expectedValuesHints: hintspb.LabelValuesResponseHints{ 2328 QueriedBlocks: []hintspb.Block{ 2329 {Id: block1.String()}, 2330 }, 2331 }, 2332 }, 2333 { 2334 name: "querying a range containing multiple blocks should return multiple blocks in the response hints", 2335 2336 labelNamesReq: &storepb.LabelNamesRequest{ 2337 Start: 0, 2338 End: 3, 2339 }, 2340 expectedNames: labelNamesFromSeriesSet( 2341 append(append([]*storepb.Series{}, seriesSet1...), seriesSet2...), 2342 ), 2343 expectedNamesHints: hintspb.LabelNamesResponseHints{ 2344 QueriedBlocks: []hintspb.Block{ 2345 {Id: block1.String()}, 2346 {Id: block2.String()}, 2347 }, 2348 }, 2349 2350 labelValuesReq: &storepb.LabelValuesRequest{ 2351 Label: "ext1", 2352 Start: 0, 2353 End: 3, 2354 }, 2355 expectedValues: []string{"1"}, 2356 expectedValuesHints: hintspb.LabelValuesResponseHints{ 2357 QueriedBlocks: []hintspb.Block{ 2358 {Id: block1.String()}, 2359 {Id: block2.String()}, 2360 }, 2361 }, 2362 }, { 2363 name: "querying a range containing multiple blocks but filtering a specific block should query only the requested block", 2364 2365 labelNamesReq: &storepb.LabelNamesRequest{ 2366 Start: 0, 2367 End: 3, 2368 Hints: mustMarshalAny(&hintspb.LabelNamesRequestHints{ 2369 BlockMatchers: []storepb.LabelMatcher{ 2370 {Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()}, 2371 }, 2372 }), 2373 }, 2374 expectedNames: labelNamesFromSeriesSet(seriesSet1), 2375 expectedNamesHints: hintspb.LabelNamesResponseHints{ 2376 QueriedBlocks: []hintspb.Block{ 2377 {Id: block1.String()}, 2378 }, 2379 }, 2380 2381 labelValuesReq: &storepb.LabelValuesRequest{ 2382 Label: "ext1", 2383 Start: 0, 2384 End: 3, 2385 Hints: mustMarshalAny(&hintspb.LabelValuesRequestHints{ 2386 BlockMatchers: []storepb.LabelMatcher{ 2387 {Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()}, 2388 }, 2389 }), 2390 }, 2391 expectedValues: []string{"1"}, 2392 expectedValuesHints: hintspb.LabelValuesResponseHints{ 2393 QueriedBlocks: []hintspb.Block{ 2394 {Id: block1.String()}, 2395 }, 2396 }, 2397 }, 2398 } 2399 2400 for _, tc := range testCases { 2401 t.Run(tc.name, func(t *testing.T) { 2402 namesResp, err := store.LabelNames(context.Background(), tc.labelNamesReq) 2403 testutil.Ok(t, err) 2404 testutil.Equals(t, tc.expectedNames, namesResp.Names) 2405 2406 var namesHints hintspb.LabelNamesResponseHints 2407 testutil.Ok(t, types.UnmarshalAny(namesResp.Hints, &namesHints)) 2408 // The order is not determinate, so we are sorting them. 2409 sort.Slice(namesHints.QueriedBlocks, func(i, j int) bool { 2410 return namesHints.QueriedBlocks[i].Id < namesHints.QueriedBlocks[j].Id 2411 }) 2412 testutil.Equals(t, tc.expectedNamesHints, namesHints) 2413 2414 valuesResp, err := store.LabelValues(context.Background(), tc.labelValuesReq) 2415 testutil.Ok(t, err) 2416 testutil.Equals(t, tc.expectedValues, valuesResp.Values) 2417 2418 var valuesHints hintspb.LabelValuesResponseHints 2419 testutil.Ok(t, types.UnmarshalAny(valuesResp.Hints, &valuesHints)) 2420 // The order is not determinate, so we are sorting them. 2421 sort.Slice(valuesHints.QueriedBlocks, func(i, j int) bool { 2422 return valuesHints.QueriedBlocks[i].Id < valuesHints.QueriedBlocks[j].Id 2423 }) 2424 testutil.Equals(t, tc.expectedValuesHints, valuesHints) 2425 }) 2426 } 2427 } 2428 2429 func TestSeries_ChunksHaveHashRepresentation(t *testing.T) { 2430 tb := testutil.NewTB(t) 2431 2432 tmpDir := t.TempDir() 2433 2434 headOpts := tsdb.DefaultHeadOptions() 2435 headOpts.ChunkDirRoot = filepath.Join(tmpDir, "block") 2436 2437 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 2438 testutil.Ok(t, err) 2439 defer func() { testutil.Ok(t, h.Close()) }() 2440 2441 series := labels.FromStrings("__name__", "test") 2442 app := h.Appender(context.Background()) 2443 for ts := int64(0); ts < 10_000; ts++ { 2444 _, err := app.Append(0, series, ts, float64(ts)) 2445 testutil.Ok(t, err) 2446 } 2447 testutil.Ok(t, app.Commit()) 2448 2449 blk := createBlockFromHead(t, headOpts.ChunkDirRoot, h) 2450 2451 thanosMeta := metadata.Thanos{ 2452 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 2453 Downsample: metadata.ThanosDownsample{Resolution: 0}, 2454 Source: metadata.TestSource, 2455 } 2456 2457 _, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil) 2458 testutil.Ok(t, err) 2459 2460 // Create a bucket and upload the block there. 2461 bktDir := filepath.Join(tmpDir, "bucket") 2462 bkt, err := filesystem.NewBucket(bktDir) 2463 testutil.Ok(t, err) 2464 defer func() { testutil.Ok(t, bkt.Close()) }() 2465 2466 instrBkt := objstore.WithNoopInstr(bkt) 2467 logger := log.NewNopLogger() 2468 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc)) 2469 2470 // Instance a real bucket store we'll use to query the series. 2471 fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil) 2472 testutil.Ok(tb, err) 2473 2474 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{}) 2475 testutil.Ok(tb, err) 2476 2477 store, err := NewBucketStore( 2478 instrBkt, 2479 fetcher, 2480 tmpDir, 2481 NewChunksLimiterFactory(100000/MaxSamplesPerChunk), 2482 NewSeriesLimiterFactory(0), 2483 NewBytesLimiterFactory(0), 2484 NewGapBasedPartitioner(PartitionerMaxGapSize), 2485 10, 2486 false, 2487 DefaultPostingOffsetInMemorySampling, 2488 true, 2489 false, 2490 0, 2491 WithLogger(logger), 2492 WithIndexCache(indexCache), 2493 ) 2494 testutil.Ok(tb, err) 2495 testutil.Ok(tb, store.SyncBlocks(context.Background())) 2496 2497 reqMinTime := math.MinInt64 2498 reqMaxTime := math.MaxInt64 2499 2500 testCases := []struct { 2501 name string 2502 calculateChecksum bool 2503 }{ 2504 { 2505 name: "calculate checksum", 2506 calculateChecksum: true, 2507 }, 2508 } 2509 2510 for _, tc := range testCases { 2511 t.Run(tc.name, func(t *testing.T) { 2512 req := &storepb.SeriesRequest{ 2513 MinTime: int64(reqMinTime), 2514 MaxTime: int64(reqMaxTime), 2515 Matchers: []storepb.LabelMatcher{ 2516 {Type: storepb.LabelMatcher_EQ, Name: "__name__", Value: "test"}, 2517 }, 2518 } 2519 2520 srv := newStoreSeriesServer(context.Background()) 2521 err = store.Series(req, srv) 2522 testutil.Ok(t, err) 2523 testutil.Assert(t, len(srv.SeriesSet) == 1) 2524 2525 for _, rawChunk := range srv.SeriesSet[0].Chunks { 2526 hash := rawChunk.Raw.Hash 2527 decodedChunk, err := chunkenc.FromData(chunkenc.EncXOR, rawChunk.Raw.Data) 2528 testutil.Ok(t, err) 2529 2530 if tc.calculateChecksum { 2531 expectedHash := xxhash.Sum64(decodedChunk.Bytes()) 2532 testutil.Equals(t, expectedHash, hash) 2533 } else { 2534 testutil.Equals(t, uint64(0), hash) 2535 } 2536 } 2537 }) 2538 } 2539 } 2540 2541 func labelNamesFromSeriesSet(series []*storepb.Series) []string { 2542 labelsMap := map[string]struct{}{} 2543 2544 for _, s := range series { 2545 for _, label := range s.Labels { 2546 labelsMap[label.Name] = struct{}{} 2547 } 2548 } 2549 2550 labels := make([]string, 0, len(labelsMap)) 2551 for k := range labelsMap { 2552 labels = append(labels, k) 2553 } 2554 2555 sort.Strings(labels) 2556 return labels 2557 } 2558 2559 func BenchmarkBucketBlock_readChunkRange(b *testing.B) { 2560 var ( 2561 ctx = context.Background() 2562 logger = log.NewNopLogger() 2563 2564 // Read chunks of different length. We're not using random to make the benchmark repeatable. 2565 readLengths = []int64{300, 500, 1000, 5000, 10000, 30000, 50000, 100000, 300000, 1500000} 2566 ) 2567 2568 tmpDir := b.TempDir() 2569 2570 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 2571 testutil.Ok(b, err) 2572 b.Cleanup(func() { 2573 testutil.Ok(b, bkt.Close()) 2574 }) 2575 2576 // Create a block. 2577 blockID := createBlockWithOneSeriesWithStep(testutil.NewTB(b), tmpDir, labels.FromStrings("__name__", "test"), 0, 100000, rand.New(rand.NewSource(0)), 5000) 2578 2579 // Upload the block to the bucket. 2580 thanosMeta := metadata.Thanos{ 2581 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 2582 Downsample: metadata.ThanosDownsample{Resolution: 0}, 2583 Source: metadata.TestSource, 2584 } 2585 2586 blockMeta, err := metadata.InjectThanos(logger, filepath.Join(tmpDir, blockID.String()), thanosMeta, nil) 2587 testutil.Ok(b, err) 2588 2589 testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 2590 2591 // Create a chunk pool with buckets between 8B and 32KB. 2592 chunkPool, err := pool.NewBucketedBytes(8, 32*1024, 2, 1e10) 2593 testutil.Ok(b, err) 2594 2595 // Create a bucket block with only the dependencies we need for the benchmark. 2596 blk, err := newBucketBlock(context.Background(), logger, newBucketStoreMetrics(nil), blockMeta, bkt, tmpDir, nil, chunkPool, nil, nil, nil, nil) 2597 testutil.Ok(b, err) 2598 2599 b.ResetTimer() 2600 2601 for n := 0; n < b.N; n++ { 2602 offset := int64(0) 2603 length := readLengths[n%len(readLengths)] 2604 2605 _, err := blk.readChunkRange(ctx, 0, offset, length, byteRanges{{offset: 0, length: int(length)}}) 2606 if err != nil { 2607 b.Fatal(err.Error()) 2608 } 2609 } 2610 } 2611 2612 func BenchmarkBlockSeries(b *testing.B) { 2613 blk, blockMeta := prepareBucket(b, compact.ResolutionLevelRaw) 2614 2615 aggrs := []storepb.Aggr{storepb.Aggr_RAW} 2616 for _, concurrency := range []int{1, 2, 4, 8, 16, 32} { 2617 b.Run(fmt.Sprintf("concurrency: %d", concurrency), func(b *testing.B) { 2618 benchmarkBlockSeriesWithConcurrency(b, concurrency, blockMeta, blk, aggrs) 2619 }) 2620 } 2621 } 2622 2623 func prepareBucket(b *testing.B, resolutionLevel compact.ResolutionLevel) (*bucketBlock, *metadata.Meta) { 2624 var ( 2625 ctx = context.Background() 2626 logger = log.NewNopLogger() 2627 ) 2628 2629 tmpDir := b.TempDir() 2630 2631 bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt")) 2632 testutil.Ok(b, err) 2633 b.Cleanup(func() { 2634 testutil.Ok(b, bkt.Close()) 2635 }) 2636 2637 // Create a block. 2638 head, _ := storetestutil.CreateHeadWithSeries(b, 0, storetestutil.HeadGenOptions{ 2639 TSDBDir: filepath.Join(tmpDir, "head"), 2640 SamplesPerSeries: 86400 / 15, // Simulate 1 day block with 15s scrape interval. 2641 ScrapeInterval: 15 * time.Second, 2642 Series: 1000, 2643 PrependLabels: nil, 2644 Random: rand.New(rand.NewSource(120)), 2645 SkipChunks: true, 2646 }) 2647 blockID := createBlockFromHead(b, tmpDir, head) 2648 2649 // Upload the block to the bucket. 2650 thanosMeta := metadata.Thanos{ 2651 Labels: labels.Labels{{Name: "ext1", Value: "1"}}.Map(), 2652 Downsample: metadata.ThanosDownsample{Resolution: 0}, 2653 Source: metadata.TestSource, 2654 } 2655 2656 blockMeta, err := metadata.InjectThanos(logger, filepath.Join(tmpDir, blockID.String()), thanosMeta, nil) 2657 testutil.Ok(b, err) 2658 2659 testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 2660 2661 if resolutionLevel > 0 { 2662 // Downsample newly-created block. 2663 blockID, err = downsample.Downsample(logger, blockMeta, head, tmpDir, int64(resolutionLevel)) 2664 testutil.Ok(b, err) 2665 blockMeta, err = metadata.ReadFromDir(filepath.Join(tmpDir, blockID.String())) 2666 testutil.Ok(b, err) 2667 2668 testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc)) 2669 } 2670 testutil.Ok(b, head.Close()) 2671 2672 // Create chunk pool and partitioner using the same production settings. 2673 chunkPool, err := NewDefaultChunkBytesPool(64 * 1024 * 1024 * 1024) 2674 testutil.Ok(b, err) 2675 2676 partitioner := NewGapBasedPartitioner(PartitionerMaxGapSize) 2677 2678 // Create an index header reader. 2679 indexHeaderReader, err := indexheader.NewBinaryReader(ctx, logger, bkt, tmpDir, blockMeta.ULID, DefaultPostingOffsetInMemorySampling) 2680 testutil.Ok(b, err) 2681 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.DefaultInMemoryIndexCacheConfig) 2682 testutil.Ok(b, err) 2683 2684 // Create a bucket block with only the dependencies we need for the benchmark. 2685 blk, err := newBucketBlock(context.Background(), logger, newBucketStoreMetrics(nil), blockMeta, bkt, tmpDir, indexCache, chunkPool, indexHeaderReader, partitioner, nil, nil) 2686 testutil.Ok(b, err) 2687 return blk, blockMeta 2688 } 2689 2690 func benchmarkBlockSeriesWithConcurrency(b *testing.B, concurrency int, blockMeta *metadata.Meta, blk *bucketBlock, aggrs []storepb.Aggr) { 2691 // Run the same number of queries per goroutine. 2692 queriesPerWorker := b.N / concurrency 2693 2694 // No limits. 2695 chunksLimiter := NewChunksLimiterFactory(0)(nil) 2696 seriesLimiter := NewSeriesLimiterFactory(0)(nil) 2697 ctx := context.Background() 2698 2699 // Run multiple workers to execute the queries. 2700 wg := sync.WaitGroup{} 2701 wg.Add(concurrency) 2702 2703 for w := 0; w < concurrency; w++ { 2704 go func() { 2705 defer wg.Done() 2706 2707 for n := 0; n < queriesPerWorker; n++ { 2708 // Each query touches a subset of series. To make it reproducible and make sure 2709 // we just don't query consecutive series (as is in the real world), we do create 2710 // a label matcher which looks for a short integer within the label value. 2711 labelMatcher := fmt.Sprintf(".*%d.*", n%20) 2712 2713 req := &storepb.SeriesRequest{ 2714 MinTime: blockMeta.MinTime, 2715 MaxTime: blockMeta.MaxTime, 2716 Matchers: []storepb.LabelMatcher{ 2717 {Type: storepb.LabelMatcher_RE, Name: "i", Value: labelMatcher}, 2718 }, 2719 SkipChunks: false, 2720 Aggregates: aggrs, 2721 } 2722 2723 matchers, err := storepb.MatchersToPromMatchers(req.Matchers...) 2724 // TODO FIXME! testutil.Ok calls b.Fatalf under the hood, which 2725 // must be called only from the goroutine running the Benchmark function. 2726 testutil.Ok(b, err) 2727 sortedMatchers := newSortedMatchers(matchers) 2728 2729 dummyHistogram := prometheus.NewHistogram(prometheus.HistogramOpts{}) 2730 blockClient := newBlockSeriesClient( 2731 ctx, 2732 nil, 2733 blk, 2734 req, 2735 chunksLimiter, 2736 NewBytesLimiterFactory(0)(nil), 2737 nil, 2738 false, 2739 SeriesBatchSize, 2740 dummyHistogram, 2741 nil, 2742 ) 2743 testutil.Ok(b, blockClient.ExpandPostings(sortedMatchers, seriesLimiter)) 2744 defer blockClient.Close() 2745 2746 // Ensure at least 1 series has been returned (as expected). 2747 _, err = blockClient.Recv() 2748 testutil.Ok(b, err) 2749 } 2750 }() 2751 } 2752 2753 wg.Wait() 2754 } 2755 2756 func BenchmarkDownsampledBlockSeries(b *testing.B) { 2757 blk, blockMeta := prepareBucket(b, compact.ResolutionLevel5m) 2758 aggrs := []storepb.Aggr{} 2759 for i := 1; i < int(storepb.Aggr_COUNTER); i++ { 2760 aggrs = append(aggrs, storepb.Aggr(i)) 2761 for _, concurrency := range []int{1, 2, 4, 8, 16, 32} { 2762 b.Run(fmt.Sprintf("aggregates: %v, concurrency: %d", aggrs, concurrency), func(b *testing.B) { 2763 benchmarkBlockSeriesWithConcurrency(b, concurrency, blockMeta, blk, aggrs) 2764 }) 2765 } 2766 } 2767 } 2768 2769 func TestExpandPostingsWithContextCancel(t *testing.T) { 2770 p := index.NewListPostings([]storage.SeriesRef{1, 2, 3, 4, 5, 6, 7, 8}) 2771 ctx, cancel := context.WithCancel(context.Background()) 2772 2773 cancel() 2774 res, err := ExpandPostingsWithContext(ctx, p) 2775 testutil.NotOk(t, err) 2776 testutil.Equals(t, context.Canceled, err) 2777 testutil.Equals(t, []storage.SeriesRef(nil), res) 2778 } 2779 2780 func TestMatchersToPostingGroup(t *testing.T) { 2781 ctx := context.Background() 2782 for _, tc := range []struct { 2783 name string 2784 matchers []*labels.Matcher 2785 labelValues map[string][]string 2786 expected []*postingGroup 2787 }{ 2788 { 2789 name: "single equal matcher", 2790 matchers: []*labels.Matcher{ 2791 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2792 }, 2793 labelValues: map[string][]string{ 2794 "foo": {"bar", "baz"}, 2795 }, 2796 expected: []*postingGroup{ 2797 { 2798 name: "foo", 2799 addAll: false, 2800 addKeys: []string{"bar"}, 2801 }, 2802 }, 2803 }, 2804 { 2805 name: "deduplicate two equal matchers", 2806 matchers: []*labels.Matcher{ 2807 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2808 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2809 }, 2810 labelValues: map[string][]string{ 2811 "foo": {"bar", "baz"}, 2812 }, 2813 expected: []*postingGroup{ 2814 { 2815 name: "foo", 2816 addAll: false, 2817 addKeys: []string{"bar"}, 2818 }, 2819 }, 2820 }, 2821 { 2822 name: "deduplicate multiple equal matchers", 2823 matchers: []*labels.Matcher{ 2824 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2825 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2826 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2827 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2828 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2829 }, 2830 labelValues: map[string][]string{ 2831 "foo": {"bar", "baz"}, 2832 }, 2833 expected: []*postingGroup{ 2834 { 2835 name: "foo", 2836 addAll: false, 2837 addKeys: []string{"bar"}, 2838 }, 2839 }, 2840 }, 2841 { 2842 name: "two equal matchers with different label name, merge", 2843 matchers: []*labels.Matcher{ 2844 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2845 labels.MustNewMatcher(labels.MatchEqual, "bar", "baz"), 2846 }, 2847 labelValues: map[string][]string{ 2848 "foo": {"bar", "baz"}, 2849 "bar": {"baz"}, 2850 }, 2851 expected: []*postingGroup{ 2852 { 2853 name: "bar", 2854 addAll: false, 2855 addKeys: []string{"baz"}, 2856 }, 2857 { 2858 name: "foo", 2859 addAll: false, 2860 addKeys: []string{"bar"}, 2861 }, 2862 }, 2863 }, 2864 { 2865 name: "two different equal matchers with same label name, intersect", 2866 matchers: []*labels.Matcher{ 2867 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2868 labels.MustNewMatcher(labels.MatchEqual, "foo", "baz"), 2869 }, 2870 labelValues: map[string][]string{ 2871 "foo": {"bar", "baz"}, 2872 }, 2873 expected: nil, 2874 }, 2875 { 2876 name: "Intersect equal and unequal matcher, no hit", 2877 matchers: []*labels.Matcher{ 2878 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2879 labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"), 2880 }, 2881 labelValues: map[string][]string{ 2882 "foo": {"bar", "baz"}, 2883 }, 2884 expected: nil, 2885 }, 2886 { 2887 name: "Intersect equal and unequal matcher, has result", 2888 matchers: []*labels.Matcher{ 2889 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2890 labels.MustNewMatcher(labels.MatchNotEqual, "foo", "baz"), 2891 }, 2892 labelValues: map[string][]string{ 2893 "foo": {"bar", "baz"}, 2894 }, 2895 expected: []*postingGroup{ 2896 { 2897 name: "foo", 2898 addAll: false, 2899 addKeys: []string{"bar"}, 2900 }, 2901 }, 2902 }, 2903 { 2904 name: "Intersect equal and regex matcher, no result", 2905 matchers: []*labels.Matcher{ 2906 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2907 labels.MustNewMatcher(labels.MatchRegexp, "foo", "x.*"), 2908 }, 2909 labelValues: map[string][]string{ 2910 "foo": {"bar", "baz"}, 2911 }, 2912 }, 2913 { 2914 name: "Intersect equal and regex matcher, have result", 2915 matchers: []*labels.Matcher{ 2916 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2917 labels.MustNewMatcher(labels.MatchRegexp, "foo", "b.*"), 2918 }, 2919 labelValues: map[string][]string{ 2920 "foo": {"bar", "baz"}, 2921 }, 2922 expected: []*postingGroup{ 2923 { 2924 name: "foo", 2925 addAll: false, 2926 addKeys: []string{"bar"}, 2927 }, 2928 }, 2929 }, 2930 { 2931 name: "Intersect equal and unequal inverse matcher", 2932 matchers: []*labels.Matcher{ 2933 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2934 labels.MustNewMatcher(labels.MatchNotEqual, "foo", ""), 2935 }, 2936 labelValues: map[string][]string{ 2937 "foo": {"bar", "baz"}, 2938 }, 2939 expected: []*postingGroup{ 2940 { 2941 name: "foo", 2942 addAll: false, 2943 addKeys: []string{"bar"}, 2944 }, 2945 }, 2946 }, 2947 { 2948 name: "Intersect equal and regexp matching non empty matcher", 2949 matchers: []*labels.Matcher{ 2950 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 2951 labels.MustNewMatcher(labels.MatchRegexp, "foo", ".+"), 2952 }, 2953 labelValues: map[string][]string{ 2954 "foo": {"bar", "baz"}, 2955 }, 2956 expected: []*postingGroup{ 2957 { 2958 name: "foo", 2959 addAll: false, 2960 addKeys: []string{"bar"}, 2961 }, 2962 }, 2963 }, 2964 { 2965 name: "Intersect two regex matchers", 2966 matchers: []*labels.Matcher{ 2967 labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar|baz"), 2968 labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar|buzz"), 2969 }, 2970 labelValues: map[string][]string{ 2971 "foo": {"bar", "baz", "buzz"}, 2972 }, 2973 expected: []*postingGroup{ 2974 { 2975 name: "foo", 2976 addAll: false, 2977 addKeys: []string{"bar"}, 2978 }, 2979 }, 2980 }, 2981 { 2982 name: "Merge inverse matchers", 2983 matchers: []*labels.Matcher{ 2984 labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"), 2985 labels.MustNewMatcher(labels.MatchNotEqual, "foo", "baz"), 2986 }, 2987 labelValues: map[string][]string{ 2988 "foo": {"buzz", "bar", "baz"}, 2989 }, 2990 expected: []*postingGroup{ 2991 { 2992 name: "foo", 2993 addAll: true, 2994 removeKeys: []string{"bar", "baz"}, 2995 }, 2996 }, 2997 }, 2998 { 2999 name: "Dedup match all regex matchers", 3000 matchers: []*labels.Matcher{ 3001 labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"), 3002 labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"), 3003 labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"), 3004 labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"), 3005 labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"), 3006 }, 3007 labelValues: map[string][]string{ 3008 labels.MetricName: {"up", "go_info"}, 3009 }, 3010 expected: []*postingGroup{ 3011 { 3012 name: labels.MetricName, 3013 addAll: true, 3014 }, 3015 }, 3016 }, 3017 { 3018 name: "Multiple posting groups", 3019 matchers: []*labels.Matcher{ 3020 labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "up"), 3021 labels.MustNewMatcher(labels.MatchRegexp, "job", ".*"), 3022 labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""), 3023 }, 3024 labelValues: map[string][]string{ 3025 labels.MetricName: {"up", "go_info"}, 3026 "cluster": {"us-east-1", "us-west-2"}, 3027 "job": {"prometheus", "thanos"}, 3028 }, 3029 expected: []*postingGroup{ 3030 { 3031 name: labels.MetricName, 3032 addAll: false, 3033 addKeys: []string{"up"}, 3034 }, 3035 { 3036 name: "cluster", 3037 addAll: false, 3038 addKeys: []string{"us-east-1", "us-west-2"}, 3039 }, 3040 { 3041 name: "job", 3042 addAll: true, 3043 }, 3044 }, 3045 }, 3046 { 3047 name: "Multiple unequal empty", 3048 matchers: []*labels.Matcher{ 3049 labels.MustNewMatcher(labels.MatchNotEqual, labels.MetricName, ""), 3050 labels.MustNewMatcher(labels.MatchNotEqual, labels.MetricName, ""), 3051 labels.MustNewMatcher(labels.MatchNotEqual, "job", ""), 3052 labels.MustNewMatcher(labels.MatchNotEqual, "job", ""), 3053 labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""), 3054 labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""), 3055 }, 3056 labelValues: map[string][]string{ 3057 labels.MetricName: {"up", "go_info"}, 3058 "cluster": {"us-east-1", "us-west-2"}, 3059 "job": {"prometheus", "thanos"}, 3060 }, 3061 expected: []*postingGroup{ 3062 { 3063 name: labels.MetricName, 3064 addAll: false, 3065 addKeys: []string{"go_info", "up"}, 3066 }, 3067 { 3068 name: "cluster", 3069 addAll: false, 3070 addKeys: []string{"us-east-1", "us-west-2"}, 3071 }, 3072 { 3073 name: "job", 3074 addAll: false, 3075 addKeys: []string{"prometheus", "thanos"}, 3076 }, 3077 }, 3078 }, 3079 } { 3080 t.Run(tc.name, func(t *testing.T) { 3081 actual, err := matchersToPostingGroups(ctx, func(name string) ([]string, error) { 3082 sort.Strings(tc.labelValues[name]) 3083 return tc.labelValues[name], nil 3084 }, tc.matchers) 3085 testutil.Ok(t, err) 3086 testutil.Equals(t, tc.expected, actual) 3087 }) 3088 } 3089 } 3090 3091 func TestPostingGroupMerge(t *testing.T) { 3092 for _, tc := range []struct { 3093 name string 3094 group1 *postingGroup 3095 group2 *postingGroup 3096 expected *postingGroup 3097 }{ 3098 { 3099 name: "empty group2", 3100 group1: &postingGroup{}, 3101 group2: nil, 3102 expected: &postingGroup{}, 3103 }, 3104 { 3105 name: "group different names, return group1", 3106 group1: &postingGroup{name: "bar"}, 3107 group2: &postingGroup{name: "foo"}, 3108 expected: nil, 3109 }, 3110 { 3111 name: "both same addKey", 3112 group1: &postingGroup{addKeys: []string{"foo"}}, 3113 group2: &postingGroup{addKeys: []string{"foo"}}, 3114 expected: &postingGroup{addKeys: []string{"foo"}}, 3115 }, 3116 { 3117 name: "both same addKeys, but different order", 3118 group1: &postingGroup{addKeys: []string{"foo", "bar"}}, 3119 group2: &postingGroup{addKeys: []string{"bar", "foo"}}, 3120 expected: &postingGroup{addKeys: []string{"bar", "foo"}}, 3121 }, 3122 { 3123 name: "different addKeys", 3124 group1: &postingGroup{addKeys: []string{"foo"}}, 3125 group2: &postingGroup{addKeys: []string{"bar"}}, 3126 expected: &postingGroup{addKeys: []string{}}, 3127 }, 3128 { 3129 name: "intersect common add keys", 3130 group1: &postingGroup{addKeys: []string{"foo", "bar"}}, 3131 group2: &postingGroup{addKeys: []string{"bar", "baz"}}, 3132 expected: &postingGroup{addKeys: []string{"bar"}}, 3133 }, 3134 { 3135 name: "both addAll, no remove keys", 3136 group1: &postingGroup{addAll: true}, 3137 group2: &postingGroup{addAll: true}, 3138 expected: &postingGroup{addAll: true}, 3139 }, 3140 { 3141 name: "both addAll, one remove keys", 3142 group1: &postingGroup{addAll: true}, 3143 group2: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3144 expected: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3145 }, 3146 { 3147 name: "both addAll, same remove keys", 3148 group1: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3149 group2: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3150 expected: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3151 }, 3152 { 3153 name: "both addAll, merge different remove keys", 3154 group1: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3155 group2: &postingGroup{addAll: true, removeKeys: []string{"bar"}}, 3156 expected: &postingGroup{addAll: true, removeKeys: []string{"bar", "foo"}}, 3157 }, 3158 { 3159 name: "both addAll, multiple remove keys", 3160 group1: &postingGroup{addAll: true, removeKeys: []string{"foo", "zoo"}}, 3161 group2: &postingGroup{addAll: true, removeKeys: []string{"a", "bar"}}, 3162 expected: &postingGroup{addAll: true, removeKeys: []string{"a", "bar", "foo", "zoo"}}, 3163 }, 3164 { 3165 name: "both addAll, multiple remove keys 2", 3166 group1: &postingGroup{addAll: true, removeKeys: []string{"a", "bar"}}, 3167 group2: &postingGroup{addAll: true, removeKeys: []string{"foo", "zoo"}}, 3168 expected: &postingGroup{addAll: true, removeKeys: []string{"a", "bar", "foo", "zoo"}}, 3169 }, 3170 { 3171 name: "one add and one remove, only keep addKeys", 3172 group1: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3173 group2: &postingGroup{addKeys: []string{""}}, 3174 expected: &postingGroup{addKeys: []string{""}}, 3175 }, 3176 { 3177 name: "one add and one remove, subtract common keys to add and remove", 3178 group1: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3179 group2: &postingGroup{addKeys: []string{"", "foo"}}, 3180 expected: &postingGroup{addKeys: []string{""}}, 3181 }, 3182 { 3183 name: "same add and remove key", 3184 group1: &postingGroup{addAll: true, removeKeys: []string{"foo"}}, 3185 group2: &postingGroup{addKeys: []string{"foo"}}, 3186 expected: &postingGroup{addKeys: []string{}}, 3187 }, 3188 { 3189 name: "same add and remove key, multiple keys", 3190 group1: &postingGroup{addAll: true, removeKeys: []string{"2"}}, 3191 group2: &postingGroup{addKeys: []string{"1", "2", "3", "4", "5", "6"}}, 3192 expected: &postingGroup{addKeys: []string{"1", "3", "4", "5", "6"}}, 3193 }, 3194 { 3195 name: "addAll and non addAll posting group merge with empty keys", 3196 group1: &postingGroup{addAll: true, removeKeys: nil}, 3197 group2: &postingGroup{addKeys: nil}, 3198 expected: &postingGroup{addKeys: nil}, 3199 }, 3200 } { 3201 t.Run(tc.name, func(t *testing.T) { 3202 if tc.group1 != nil { 3203 slices.Sort(tc.group1.addKeys) 3204 slices.Sort(tc.group1.removeKeys) 3205 } 3206 if tc.group2 != nil { 3207 slices.Sort(tc.group2.addKeys) 3208 slices.Sort(tc.group2.removeKeys) 3209 } 3210 res := tc.group1.merge(tc.group2) 3211 testutil.Equals(t, tc.expected, res) 3212 }) 3213 } 3214 } 3215 3216 // TestExpandedPostings is a test whether there is a race between multiple ExpandPostings() calls. 3217 func TestExpandedPostingsRace(t *testing.T) { 3218 const blockCount = 10 3219 3220 tmpDir := t.TempDir() 3221 t.Cleanup(func() { 3222 testutil.Ok(t, os.RemoveAll(tmpDir)) 3223 }) 3224 3225 bkt := objstore.NewInMemBucket() 3226 t.Cleanup(func() { 3227 testutil.Ok(t, bkt.Close()) 3228 }) 3229 3230 // Create a block. 3231 head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{ 3232 TSDBDir: filepath.Join(tmpDir, "head"), 3233 SamplesPerSeries: 10, 3234 ScrapeInterval: 15 * time.Second, 3235 Series: 1000, 3236 PrependLabels: nil, 3237 Random: rand.New(rand.NewSource(120)), 3238 SkipChunks: true, 3239 }) 3240 blockID := createBlockFromHead(t, tmpDir, head) 3241 3242 bucketBlocks := make([]*bucketBlock, 0, blockCount) 3243 3244 for i := 0; i < blockCount; i++ { 3245 ul := ulid.MustNew(uint64(i), rand.New(rand.NewSource(444))) 3246 3247 // Upload the block to the bucket. 3248 thanosMeta := metadata.Thanos{ 3249 Labels: labels.Labels{{Name: "ext1", Value: fmt.Sprintf("%d", i)}}.Map(), 3250 Downsample: metadata.ThanosDownsample{Resolution: 0}, 3251 Source: metadata.TestSource, 3252 } 3253 m, err := metadata.ReadFromDir(filepath.Join(tmpDir, blockID.String())) 3254 testutil.Ok(t, err) 3255 3256 m.Thanos = thanosMeta 3257 m.BlockMeta.ULID = ul 3258 3259 e2eutil.Copy(t, filepath.Join(tmpDir, blockID.String()), filepath.Join(tmpDir, ul.String())) 3260 testutil.Ok(t, m.WriteToDir(log.NewLogfmtLogger(os.Stderr), filepath.Join(tmpDir, ul.String()))) 3261 testutil.Ok(t, err) 3262 testutil.Ok(t, block.Upload(context.Background(), log.NewLogfmtLogger(os.Stderr), bkt, filepath.Join(tmpDir, ul.String()), metadata.NoneFunc)) 3263 3264 r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, ul, DefaultPostingOffsetInMemorySampling) 3265 testutil.Ok(t, err) 3266 3267 blk, err := newBucketBlock( 3268 context.Background(), 3269 log.NewLogfmtLogger(os.Stderr), 3270 newBucketStoreMetrics(nil), 3271 m, 3272 bkt, 3273 filepath.Join(tmpDir, ul.String()), 3274 noopCache{}, 3275 nil, 3276 r, 3277 NewGapBasedPartitioner(PartitionerMaxGapSize), 3278 nil, 3279 nil, 3280 ) 3281 testutil.Ok(t, err) 3282 3283 bucketBlocks = append(bucketBlocks, blk) 3284 } 3285 3286 tm, cancel := context.WithTimeout(context.Background(), 40*time.Second) 3287 t.Cleanup(cancel) 3288 3289 l := sync.Mutex{} 3290 previousRefs := make(map[int][]storage.SeriesRef) 3291 3292 for { 3293 if tm.Err() != nil { 3294 break 3295 } 3296 3297 m := []*labels.Matcher{ 3298 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 3299 labels.MustNewMatcher(labels.MatchRegexp, "j", ".+"), 3300 labels.MustNewMatcher(labels.MatchRegexp, "i", ".+"), 3301 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 3302 labels.MustNewMatcher(labels.MatchRegexp, "j", ".+"), 3303 labels.MustNewMatcher(labels.MatchRegexp, "i", ".+"), 3304 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 3305 } 3306 3307 wg := &sync.WaitGroup{} 3308 for i, bb := range bucketBlocks { 3309 wg.Add(1) 3310 i := i 3311 bb := bb 3312 go func(i int, bb *bucketBlock) { 3313 refs, err := bb.indexReader().ExpandedPostings(context.Background(), m, NewBytesLimiterFactory(0)(nil)) 3314 testutil.Ok(t, err) 3315 defer wg.Done() 3316 3317 l.Lock() 3318 defer l.Unlock() 3319 if previousRefs[i] != nil { 3320 testutil.Equals(t, previousRefs[i], refs) 3321 } else { 3322 previousRefs[i] = refs 3323 } 3324 }(i, bb) 3325 } 3326 wg.Wait() 3327 } 3328 } 3329 3330 func TestBucketIndexReader_decodeCachedPostingsErrors(t *testing.T) { 3331 bir := bucketIndexReader{stats: &queryStats{}} 3332 t.Run("should return error on broken cached postings without snappy prefix", func(t *testing.T) { 3333 _, _, err := bir.decodeCachedPostings([]byte("foo")) 3334 testutil.NotOk(t, err) 3335 }) 3336 t.Run("should return error on broken cached postings with snappy prefix", func(t *testing.T) { 3337 _, _, err := bir.decodeCachedPostings(append([]byte(codecHeaderSnappy), []byte("foo")...)) 3338 testutil.NotOk(t, err) 3339 }) 3340 } 3341 3342 func TestBucketStoreDedupOnBlockSeriesSet(t *testing.T) { 3343 logger := log.NewNopLogger() 3344 tmpDir := t.TempDir() 3345 bktDir := filepath.Join(tmpDir, "bkt") 3346 auxDir := filepath.Join(tmpDir, "aux") 3347 metaDir := filepath.Join(tmpDir, "meta") 3348 extLset := labels.FromStrings("region", "eu-west") 3349 3350 testutil.Ok(t, os.MkdirAll(metaDir, os.ModePerm)) 3351 testutil.Ok(t, os.MkdirAll(auxDir, os.ModePerm)) 3352 3353 bkt, err := filesystem.NewBucket(bktDir) 3354 testutil.Ok(t, err) 3355 t.Cleanup(func() { testutil.Ok(t, bkt.Close()) }) 3356 3357 for i := 0; i < 2; i++ { 3358 headOpts := tsdb.DefaultHeadOptions() 3359 headOpts.ChunkDirRoot = tmpDir 3360 headOpts.ChunkRange = 1000 3361 h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil) 3362 testutil.Ok(t, err) 3363 t.Cleanup(func() { testutil.Ok(t, h.Close()) }) 3364 3365 app := h.Appender(context.Background()) 3366 _, err = app.Append(0, labels.FromStrings("replica", "a", "z", "1"), 0, 1) 3367 testutil.Ok(t, err) 3368 _, err = app.Append(0, labels.FromStrings("replica", "a", "z", "2"), 0, 1) 3369 testutil.Ok(t, err) 3370 _, err = app.Append(0, labels.FromStrings("replica", "b", "z", "1"), 0, 1) 3371 testutil.Ok(t, err) 3372 _, err = app.Append(0, labels.FromStrings("replica", "b", "z", "2"), 0, 1) 3373 testutil.Ok(t, err) 3374 testutil.Ok(t, app.Commit()) 3375 3376 id := createBlockFromHead(t, auxDir, h) 3377 3378 auxBlockDir := filepath.Join(auxDir, id.String()) 3379 _, err = metadata.InjectThanos(log.NewNopLogger(), auxBlockDir, metadata.Thanos{ 3380 Labels: extLset.Map(), 3381 Downsample: metadata.ThanosDownsample{Resolution: 0}, 3382 Source: metadata.TestSource, 3383 }, nil) 3384 testutil.Ok(t, err) 3385 3386 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, auxBlockDir, metadata.NoneFunc)) 3387 testutil.Ok(t, block.Upload(context.Background(), logger, bkt, auxBlockDir, metadata.NoneFunc)) 3388 } 3389 3390 chunkPool, err := NewDefaultChunkBytesPool(2e5) 3391 testutil.Ok(t, err) 3392 3393 metaFetcher, err := block.NewMetaFetcher(logger, 20, objstore.WithNoopInstr(bkt), metaDir, nil, []block.MetadataFilter{ 3394 block.NewTimePartitionMetaFilter(allowAllFilterConf.MinTime, allowAllFilterConf.MaxTime), 3395 }) 3396 testutil.Ok(t, err) 3397 3398 bucketStore, err := NewBucketStore( 3399 objstore.WithNoopInstr(bkt), 3400 metaFetcher, 3401 "", 3402 NewChunksLimiterFactory(10e6), 3403 NewSeriesLimiterFactory(10e6), 3404 NewBytesLimiterFactory(10e6), 3405 NewGapBasedPartitioner(PartitionerMaxGapSize), 3406 20, 3407 true, 3408 DefaultPostingOffsetInMemorySampling, 3409 false, 3410 false, 3411 1*time.Minute, 3412 WithChunkPool(chunkPool), 3413 WithFilterConfig(allowAllFilterConf), 3414 ) 3415 testutil.Ok(t, err) 3416 t.Cleanup(func() { testutil.Ok(t, bucketStore.Close()) }) 3417 3418 testutil.Ok(t, bucketStore.SyncBlocks(context.Background())) 3419 3420 srv := newStoreSeriesServer(context.Background()) 3421 testutil.Ok(t, bucketStore.Series(&storepb.SeriesRequest{ 3422 WithoutReplicaLabels: []string{"replica"}, 3423 MinTime: timestamp.FromTime(minTime), 3424 MaxTime: timestamp.FromTime(maxTime), 3425 Matchers: []storepb.LabelMatcher{ 3426 {Type: storepb.LabelMatcher_NEQ, Name: "z", Value: ""}, 3427 }, 3428 }, srv)) 3429 3430 testutil.Equals(t, true, slices.IsSortedFunc(srv.SeriesSet, func(x, y storepb.Series) bool { 3431 return labels.Compare(x.PromLabels(), y.PromLabels()) < 0 3432 })) 3433 testutil.Equals(t, 2, len(srv.SeriesSet)) 3434 }