github.com/thanos-io/thanos@v0.32.5/pkg/receive/multitsdb_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package receive 5 6 import ( 7 "context" 8 "io" 9 "math" 10 "os" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/efficientgo/core/testutil" 16 "github.com/go-kit/log" 17 "github.com/prometheus/client_golang/prometheus" 18 "github.com/prometheus/prometheus/model/exemplar" 19 "github.com/prometheus/prometheus/model/labels" 20 "github.com/prometheus/prometheus/storage" 21 "github.com/prometheus/prometheus/tsdb" 22 "github.com/thanos-io/objstore" 23 "golang.org/x/sync/errgroup" 24 "google.golang.org/grpc" 25 26 "github.com/thanos-io/thanos/pkg/block/metadata" 27 "github.com/thanos-io/thanos/pkg/component" 28 "github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" 29 "github.com/thanos-io/thanos/pkg/runutil" 30 "github.com/thanos-io/thanos/pkg/store" 31 "github.com/thanos-io/thanos/pkg/store/labelpb" 32 "github.com/thanos-io/thanos/pkg/store/storepb" 33 ) 34 35 func TestMultiTSDB(t *testing.T) { 36 dir := t.TempDir() 37 38 logger := log.NewLogfmtLogger(os.Stderr) 39 t.Run("run fresh", func(t *testing.T) { 40 m := NewMultiTSDB( 41 dir, logger, prometheus.NewRegistry(), &tsdb.Options{ 42 MinBlockDuration: (2 * time.Hour).Milliseconds(), 43 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 44 RetentionDuration: (6 * time.Hour).Milliseconds(), 45 NoLockfile: true, 46 MaxExemplars: 100, 47 EnableExemplarStorage: true, 48 }, 49 labels.FromStrings("replica", "01"), 50 "tenant_id", 51 nil, 52 false, 53 metadata.NoneFunc, 54 ) 55 defer func() { testutil.Ok(t, m.Close()) }() 56 57 testutil.Ok(t, m.Flush()) 58 testutil.Ok(t, m.Open()) 59 60 app, err := m.TenantAppendable("foo") 61 testutil.Ok(t, err) 62 63 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 64 defer cancel() 65 66 var a storage.Appender 67 testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error { 68 a, err = app.Appender(context.Background()) 69 return err 70 })) 71 72 _, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 1, 2.41241) 73 testutil.Ok(t, err) 74 _, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 2, 3.41241) 75 testutil.Ok(t, err) 76 ref, err := a.Append(0, labels.FromStrings("a", "1", "b", "2"), 3, 4.41241) 77 testutil.Ok(t, err) 78 79 // Test exemplars. 80 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 1, Ts: 1, HasTs: true}) 81 testutil.Ok(t, err) 82 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 2.1212, Ts: 2, HasTs: true}) 83 testutil.Ok(t, err) 84 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 3.1313, Ts: 3, HasTs: true}) 85 testutil.Ok(t, err) 86 testutil.Ok(t, a.Commit()) 87 88 // Check if not leaking. 89 _, err = m.TenantAppendable("foo") 90 testutil.Ok(t, err) 91 _, err = m.TenantAppendable("foo") 92 testutil.Ok(t, err) 93 _, err = m.TenantAppendable("foo") 94 testutil.Ok(t, err) 95 96 ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) 97 defer cancel() 98 99 app, err = m.TenantAppendable("bar") 100 testutil.Ok(t, err) 101 102 testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error { 103 a, err = app.Appender(context.Background()) 104 return err 105 })) 106 107 _, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 1, 20.41241) 108 testutil.Ok(t, err) 109 _, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 2, 30.41241) 110 testutil.Ok(t, err) 111 ref, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 3, 40.41241) 112 testutil.Ok(t, err) 113 114 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 11, Ts: 1, HasTs: true, Labels: labels.FromStrings("traceID", "abc")}) 115 testutil.Ok(t, err) 116 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 22.1212, Ts: 2, HasTs: true, Labels: labels.FromStrings("traceID", "def")}) 117 testutil.Ok(t, err) 118 _, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 33.1313, Ts: 3, HasTs: true, Labels: labels.FromStrings("traceID", "ghi")}) 119 testutil.Ok(t, err) 120 testutil.Ok(t, a.Commit()) 121 122 testMulitTSDBSeries(t, m) 123 testMultiTSDBExemplars(t, m) 124 }) 125 t.Run("run on existing storage", func(t *testing.T) { 126 m := NewMultiTSDB( 127 dir, logger, prometheus.NewRegistry(), &tsdb.Options{ 128 MinBlockDuration: (2 * time.Hour).Milliseconds(), 129 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 130 RetentionDuration: (6 * time.Hour).Milliseconds(), 131 NoLockfile: true, 132 }, 133 labels.FromStrings("replica", "01"), 134 "tenant_id", 135 nil, 136 false, 137 metadata.NoneFunc, 138 ) 139 defer func() { testutil.Ok(t, m.Close()) }() 140 141 testutil.Ok(t, m.Flush()) 142 testutil.Ok(t, m.Open()) 143 144 // Get appender just for test. 145 app, err := m.TenantAppendable("foo") 146 testutil.Ok(t, err) 147 148 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 149 defer cancel() 150 151 testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error { 152 _, err := app.Appender(context.Background()) 153 return err 154 })) 155 156 // Check if not leaking. 157 _, err = m.TenantAppendable("foo") 158 testutil.Ok(t, err) 159 _, err = m.TenantAppendable("foo") 160 testutil.Ok(t, err) 161 _, err = m.TenantAppendable("foo") 162 testutil.Ok(t, err) 163 164 testMulitTSDBSeries(t, m) 165 }) 166 167 t.Run("flush with one sample produces a block", func(t *testing.T) { 168 const testTenant = "test_tenant" 169 m := NewMultiTSDB( 170 dir, logger, prometheus.NewRegistry(), &tsdb.Options{ 171 MinBlockDuration: (2 * time.Hour).Milliseconds(), 172 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 173 RetentionDuration: (6 * time.Hour).Milliseconds(), 174 NoLockfile: true, 175 }, 176 labels.FromStrings("replica", "01"), 177 "tenant_id", 178 nil, 179 false, 180 metadata.NoneFunc, 181 ) 182 defer func() { testutil.Ok(t, m.Close()) }() 183 184 testutil.Ok(t, m.Flush()) 185 testutil.Ok(t, m.Open()) 186 testutil.Ok(t, appendSample(m, testTenant, time.Now())) 187 188 tenant := m.tenants[testTenant] 189 db := tenant.readyStorage().Get() 190 191 testutil.Equals(t, 0, len(db.Blocks())) 192 testutil.Ok(t, m.Flush()) 193 testutil.Equals(t, 1, len(db.Blocks())) 194 }) 195 } 196 197 var ( 198 expectedFooResp = &storepb.Series{ 199 Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "foo"}}, 200 Chunks: []storepb.AggrChunk{{MinTime: 1, MaxTime: 3, Raw: &storepb.Chunk{Data: []byte("\000\003\002@\003L\235\2354X\315\001\330\r\257Mui\251\327:U"), Hash: 9768694233508509040}}}, 201 } 202 expectedBarResp = &storepb.Series{ 203 Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "bar"}}, 204 Chunks: []storepb.AggrChunk{{MinTime: 1, MaxTime: 3, Raw: &storepb.Chunk{Data: []byte("\000\003\002@4i\223\263\246\213\032\001\330\035i\337\322\352\323S\256t\270"), Hash: 2304287992246504442}}}, 205 } 206 ) 207 208 func testMulitTSDBSeries(t *testing.T, m *MultiTSDB) { 209 g := &errgroup.Group{} 210 respFoo := make(chan *storepb.Series) 211 respBar := make(chan *storepb.Series) 212 for i := 0; i < 100; i++ { 213 ss := m.TSDBLocalClients() 214 testutil.Assert(t, len(ss) == 2) 215 216 for _, s := range ss { 217 s := s 218 219 switch isFoo := strings.Contains(s.String(), "foo"); isFoo { 220 case true: 221 g.Go(func() error { 222 return getResponses(s, respFoo) 223 }) 224 case false: 225 g.Go(func() error { 226 return getResponses(s, respBar) 227 }) 228 } 229 } 230 } 231 var err error 232 go func() { 233 err = g.Wait() 234 close(respFoo) 235 close(respBar) 236 }() 237 Outer: 238 for { 239 select { 240 case r, ok := <-respFoo: 241 if !ok { 242 break Outer 243 } 244 testutil.Equals(t, expectedFooResp, r) 245 case r, ok := <-respBar: 246 if !ok { 247 break Outer 248 } 249 testutil.Equals(t, expectedBarResp, r) 250 } 251 } 252 testutil.Ok(t, err) 253 } 254 255 func getResponses(storeClient store.Client, respCh chan<- *storepb.Series) error { 256 sc, err := storeClient.Series(context.Background(), &storepb.SeriesRequest{ 257 MinTime: 0, 258 MaxTime: 10, 259 Matchers: []storepb.LabelMatcher{{Name: "a", Value: ".*", Type: storepb.LabelMatcher_RE}}, 260 }) 261 if err != nil { 262 return err 263 } 264 265 for { 266 resp, err := sc.Recv() 267 if err == io.EOF { 268 break 269 } 270 271 if err != nil { 272 return err 273 } 274 275 respCh <- resp.GetSeries() 276 } 277 278 return nil 279 } 280 281 var ( 282 expectedFooRespExemplars = []exemplarspb.ExemplarData{ 283 { 284 SeriesLabels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "foo"}}}, 285 Exemplars: []*exemplarspb.Exemplar{ 286 {Value: 1, Ts: 1}, 287 {Value: 2.1212, Ts: 2}, 288 {Value: 3.1313, Ts: 3}, 289 }, 290 }, 291 } 292 expectedBarRespExemplars = []exemplarspb.ExemplarData{ 293 { 294 SeriesLabels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "bar"}}}, 295 Exemplars: []*exemplarspb.Exemplar{ 296 {Value: 11, Ts: 1, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "abc"}}}}, 297 {Value: 22.1212, Ts: 2, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "def"}}}}, 298 {Value: 33.1313, Ts: 3, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "ghi"}}}}, 299 }, 300 }, 301 } 302 ) 303 304 func testMultiTSDBExemplars(t *testing.T, m *MultiTSDB) { 305 g := &errgroup.Group{} 306 respFoo := make(chan []exemplarspb.ExemplarData) 307 respBar := make(chan []exemplarspb.ExemplarData) 308 for i := 0; i < 100; i++ { 309 s := m.TSDBExemplars() 310 testutil.Assert(t, len(s) == 2) 311 312 g.Go(func() error { 313 srv := newExemplarsServer(context.Background()) 314 if err := s["foo"].Exemplars( 315 [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}}, 316 0, 317 10, 318 srv, 319 ); err != nil { 320 return err 321 } 322 respFoo <- srv.Data 323 return nil 324 }) 325 g.Go(func() error { 326 srv := newExemplarsServer(context.Background()) 327 if err := s["bar"].Exemplars( 328 [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}}, 329 0, 330 10, 331 srv, 332 ); err != nil { 333 return err 334 } 335 respBar <- srv.Data 336 return nil 337 }) 338 } 339 var err error 340 go func() { 341 err = g.Wait() 342 close(respFoo) 343 close(respBar) 344 }() 345 OuterE: 346 for { 347 select { 348 case r, ok := <-respFoo: 349 if !ok { 350 break OuterE 351 } 352 checkExemplarsResponse(t, expectedFooRespExemplars, r) 353 case r, ok := <-respBar: 354 if !ok { 355 break OuterE 356 } 357 checkExemplarsResponse(t, expectedBarRespExemplars, r) 358 } 359 } 360 testutil.Ok(t, err) 361 } 362 363 // exemplarsServer is test gRPC exemplarsAPI exemplars server. 364 type exemplarsServer struct { 365 // This field just exist to pseudo-implement the unused methods of the interface. 366 exemplarspb.Exemplars_ExemplarsServer 367 368 ctx context.Context 369 370 Data []exemplarspb.ExemplarData 371 Warnings []string 372 373 Size int64 374 } 375 376 func newExemplarsServer(ctx context.Context) *exemplarsServer { 377 return &exemplarsServer{ctx: ctx} 378 } 379 380 func (e *exemplarsServer) Send(r *exemplarspb.ExemplarsResponse) error { 381 e.Size += int64(r.Size()) 382 383 if r.GetWarning() != "" { 384 e.Warnings = append(e.Warnings, r.GetWarning()) 385 return nil 386 } 387 388 if r.GetData() != nil { 389 e.Data = append(e.Data, *r.GetData()) 390 return nil 391 } 392 393 // Unsupported field, skip. 394 return nil 395 } 396 397 func (s *exemplarsServer) Context() context.Context { 398 return s.ctx 399 } 400 401 func checkExemplarsResponse(t *testing.T, expected, data []exemplarspb.ExemplarData) { 402 testutil.Equals(t, len(expected), len(data)) 403 for i := range data { 404 testutil.Equals(t, expected[i].SeriesLabels, data[i].SeriesLabels) 405 testutil.Equals(t, len(expected[i].Exemplars), len(data[i].Exemplars)) 406 for j := range data[i].Exemplars { 407 testutil.Equals(t, *expected[i].Exemplars[j], *data[i].Exemplars[j]) 408 } 409 } 410 } 411 412 func TestMultiTSDBPrune(t *testing.T) { 413 tests := []struct { 414 name string 415 bucket objstore.Bucket 416 expectedTenants int 417 expectedUploads int 418 }{ 419 { 420 name: "prune tsdbs without object storage", 421 bucket: nil, 422 expectedTenants: 2, 423 expectedUploads: 0, 424 }, 425 { 426 name: "prune tsdbs with object storage", 427 bucket: objstore.NewInMemBucket(), 428 expectedTenants: 2, 429 expectedUploads: 2, 430 }, 431 } 432 433 for _, test := range tests { 434 t.Run(test.name, func(t *testing.T) { 435 dir := t.TempDir() 436 437 m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), 438 &tsdb.Options{ 439 MinBlockDuration: (2 * time.Hour).Milliseconds(), 440 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 441 RetentionDuration: (6 * time.Hour).Milliseconds(), 442 }, 443 labels.FromStrings("replica", "test"), 444 "tenant_id", 445 test.bucket, 446 false, 447 metadata.NoneFunc, 448 ) 449 defer func() { testutil.Ok(t, m.Close()) }() 450 451 for i := 0; i < 100; i++ { 452 testutil.Ok(t, appendSample(m, "deleted-tenant", time.UnixMilli(int64(10+i)))) 453 testutil.Ok(t, appendSample(m, "compacted-tenant", time.Now().Add(-4*time.Hour))) 454 testutil.Ok(t, appendSample(m, "active-tenant", time.Now().Add(time.Duration(i)*time.Second))) 455 } 456 testutil.Equals(t, 3, len(m.TSDBLocalClients())) 457 458 ctx, cancel := context.WithCancel(context.Background()) 459 defer cancel() 460 461 if test.bucket != nil { 462 go func() { 463 testutil.Ok(t, syncTSDBs(ctx, m, 10*time.Millisecond)) 464 }() 465 } 466 467 testutil.Ok(t, m.Prune(ctx)) 468 testutil.Equals(t, test.expectedTenants, len(m.TSDBLocalClients())) 469 var shippedBlocks int 470 if test.bucket != nil { 471 testutil.Ok(t, test.bucket.Iter(context.Background(), "", func(s string) error { 472 shippedBlocks++ 473 return nil 474 })) 475 } 476 testutil.Equals(t, test.expectedUploads, shippedBlocks) 477 }) 478 } 479 } 480 481 func syncTSDBs(ctx context.Context, m *MultiTSDB, interval time.Duration) error { 482 for { 483 select { 484 case <-ctx.Done(): 485 return nil 486 case <-time.After(interval): 487 _, err := m.Sync(ctx) 488 if err != nil { 489 return err 490 } 491 } 492 } 493 } 494 495 func TestMultiTSDBRecreatePrunedTenant(t *testing.T) { 496 dir := t.TempDir() 497 498 m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), 499 &tsdb.Options{ 500 MinBlockDuration: (2 * time.Hour).Milliseconds(), 501 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 502 RetentionDuration: (6 * time.Hour).Milliseconds(), 503 }, 504 labels.FromStrings("replica", "test"), 505 "tenant_id", 506 objstore.NewInMemBucket(), 507 false, 508 metadata.NoneFunc, 509 ) 510 defer func() { testutil.Ok(t, m.Close()) }() 511 512 testutil.Ok(t, appendSample(m, "foo", time.UnixMilli(int64(10)))) 513 testutil.Ok(t, m.Prune(context.Background())) 514 testutil.Equals(t, 0, len(m.TSDBLocalClients())) 515 516 testutil.Ok(t, appendSample(m, "foo", time.UnixMilli(int64(10)))) 517 testutil.Equals(t, 1, len(m.TSDBLocalClients())) 518 } 519 520 func TestMultiTSDBStats(t *testing.T) { 521 tests := []struct { 522 name string 523 tenants []string 524 expectedStats int 525 }{ 526 { 527 name: "single tenant", 528 tenants: []string{"foo"}, 529 expectedStats: 1, 530 }, 531 { 532 name: "missing tenant", 533 tenants: []string{"missing-foo"}, 534 expectedStats: 0, 535 }, 536 { 537 name: "multiple tenants with missing tenant", 538 tenants: []string{"foo", "missing-foo"}, 539 expectedStats: 1, 540 }, 541 { 542 name: "all tenants", 543 tenants: []string{"foo", "bar", "baz"}, 544 expectedStats: 3, 545 }, 546 } 547 548 for _, test := range tests { 549 t.Run(test.name, func(t *testing.T) { 550 dir := t.TempDir() 551 552 m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), 553 &tsdb.Options{ 554 MinBlockDuration: (2 * time.Hour).Milliseconds(), 555 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 556 RetentionDuration: (6 * time.Hour).Milliseconds(), 557 }, 558 labels.FromStrings("replica", "test"), 559 "tenant_id", 560 nil, 561 false, 562 metadata.NoneFunc, 563 ) 564 defer func() { testutil.Ok(t, m.Close()) }() 565 566 testutil.Ok(t, appendSample(m, "foo", time.Now())) 567 testutil.Ok(t, appendSample(m, "bar", time.Now())) 568 testutil.Ok(t, appendSample(m, "baz", time.Now())) 569 testutil.Equals(t, 3, len(m.TSDBLocalClients())) 570 571 stats := m.TenantStats(10, labels.MetricName, test.tenants...) 572 testutil.Equals(t, test.expectedStats, len(stats)) 573 }) 574 } 575 } 576 577 // Regression test for https://github.com/thanos-io/thanos/issues/6047. 578 func TestMultiTSDBWithNilStore(t *testing.T) { 579 dir := t.TempDir() 580 581 m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), 582 &tsdb.Options{ 583 MinBlockDuration: (2 * time.Hour).Milliseconds(), 584 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 585 RetentionDuration: (6 * time.Hour).Milliseconds(), 586 }, 587 labels.FromStrings("replica", "test"), 588 "tenant_id", 589 nil, 590 false, 591 metadata.NoneFunc, 592 ) 593 defer func() { testutil.Ok(t, m.Close()) }() 594 595 const tenantID = "test-tenant" 596 _, err := m.TenantAppendable(tenantID) 597 testutil.Ok(t, err) 598 599 // Get LabelSets of newly created TSDB. 600 clients := m.TSDBLocalClients() 601 for _, client := range clients { 602 testutil.Ok(t, testutil.FaultOrPanicToErr(func() { client.LabelSets() })) 603 } 604 605 // Wait for tenant to become ready before terminating the test. 606 // This allows the tear down procedure to cleanup properly. 607 testutil.Ok(t, appendSample(m, tenantID, time.Now())) 608 } 609 610 type slowClient struct { 611 store.Client 612 } 613 614 func (s *slowClient) LabelValues(ctx context.Context, r *storepb.LabelValuesRequest, _ ...grpc.CallOption) (*storepb.LabelValuesResponse, error) { 615 <-time.After(10 * time.Millisecond) 616 return s.Client.LabelValues(ctx, r) 617 } 618 619 func TestProxyLabelValues(t *testing.T) { 620 dir := t.TempDir() 621 m := NewMultiTSDB( 622 dir, nil, prometheus.NewRegistry(), &tsdb.Options{ 623 RetentionDuration: 10 * time.Minute.Milliseconds(), 624 MinBlockDuration: 5 * time.Minute.Milliseconds(), 625 MaxBlockDuration: 5 * time.Minute.Milliseconds(), 626 NoLockfile: true, 627 }, 628 labels.FromStrings("replica", "01"), 629 "tenant_id", 630 nil, 631 false, 632 metadata.NoneFunc, 633 ) 634 defer func() { testutil.Ok(t, m.Close()) }() 635 636 ctx, cancel := context.WithCancel(context.Background()) 637 defer cancel() 638 go func() { 639 for { 640 select { 641 case <-ctx.Done(): 642 return 643 default: 644 testutil.Ok(t, queryLabelValues(ctx, m)) 645 } 646 } 647 }() 648 649 // Append several samples to a TSDB outside the retention period. 650 testutil.Ok(t, appendSampleWithLabels(m, "tenant-a", labels.FromStrings(labels.MetricName, "metric-a"), time.Now().Add(-5*time.Hour))) 651 testutil.Ok(t, appendSampleWithLabels(m, "tenant-a", labels.FromStrings(labels.MetricName, "metric-b"), time.Now().Add(-3*time.Hour))) 652 testutil.Ok(t, appendSampleWithLabels(m, "tenant-b", labels.FromStrings(labels.MetricName, "metric-c"), time.Now().Add(-1*time.Hour))) 653 654 // Append a sample within the retention period and flush all tenants. 655 // This will lead deletion of blocks that fall out of the retention period. 656 testutil.Ok(t, appendSampleWithLabels(m, "tenant-b", labels.FromStrings(labels.MetricName, "metric-d"), time.Now())) 657 testutil.Ok(t, m.Flush()) 658 } 659 660 func appendSample(m *MultiTSDB, tenant string, timestamp time.Time) error { 661 return appendSampleWithLabels(m, tenant, labels.FromStrings("foo", "bar"), timestamp) 662 } 663 664 func appendSampleWithLabels(m *MultiTSDB, tenant string, lbls labels.Labels, timestamp time.Time) error { 665 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 666 defer cancel() 667 668 app, err := m.TenantAppendable(tenant) 669 if err != nil { 670 return err 671 } 672 673 var a storage.Appender 674 if err := runutil.Retry(1*time.Second, ctx.Done(), func() error { 675 a, err = app.Appender(ctx) 676 return err 677 }); err != nil { 678 return err 679 } 680 681 _, err = a.Append(0, lbls, timestamp.UnixMilli(), 10) 682 if err != nil { 683 return err 684 } 685 686 return a.Commit() 687 } 688 689 func queryLabelValues(ctx context.Context, m *MultiTSDB) error { 690 proxy := store.NewProxyStore(nil, nil, func() []store.Client { 691 clients := m.TSDBLocalClients() 692 if len(clients) > 0 { 693 clients[0] = &slowClient{clients[0]} 694 } 695 return clients 696 }, component.Store, nil, 1*time.Minute, store.LazyRetrieval) 697 698 req := &storepb.LabelValuesRequest{ 699 Label: labels.MetricName, 700 Start: math.MinInt64, 701 End: math.MaxInt64, 702 } 703 _, err := proxy.LabelValues(ctx, req) 704 if err != nil { 705 return err 706 } 707 return nil 708 } 709 710 func BenchmarkMultiTSDB(b *testing.B) { 711 dir := b.TempDir() 712 713 m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), &tsdb.Options{ 714 MinBlockDuration: (2 * time.Hour).Milliseconds(), 715 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 716 RetentionDuration: (6 * time.Hour).Milliseconds(), 717 NoLockfile: true, 718 }, labels.FromStrings("replica", "test"), 719 "tenant_id", 720 nil, 721 false, 722 metadata.NoneFunc, 723 ) 724 defer func() { testutil.Ok(b, m.Close()) }() 725 726 testutil.Ok(b, m.Flush()) 727 testutil.Ok(b, m.Open()) 728 729 app, err := m.TenantAppendable("foo") 730 testutil.Ok(b, err) 731 732 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 733 defer cancel() 734 735 var a storage.Appender 736 testutil.Ok(b, runutil.Retry(1*time.Second, ctx.Done(), func() error { 737 a, err = app.Appender(context.Background()) 738 return err 739 })) 740 741 l := labels.FromStrings("a", "1", "b", "2") 742 743 b.ReportAllocs() 744 b.ResetTimer() 745 746 for i := 0; i < b.N; i++ { 747 _, _ = a.Append(0, l, int64(i), float64(i)) 748 } 749 }