github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/storage_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package m3 22 23 import ( 24 "context" 25 "fmt" 26 "math" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/m3db/m3/src/dbnode/client" 32 "github.com/m3db/m3/src/dbnode/encoding" 33 "github.com/m3db/m3/src/dbnode/storage/index" 34 "github.com/m3db/m3/src/query/block" 35 "github.com/m3db/m3/src/query/generated/proto/prompb" 36 "github.com/m3db/m3/src/query/models" 37 "github.com/m3db/m3/src/query/storage" 38 "github.com/m3db/m3/src/query/storage/m3/consolidators" 39 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 40 "github.com/m3db/m3/src/query/test/seriesiter" 41 "github.com/m3db/m3/src/query/ts" 42 "github.com/m3db/m3/src/x/ident" 43 "github.com/m3db/m3/src/x/instrument" 44 "github.com/m3db/m3/src/x/sync" 45 bytetest "github.com/m3db/m3/src/x/test" 46 xtest "github.com/m3db/m3/src/x/test" 47 xtime "github.com/m3db/m3/src/x/time" 48 49 "github.com/golang/mock/gomock" 50 "github.com/stretchr/testify/assert" 51 "github.com/stretchr/testify/require" 52 ) 53 54 const ( 55 test1MonthRetention = 30 * 24 * time.Hour 56 test3MonthRetention = 90 * 24 * time.Hour 57 test6MonthRetention = 180 * 24 * time.Hour 58 test1YearRetention = 365 * 24 * time.Hour 59 testLongestRetention = test1YearRetention 60 ) 61 62 var testFetchResponseMetadata = client.FetchResponseMetadata{Exhaustive: true} 63 64 type testSessions struct { 65 unaggregated1MonthRetention *client.MockSession 66 aggregated1MonthRetention1MinuteResolution *client.MockSession 67 aggregated3MonthRetention5MinuteResolution *client.MockSession 68 aggregatedPartial6MonthRetention1MinuteResolution *client.MockSession 69 aggregated1YearRetention10MinuteResolution *client.MockSession 70 } 71 72 func (s testSessions) forEach(fn func(session *client.MockSession)) { 73 for _, session := range []*client.MockSession{ 74 s.unaggregated1MonthRetention, 75 s.aggregated1MonthRetention1MinuteResolution, 76 s.aggregated3MonthRetention5MinuteResolution, 77 s.aggregatedPartial6MonthRetention1MinuteResolution, 78 s.aggregated1YearRetention10MinuteResolution, 79 } { 80 fn(session) 81 } 82 } 83 84 func setup( 85 t *testing.T, 86 ctrl *gomock.Controller, 87 ) (storage.Storage, testSessions) { 88 unaggregated1MonthRetention := client.NewMockSession(ctrl) 89 aggregated1MonthRetention1MinuteResolution := client.NewMockSession(ctrl) 90 aggregated3MonthRetention5MinuteResolution := client.NewMockSession(ctrl) 91 aggregatedPartial6MonthRetention1MinuteResolution := client.NewMockSession(ctrl) 92 aggregated1YearRetention10MinuteResolution := client.NewMockSession(ctrl) 93 clusters, err := NewClusters(UnaggregatedClusterNamespaceDefinition{ 94 NamespaceID: ident.StringID("metrics_unaggregated"), 95 Session: unaggregated1MonthRetention, 96 Retention: test1MonthRetention, 97 }, AggregatedClusterNamespaceDefinition{ 98 NamespaceID: ident.StringID("metrics_aggregated_1m:30d"), 99 Session: aggregated1MonthRetention1MinuteResolution, 100 Retention: test1MonthRetention, 101 Resolution: time.Minute, 102 }, AggregatedClusterNamespaceDefinition{ 103 NamespaceID: ident.StringID("metrics_aggregated_5m:90d"), 104 Session: aggregated3MonthRetention5MinuteResolution, 105 Retention: test3MonthRetention, 106 Resolution: 5 * time.Minute, 107 }, AggregatedClusterNamespaceDefinition{ 108 NamespaceID: ident.StringID("metrics_aggregated_partial_1m:180d"), 109 Session: aggregatedPartial6MonthRetention1MinuteResolution, 110 Retention: test6MonthRetention, 111 Resolution: 1 * time.Minute, 112 Downsample: &ClusterNamespaceDownsampleOptions{All: false}, 113 }, AggregatedClusterNamespaceDefinition{ 114 NamespaceID: ident.StringID("metrics_aggregated_10m:365d"), 115 Session: aggregated1YearRetention10MinuteResolution, 116 Retention: test1YearRetention, 117 Resolution: 10 * time.Minute, 118 }) 119 require.NoError(t, err) 120 return newTestStorage(t, clusters), testSessions{ 121 unaggregated1MonthRetention: unaggregated1MonthRetention, 122 aggregated1MonthRetention1MinuteResolution: aggregated1MonthRetention1MinuteResolution, 123 aggregated3MonthRetention5MinuteResolution: aggregated3MonthRetention5MinuteResolution, 124 aggregatedPartial6MonthRetention1MinuteResolution: aggregatedPartial6MonthRetention1MinuteResolution, 125 aggregated1YearRetention10MinuteResolution: aggregated1YearRetention10MinuteResolution, 126 } 127 } 128 129 func newTestStorage(t *testing.T, clusters Clusters) storage.Storage { 130 writePool, err := sync.NewPooledWorkerPool(10, 131 sync.NewPooledWorkerPoolOptions()) 132 require.NoError(t, err) 133 writePool.Init() 134 tagOpts := models.NewTagOptions().SetMetricName([]byte("name")) 135 opts := NewOptions(encoding.NewOptions()). 136 SetWriteWorkerPool(writePool). 137 SetLookbackDuration(time.Minute). 138 SetTagOptions(tagOpts) 139 storage, err := NewStorage(clusters, opts, instrument.NewTestOptions(t)) 140 require.NoError(t, err) 141 return storage 142 } 143 144 func newFetchReq() *storage.FetchQuery { 145 matchers := models.Matchers{ 146 { 147 Type: models.MatchEqual, 148 Name: []byte("foo"), 149 Value: []byte("bar"), 150 }, 151 { 152 Type: models.MatchEqual, 153 Name: []byte("biz"), 154 Value: []byte("baz"), 155 }, 156 } 157 return &storage.FetchQuery{ 158 TagMatchers: matchers, 159 Start: time.Now().Add(-10 * time.Minute), 160 End: time.Now(), 161 } 162 } 163 164 func newWriteQuery(t *testing.T) *storage.WriteQuery { 165 tags := models.EmptyTags().AddTags([]models.Tag{ 166 {Name: []byte("foo"), Value: []byte("bar")}, 167 {Name: []byte("biz"), Value: []byte("baz")}, 168 }) 169 170 q, err := storage.NewWriteQuery(storage.WriteQueryOptions{ 171 Tags: tags, 172 Unit: xtime.Millisecond, 173 Datapoints: ts.Datapoints{ 174 { 175 Timestamp: xtime.Now(), 176 Value: 1.0, 177 }, 178 { 179 Timestamp: xtime.Now().Add(-10 * time.Second), 180 Value: 2.0, 181 }, 182 }, 183 Attributes: storagemetadata.Attributes{ 184 MetricsType: storagemetadata.UnaggregatedMetricsType, 185 }, 186 }) 187 require.NoError(t, err) 188 189 return q 190 } 191 192 func setupLocalWrite(t *testing.T, ctrl *gomock.Controller) storage.Storage { 193 store, sessions := setup(t, ctrl) 194 session := sessions.unaggregated1MonthRetention 195 session.EXPECT().WriteTagged(gomock.Any(), gomock.Any(), gomock.Any(), 196 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 197 return store 198 } 199 200 func TestQueryStorageMetadataAttributes(t *testing.T) { 201 ctrl := xtest.NewController(t) 202 defer ctrl.Finish() 203 store, _ := setup(t, ctrl) 204 205 unaggAttrs, err := store.QueryStorageMetadataAttributes( 206 context.Background(), 207 time.Now().Add(-10*time.Minute), 208 time.Now(), 209 buildFetchOpts(), 210 ) 211 require.NoError(t, err) 212 require.Equal(t, []storagemetadata.Attributes{ 213 { 214 MetricsType: storagemetadata.UnaggregatedMetricsType, 215 Retention: test1MonthRetention, 216 }, 217 }, unaggAttrs) 218 219 aggAttrs, err := store.QueryStorageMetadataAttributes( 220 context.Background(), 221 time.Now().Add(-120*24*time.Hour), 222 time.Now(), 223 buildFetchOpts(), 224 ) 225 require.NoError(t, err) 226 require.Equal(t, []storagemetadata.Attributes{ 227 { 228 MetricsType: storagemetadata.AggregatedMetricsType, 229 Retention: test1YearRetention, 230 Resolution: 10 * time.Minute, 231 }, 232 { 233 MetricsType: storagemetadata.AggregatedMetricsType, 234 Retention: test6MonthRetention, 235 Resolution: 1 * time.Minute, 236 }, 237 }, aggAttrs) 238 } 239 240 func TestLocalWriteEmpty(t *testing.T) { 241 ctrl := xtest.NewController(t) 242 defer ctrl.Finish() 243 store := setupLocalWrite(t, ctrl) 244 err := store.Write(context.TODO(), nil) 245 assert.Error(t, err) 246 } 247 248 func TestLocalWriteSuccess(t *testing.T) { 249 ctrl := xtest.NewController(t) 250 defer ctrl.Finish() 251 store := setupLocalWrite(t, ctrl) 252 writeQuery := newWriteQuery(t) 253 err := store.Write(context.TODO(), writeQuery) 254 assert.NoError(t, err) 255 assert.NoError(t, store.Close()) 256 } 257 258 func TestLocalWriteAggregatedNoClusterNamespaceError(t *testing.T) { 259 ctrl := xtest.NewController(t) 260 defer ctrl.Finish() 261 store, _ := setup(t, ctrl) 262 263 opts := newWriteQuery(t).Options() 264 265 // Use unsupported retention/resolution 266 opts.Attributes = storagemetadata.Attributes{ 267 MetricsType: storagemetadata.AggregatedMetricsType, 268 Retention: 1234, 269 Resolution: 5678, 270 } 271 272 writeQuery, err := storage.NewWriteQuery(opts) 273 require.NoError(t, err) 274 275 err = store.Write(context.TODO(), writeQuery) 276 assert.Error(t, err) 277 assert.True(t, strings.Contains(err.Error(), "no configured cluster namespace"), 278 fmt.Sprintf("unexpected error string: %v", err.Error())) 279 } 280 281 func TestLocalWriteUnaggregatedNamespaceUninitializedError(t *testing.T) { 282 t.Parallel() 283 284 ctrl := xtest.NewController(t) 285 defer ctrl.Finish() 286 // We setup an empty dynamic cluster, which will by default 287 // have an uninitialized unaggregated namespace. 288 store := newTestStorage(t, &dynamicCluster{}) 289 290 opts := newWriteQuery(t).Options() 291 292 writeQuery, err := storage.NewWriteQuery(opts) 293 require.NoError(t, err) 294 295 err = store.Write(context.TODO(), writeQuery) 296 assert.Error(t, err) 297 assert.True(t, strings.Contains(err.Error(), "unaggregated namespace is not yet initialized"), 298 fmt.Sprintf("unexpected error string: %v", err.Error())) 299 } 300 301 func TestWriteToReadOnlyNamespaceFail(t *testing.T) { 302 ctrl := xtest.NewController(t) 303 defer ctrl.Finish() 304 305 clusters, err := NewClusters( 306 UnaggregatedClusterNamespaceDefinition{ 307 NamespaceID: ident.StringID("unaggregated"), 308 Session: client.NewMockSession(ctrl), 309 Retention: time.Hour, 310 }, 311 AggregatedClusterNamespaceDefinition{ 312 NamespaceID: ident.StringID("aggregated_readonly"), 313 Session: client.NewMockSession(ctrl), 314 Retention: 24 * time.Hour, 315 Resolution: time.Minute, 316 ReadOnly: true, 317 }, 318 ) 319 require.NoError(t, err) 320 321 store := newTestStorage(t, clusters) 322 323 opts := newWriteQuery(t).Options() 324 325 opts.Attributes = storagemetadata.Attributes{ 326 MetricsType: storagemetadata.AggregatedMetricsType, 327 Retention: 24 * time.Hour, 328 Resolution: time.Minute, 329 } 330 331 writeQuery, err := storage.NewWriteQuery(opts) 332 require.NoError(t, err) 333 334 err = store.Write(context.TODO(), writeQuery) 335 assert.Error(t, err) 336 assert.True(t, 337 strings.Contains(err.Error(), "cannot write to read only namespace aggregated_readonly"), 338 fmt.Sprintf("unexpected error string: %v", err.Error())) 339 } 340 341 func TestLocalWriteAggregatedInvalidMetricsTypeError(t *testing.T) { 342 ctrl := xtest.NewController(t) 343 defer ctrl.Finish() 344 store, _ := setup(t, ctrl) 345 346 opts := newWriteQuery(t).Options() 347 348 // Use unsupported retention/resolution 349 opts.Attributes = storagemetadata.Attributes{ 350 MetricsType: storagemetadata.MetricsType(math.MaxUint64), 351 Retention: 30 * 24 * time.Hour, 352 } 353 354 writeQuery, err := storage.NewWriteQuery(opts) 355 require.NoError(t, err) 356 357 err = store.Write(context.TODO(), writeQuery) 358 assert.Error(t, err) 359 assert.True(t, strings.Contains(err.Error(), "invalid write request"), 360 fmt.Sprintf("unexpected error string: %v", err.Error())) 361 } 362 363 func TestLocalWriteAggregatedSuccess(t *testing.T) { 364 ctrl := xtest.NewController(t) 365 defer ctrl.Finish() 366 store, sessions := setup(t, ctrl) 367 368 opts := newWriteQuery(t).Options() 369 370 // Use unsupported retention/resolution 371 opts.Attributes = storagemetadata.Attributes{ 372 MetricsType: storagemetadata.AggregatedMetricsType, 373 Retention: 30 * 24 * time.Hour, 374 Resolution: time.Minute, 375 } 376 377 writeQuery, err := storage.NewWriteQuery(opts) 378 require.NoError(t, err) 379 380 session := sessions.aggregated1MonthRetention1MinuteResolution 381 session.EXPECT().WriteTagged(gomock.Any(), gomock.Any(), gomock.Any(), 382 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(len(writeQuery.Datapoints())) 383 384 err = store.Write(context.TODO(), writeQuery) 385 assert.NoError(t, err) 386 assert.NoError(t, store.Close()) 387 } 388 389 func TestLocalRead(t *testing.T) { 390 ctrl := xtest.NewController(t) 391 defer ctrl.Finish() 392 393 store, sessions := setup(t, ctrl) 394 testTags := seriesiter.GenerateTag() 395 396 session := sessions.unaggregated1MonthRetention 397 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 398 Return(seriesiter.NewMockSeriesIters(ctrl, testTags, 1, 2), 399 testFetchResponseMetadata, nil) 400 401 searchReq := newFetchReq() 402 results, err := store.FetchProm(context.TODO(), searchReq, buildFetchOpts()) 403 require.NoError(t, err) 404 assertFetchResult(t, results, testTags) 405 } 406 407 func TestLocalReadExceedsRetention(t *testing.T) { 408 ctrl := xtest.NewController(t) 409 defer ctrl.Finish() 410 store, sessions := setup(t, ctrl) 411 testTag := seriesiter.GenerateTag() 412 413 session := sessions.aggregated1YearRetention10MinuteResolution 414 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 415 Return(seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), 416 testFetchResponseMetadata, nil) 417 418 searchReq := newFetchReq() 419 searchReq.Start = time.Now().Add(-2 * testLongestRetention) 420 searchReq.End = time.Now() 421 results, err := store.FetchProm(context.TODO(), searchReq, buildFetchOpts()) 422 require.NoError(t, err) 423 assertFetchResult(t, results, testTag) 424 } 425 426 func TestFetchPromWithNamespaceStitching(t *testing.T) { 427 ctrl := xtest.NewController(t) 428 defer ctrl.Finish() 429 430 var ( 431 end = xtime.Now().Truncate(time.Hour) 432 start = end.Add(-48 * time.Hour) 433 434 testTag = seriesiter.GenerateTag() 435 436 unaggSession = client.NewMockSession(ctrl) 437 aggSession = client.NewMockSession(ctrl) 438 439 unaggNamespaceID = ident.StringID("unaggregated") 440 aggNamespaceID = ident.StringID("aggregated") 441 442 unaggQueryOpts, aggQueryOpts index.QueryOptions 443 ) 444 445 clusters, err := NewClusters( 446 UnaggregatedClusterNamespaceDefinition{ 447 NamespaceID: unaggNamespaceID, 448 Session: unaggSession, 449 Retention: 24 * time.Hour, 450 }, 451 AggregatedClusterNamespaceDefinition{ 452 NamespaceID: aggNamespaceID, 453 Session: aggSession, 454 Retention: 96 * time.Hour, 455 Resolution: time.Minute, 456 DataLatency: 10 * time.Hour, 457 }, 458 ) 459 require.NoError(t, err) 460 461 store := newTestStorage(t, clusters) 462 463 unaggSession.EXPECT().FetchTagged(gomock.Any(), unaggNamespaceID, gomock.Any(), gomock.Any()). 464 DoAndReturn(func( 465 _ context.Context, 466 _ ident.ID, 467 _ index.Query, 468 opts index.QueryOptions, 469 ) (encoding.SeriesIterators, client.FetchResponseMetadata, error) { 470 unaggQueryOpts = opts 471 return seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), testFetchResponseMetadata, nil 472 }) 473 474 aggSession.EXPECT().FetchTagged(gomock.Any(), aggNamespaceID, gomock.Any(), gomock.Any()). 475 DoAndReturn(func( 476 _ context.Context, 477 _ ident.ID, 478 _ index.Query, 479 opts index.QueryOptions, 480 ) (encoding.SeriesIterators, client.FetchResponseMetadata, error) { 481 aggQueryOpts = opts 482 return seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), testFetchResponseMetadata, nil 483 }) 484 485 var ( 486 fetchOpts = buildFetchOpts() 487 req = newFetchReq() 488 ) 489 490 req.Start = start.ToTime() 491 req.End = end.ToTime() 492 493 results, err := store.FetchProm(context.TODO(), req, fetchOpts) 494 require.NoError(t, err) 495 496 assert.Equal(t, start, aggQueryOpts.StartInclusive) 497 assert.Equal(t, aggQueryOpts.EndExclusive, unaggQueryOpts.StartInclusive) // stitching point 498 assert.Equal(t, end, unaggQueryOpts.EndExclusive) 499 500 assertFetchResult(t, results, testTag) 501 } 502 503 // TestLocalWriteWithExpiredContext ensures that writes are at least attempted 504 // even with an expired context, this is so that data is not lost even if 505 // the original writer has already disconnected. 506 func TestLocalWriteWithExpiredContext(t *testing.T) { 507 ctrl := xtest.NewController(t) 508 defer ctrl.Finish() 509 store := setupLocalWrite(t, ctrl) 510 writeQuery := newWriteQuery(t) 511 512 past := time.Now().Add(-time.Minute) 513 514 ctx, cancel := context.WithDeadline(context.Background(), past) 515 defer cancel() 516 517 // Ensure expired. 518 var expired bool 519 select { 520 case <-ctx.Done(): 521 expired = true 522 default: 523 } 524 require.True(t, expired, "context expected to be expired") 525 526 err := store.Write(ctx, writeQuery) 527 assert.NoError(t, err) 528 assert.NoError(t, store.Close()) 529 } 530 531 // TestLocalWritesWithExpiredContext ensures that writes are at least attempted 532 // even with an expired context, this is so that data is not lost even if 533 // the original writer has already disconnected. 534 func TestLocalWritesWithExpiredContext(t *testing.T) { 535 ctrl := xtest.NewController(t) 536 defer ctrl.Finish() 537 store := setupLocalWrite(t, ctrl) 538 writeQueryOpts := newWriteQuery(t).Options() 539 writeQueryOpts.Datapoints = ts.Datapoints{ 540 ts.Datapoint{ 541 Timestamp: xtime.Now(), 542 Value: 42, 543 }, 544 ts.Datapoint{ 545 Timestamp: xtime.Now(), 546 Value: 84, 547 }, 548 } 549 writeQuery, err := storage.NewWriteQuery(writeQueryOpts) 550 require.NoError(t, err) 551 552 past := time.Now().Add(-time.Minute) 553 554 ctx, cancel := context.WithDeadline(context.Background(), past) 555 defer cancel() 556 557 // Ensure expired. 558 var expired bool 559 select { 560 case <-ctx.Done(): 561 expired = true 562 default: 563 } 564 require.True(t, expired, "context expected to be expired") 565 566 err = store.Write(ctx, writeQuery) 567 assert.NoError(t, err) 568 assert.NoError(t, store.Close()) 569 } 570 571 func buildFetchOpts() *storage.FetchOptions { 572 opts := storage.NewFetchOptions() 573 opts.SeriesLimit = 100 574 opts.MaxMetricMetadataStats = 1 575 return opts 576 } 577 578 func TestLocalReadExceedsUnaggregatedRetentionWithinAggregatedRetention(t *testing.T) { 579 ctrl := xtest.NewController(t) 580 defer ctrl.Finish() 581 store, sessions := setup(t, ctrl) 582 testTag := seriesiter.GenerateTag() 583 584 session := sessions.aggregated3MonthRetention5MinuteResolution 585 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 586 Return(seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), 587 testFetchResponseMetadata, nil) 588 589 session = sessions.aggregatedPartial6MonthRetention1MinuteResolution 590 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 591 Return(encoding.EmptySeriesIterators, 592 testFetchResponseMetadata, nil) 593 594 // Test searching between 1month and 3 months (so 2 months) to hit multiple aggregated 595 // namespaces that we need to choose from 596 searchReq := newFetchReq() 597 searchReq.Start = time.Now().Add(-2 * test1MonthRetention) 598 searchReq.End = time.Now() 599 results, err := store.FetchProm(context.TODO(), searchReq, buildFetchOpts()) 600 require.NoError(t, err) 601 assertFetchResult(t, results, testTag) 602 } 603 604 func TestLocalReadExceedsAggregatedButNotUnaggregatedAndPartialAggregated(t *testing.T) { 605 ctrl := xtest.NewController(t) 606 defer ctrl.Finish() 607 608 unaggregated1MonthRetention := client.NewMockSession(ctrl) 609 aggregatedPartial6MonthRetention1MinuteResolution := client.NewMockSession(ctrl) 610 611 clusters, err := NewClusters(UnaggregatedClusterNamespaceDefinition{ 612 NamespaceID: ident.StringID("metrics_unaggregated"), 613 Session: unaggregated1MonthRetention, 614 Retention: test1MonthRetention, 615 }, AggregatedClusterNamespaceDefinition{ 616 NamespaceID: ident.StringID("metrics_aggregated_1m:180d"), 617 Session: aggregatedPartial6MonthRetention1MinuteResolution, 618 Retention: test6MonthRetention, 619 Resolution: time.Minute, 620 Downsample: &ClusterNamespaceDownsampleOptions{All: false}, 621 }) 622 require.NoError(t, err) 623 624 store := newTestStorage(t, clusters) 625 626 testTag := seriesiter.GenerateTag() 627 628 session := unaggregated1MonthRetention 629 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 630 Return(seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), 631 testFetchResponseMetadata, nil) 632 633 session = aggregatedPartial6MonthRetention1MinuteResolution 634 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 635 Return(encoding.EmptySeriesIterators, 636 testFetchResponseMetadata, nil) 637 638 // Test searching past unaggregated namespace and verify that we fan out to both 639 // the unaggregated namespaces and the partial aggregated namespace 640 searchReq := newFetchReq() 641 searchReq.Start = time.Now().Add(-2 * test1MonthRetention) 642 searchReq.End = time.Now() 643 results, err := store.FetchProm(context.TODO(), searchReq, buildFetchOpts()) 644 require.NoError(t, err) 645 assertFetchResult(t, results, testTag) 646 } 647 648 func TestLocalReadExceedsAggregatedAndPartialAggregated(t *testing.T) { 649 ctrl := xtest.NewController(t) 650 defer ctrl.Finish() 651 652 unaggregated1MonthRetention := client.NewMockSession(ctrl) 653 aggregated3MonthRetention5MinuteResolution := client.NewMockSession(ctrl) 654 aggregatedPartial6MonthRetention1MinuteResolution := client.NewMockSession(ctrl) 655 656 clusters, err := NewClusters(UnaggregatedClusterNamespaceDefinition{ 657 NamespaceID: ident.StringID("metrics_unaggregated"), 658 Session: unaggregated1MonthRetention, 659 Retention: test1MonthRetention, 660 }, AggregatedClusterNamespaceDefinition{ 661 NamespaceID: ident.StringID("metrics_aggregated_5m:90d"), 662 Session: aggregated3MonthRetention5MinuteResolution, 663 Retention: test3MonthRetention, 664 Resolution: 5 * time.Minute, 665 }, AggregatedClusterNamespaceDefinition{ 666 NamespaceID: ident.StringID("metrics_aggregated_1m:180d"), 667 Session: aggregatedPartial6MonthRetention1MinuteResolution, 668 Retention: test6MonthRetention, 669 Resolution: time.Minute, 670 Downsample: &ClusterNamespaceDownsampleOptions{All: false}, 671 }) 672 require.NoError(t, err) 673 674 store := newTestStorage(t, clusters) 675 676 testTag := seriesiter.GenerateTag() 677 678 session := aggregated3MonthRetention5MinuteResolution 679 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 680 Return(seriesiter.NewMockSeriesIters(ctrl, testTag, 1, 2), 681 testFetchResponseMetadata, nil) 682 683 session = aggregatedPartial6MonthRetention1MinuteResolution 684 session.EXPECT().FetchTagged(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 685 Return(encoding.EmptySeriesIterators, 686 testFetchResponseMetadata, nil) 687 688 // Test searching past aggregated and partially aggregated namespace, fan out to both 689 searchReq := newFetchReq() 690 searchReq.Start = time.Now().Add(-2 * test6MonthRetention) 691 searchReq.End = time.Now() 692 results, err := store.FetchProm(context.TODO(), searchReq, buildFetchOpts()) 693 require.NoError(t, err) 694 assertFetchResult(t, results, testTag) 695 } 696 697 func assertFetchResult(t *testing.T, results storage.PromResult, testTag ident.Tag) { 698 require.NotNil(t, results.PromResult) 699 series := results.PromResult.GetTimeseries() 700 meta := results.Metadata 701 require.Equal(t, 1, len(series)) 702 labels := series[0].GetLabels() 703 require.Equal(t, 1, len(labels)) 704 l := labels[0] 705 assert.Equal(t, testTag.Name.String(), string(l.GetName())) 706 assert.Equal(t, testTag.Value.String(), string(l.GetValue())) 707 merged := meta.MetadataByNameMerged() 708 assert.Equal(t, 1, meta.FetchedSeriesCount) 709 assert.Equal(t, block.ResultMetricMetadata{Unaggregated: 1, WithSamples: 1}, merged) 710 } 711 712 func TestLocalSearchError(t *testing.T) { 713 ctrl := xtest.NewController(t) 714 defer ctrl.Finish() 715 store, sessions := setup(t, ctrl) 716 717 // Query is just for last 10mins to only expect unaggregated namespace. 718 for _, session := range []*client.MockSession{ 719 sessions.unaggregated1MonthRetention, 720 } { 721 session.EXPECT().FetchTaggedIDs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 722 Return(nil, client.FetchResponseMetadata{Exhaustive: false}, fmt.Errorf("an error")) 723 session.EXPECT().IteratorPools(). 724 Return(nil, nil).AnyTimes() 725 } 726 727 // Issue query for last 10mins. 728 searchReq := newFetchReq() 729 searchReq.Start = time.Now().Add(-10 * time.Minute) 730 searchReq.End = time.Now() 731 _, err := store.SearchSeries(context.TODO(), searchReq, buildFetchOpts()) 732 assert.Error(t, err) 733 } 734 735 func TestLocalSearchSuccess(t *testing.T) { 736 ctrl := xtest.NewController(t) 737 defer ctrl.Finish() 738 store, sessions := setup(t, ctrl) 739 740 type testFetchTaggedID struct { 741 id string 742 namespace string 743 tagName string 744 tagValue string 745 } 746 747 fetches := []testFetchTaggedID{ 748 { 749 id: "foo", 750 namespace: "metrics_unaggregated", 751 tagName: "qux", 752 tagValue: "qaz", 753 }, 754 } 755 756 sessions.forEach(func(session *client.MockSession) { 757 var f testFetchTaggedID 758 switch { 759 case session == sessions.unaggregated1MonthRetention: 760 f = fetches[0] 761 default: 762 // Not expecting from other (partial) namespaces 763 return 764 } 765 iter := client.NewMockTaggedIDsIterator(ctrl) 766 gomock.InOrder( 767 iter.EXPECT().Next().Return(true), 768 iter.EXPECT().Current().Return( 769 ident.StringID(f.namespace), 770 ident.StringID(f.id), 771 ident.NewTagsIterator(ident.NewTags( 772 ident.Tag{ 773 Name: ident.StringID(f.tagName), 774 Value: ident.StringID(f.tagValue), 775 })), 776 ), 777 iter.EXPECT().Next().Return(false), 778 iter.EXPECT().Err().Return(nil), 779 iter.EXPECT().Finalize(), 780 ) 781 782 session.EXPECT().FetchTaggedIDs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 783 Return(iter, testFetchResponseMetadata, nil) 784 785 session.EXPECT().IteratorPools(). 786 Return(nil, nil).AnyTimes() 787 }) 788 searchReq := newFetchReq() 789 searchReq.Start = time.Now().Add(-10 * time.Minute) 790 searchReq.End = time.Now() 791 result, err := store.SearchSeries(context.TODO(), searchReq, buildFetchOpts()) 792 require.NoError(t, err) 793 794 require.Equal(t, len(fetches), len(result.Metrics)) 795 796 expected := make(map[string]testFetchTaggedID) 797 for _, f := range fetches { 798 expected[f.id] = f 799 } 800 801 actual := make(map[string]models.Metric) 802 for _, m := range result.Metrics { 803 actual[string(m.ID)] = m 804 } 805 806 for id, actual := range actual { 807 expected, ok := expected[id] 808 require.True(t, ok) 809 810 assert.Equal(t, []byte(expected.id), actual.ID) 811 assert.Equal(t, []models.Tag{{ 812 Name: []byte(expected.tagName), Value: []byte(expected.tagValue), 813 }}, actual.Tags.Tags) 814 } 815 } 816 817 func newCompleteTagsReq() *storage.CompleteTagsQuery { 818 matchers := models.Matchers{ 819 { 820 Type: models.MatchEqual, 821 Name: []byte("qux"), 822 Value: []byte(".*"), 823 }, 824 } 825 826 return &storage.CompleteTagsQuery{ 827 CompleteNameOnly: false, 828 FilterNameTags: [][]byte{[]byte("qux")}, 829 TagMatchers: matchers, 830 } 831 } 832 833 func TestLocalCompleteTagsSuccess(t *testing.T) { 834 ctrl := xtest.NewController(t) 835 defer ctrl.Finish() 836 store, sessions := setup(t, ctrl) 837 838 type testFetchTaggedID struct { 839 tagName string 840 tagValue string 841 } 842 843 fetches := []testFetchTaggedID{ 844 { 845 tagName: "qux", 846 tagValue: "qaz", 847 }, 848 { 849 tagName: "aba", 850 tagValue: "quz", 851 }, 852 { 853 tagName: "qam", 854 tagValue: "qak", 855 }, 856 { 857 tagName: "qux", 858 tagValue: "qaz2", 859 }, 860 } 861 862 sessions.forEach(func(session *client.MockSession) { 863 var f []testFetchTaggedID 864 switch { 865 case session == sessions.unaggregated1MonthRetention: 866 f = fetches 867 default: 868 // Not expecting from other (partial) namespaces 869 return 870 } 871 872 iter := client.NewMockAggregatedTagsIterator(ctrl) 873 874 var calls []*gomock.Call 875 calls = append(calls, []*gomock.Call{ 876 iter.EXPECT().Remaining().Return(len(f)), 877 }...) 878 for _, elem := range f { 879 calls = append(calls, []*gomock.Call{ 880 iter.EXPECT().Next().Return(true), 881 iter.EXPECT().Current().Return( 882 ident.StringID(elem.tagName), 883 ident.NewIDsIterator(ident.StringID(elem.tagValue)), 884 ), 885 }...) 886 } 887 calls = append(calls, []*gomock.Call{ 888 iter.EXPECT().Next().Return(false), 889 iter.EXPECT().Err().Return(nil), 890 iter.EXPECT().Finalize(), 891 }...) 892 893 gomock.InOrder(calls...) 894 895 session.EXPECT().Aggregate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 896 Return(iter, testFetchResponseMetadata, nil) 897 }) 898 899 req := newCompleteTagsReq() 900 req.Start = xtime.Now().Add(-10 * time.Minute) 901 req.End = xtime.Now() 902 result, err := store.CompleteTags(context.TODO(), req, buildFetchOpts()) 903 require.NoError(t, err) 904 905 require.False(t, result.CompleteNameOnly) 906 require.Equal(t, 3, len(result.CompletedTags)) 907 // NB: expected will be sorted alphabetically 908 expected := []consolidators.CompletedTag{ 909 { 910 Name: []byte("aba"), 911 Values: [][]byte{[]byte("quz")}, 912 }, 913 { 914 Name: []byte("qam"), 915 Values: [][]byte{[]byte("qak")}, 916 }, 917 { 918 Name: []byte("qux"), 919 Values: [][]byte{[]byte("qaz"), []byte("qaz2")}, 920 }, 921 } 922 923 assert.Equal(t, expected, result.CompletedTags) 924 } 925 926 func TestLocalCompleteTagsSuccessFinalize(t *testing.T) { 927 ctrl := xtest.NewController(t) 928 defer ctrl.Finish() 929 930 unagg := client.NewMockSession(ctrl) 931 clusters, err := NewClusters(UnaggregatedClusterNamespaceDefinition{ 932 NamespaceID: ident.StringID("metrics_unaggregated"), 933 Session: unagg, 934 Retention: test1MonthRetention, 935 }) 936 937 require.NoError(t, err) 938 store := newTestStorage(t, clusters) 939 940 name, value := ident.StringID("name"), ident.StringID("value") 941 iter := newAggregatedTagsIter(ctrl, name, value) 942 943 unagg.EXPECT().Aggregate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 944 Return(iter, testFetchResponseMetadata, nil) 945 946 req := newCompleteTagsReq() 947 result, err := store.CompleteTags(context.TODO(), req, buildFetchOpts()) 948 require.NoError(t, err) 949 950 require.False(t, result.CompleteNameOnly) 951 require.Equal(t, 1, len(result.CompletedTags)) 952 // NB: expected will be sorted alphabetically 953 expected := []consolidators.CompletedTag{ 954 { 955 Name: []byte("name"), 956 Values: [][]byte{[]byte("value")}, 957 }, 958 } 959 960 require.Equal(t, expected, result.CompletedTags) 961 962 // ensure that the tag names and values are not backed by the same data. 963 n, v := result.CompletedTags[0].Name, result.CompletedTags[0].Values[0] 964 assert.False(t, bytetest.ByteSlicesBackedBySameData(name.Bytes(), n)) 965 assert.False(t, bytetest.ByteSlicesBackedBySameData(value.Bytes(), v)) 966 } 967 968 func TestCompleteTagsWithNamespaceStitching(t *testing.T) { 969 ctrl := xtest.NewController(t) 970 defer ctrl.Finish() 971 972 var ( 973 end = xtime.Now().Truncate(time.Hour) 974 start = end.Add(-48 * time.Hour) 975 976 name = ident.StringID("name") 977 value = ident.StringID("value") 978 979 unaggSession = client.NewMockSession(ctrl) 980 aggSession = client.NewMockSession(ctrl) 981 982 unaggNamespaceID = ident.StringID("unaggregated") 983 aggNamespaceID = ident.StringID("aggregated") 984 985 unaggQueryOpts, aggQueryOpts index.AggregationOptions 986 ) 987 988 clusters, err := NewClusters( 989 UnaggregatedClusterNamespaceDefinition{ 990 NamespaceID: unaggNamespaceID, 991 Session: unaggSession, 992 Retention: 24 * time.Hour, 993 }, 994 AggregatedClusterNamespaceDefinition{ 995 NamespaceID: aggNamespaceID, 996 Session: aggSession, 997 Retention: 96 * time.Hour, 998 Resolution: time.Minute, 999 DataLatency: 10 * time.Hour, 1000 }, 1001 ) 1002 require.NoError(t, err) 1003 1004 store := newTestStorage(t, clusters) 1005 1006 unaggIter := newAggregatedTagsIter(ctrl, name, value) 1007 unaggSession.EXPECT().Aggregate(gomock.Any(), unaggNamespaceID, gomock.Any(), gomock.Any()). 1008 DoAndReturn(func( 1009 _ context.Context, 1010 _ ident.ID, 1011 _ index.Query, 1012 opts index.AggregationOptions, 1013 ) (client.AggregatedTagsIterator, client.FetchResponseMetadata, error) { 1014 unaggQueryOpts = opts 1015 return unaggIter, testFetchResponseMetadata, nil 1016 }) 1017 1018 aggIter := newAggregatedTagsIter(ctrl, name, value) 1019 aggSession.EXPECT().Aggregate(gomock.Any(), aggNamespaceID, gomock.Any(), gomock.Any()). 1020 DoAndReturn(func( 1021 _ context.Context, 1022 _ ident.ID, 1023 _ index.Query, 1024 opts index.AggregationOptions, 1025 ) (client.AggregatedTagsIterator, client.FetchResponseMetadata, error) { 1026 aggQueryOpts = opts 1027 return aggIter, testFetchResponseMetadata, nil 1028 }) 1029 1030 var ( 1031 fetchOpts = buildFetchOpts() 1032 req = newCompleteTagsReq() 1033 ) 1034 1035 req.Start = start 1036 req.End = end 1037 1038 result, err := store.CompleteTags(context.TODO(), req, fetchOpts) 1039 require.NoError(t, err) 1040 1041 assert.Equal(t, start, aggQueryOpts.StartInclusive) 1042 assert.Equal(t, aggQueryOpts.EndExclusive, unaggQueryOpts.StartInclusive) // stitching point 1043 assert.Equal(t, end, unaggQueryOpts.EndExclusive) 1044 1045 expected := []consolidators.CompletedTag{ 1046 { 1047 Name: []byte("name"), 1048 Values: [][]byte{[]byte("value")}, 1049 }, 1050 } 1051 assert.Equal(t, expected, result.CompletedTags) 1052 } 1053 1054 func TestInvalidBlockTypes(t *testing.T) { 1055 opts := NewOptions(encoding.NewOptions()) 1056 s, err := NewStorage(nil, opts, instrument.NewOptions()) 1057 require.NoError(t, err) 1058 1059 query := &storage.FetchQuery{} 1060 fetchOpts := &storage.FetchOptions{BlockType: models.TypeMultiBlock} 1061 defer instrument.SetShouldPanicEnvironmentVariable(true)() 1062 require.Panics(t, func() { _, _ = s.FetchBlocks(context.TODO(), query, fetchOpts) }) 1063 } 1064 1065 func newAggregatedTagsIter( 1066 ctrl *gomock.Controller, 1067 name, value ident.ID, 1068 ) client.AggregatedTagsIterator { 1069 iter := client.NewMockAggregatedTagsIterator(ctrl) 1070 1071 gomock.InOrder( 1072 iter.EXPECT().Remaining().Return(1), 1073 iter.EXPECT().Next().Return(true), 1074 iter.EXPECT().Current().Return( 1075 name, 1076 ident.NewIDsIterator(value), 1077 ), 1078 iter.EXPECT().Next().Return(false), 1079 iter.EXPECT().Err().Return(nil), 1080 iter.EXPECT().Finalize().Do(func() { 1081 name.Finalize() 1082 value.Finalize() 1083 }), 1084 ) 1085 1086 return iter 1087 } 1088 1089 func TestFindReservedLabel(t *testing.T) { 1090 nameLabel := []byte("__name__") 1091 rollupLabel := []byte("__rollup__") 1092 1093 // Empty 1094 labels := []prompb.Label{} 1095 assert.Nil(t, findReservedLabel(labels, nameLabel)) 1096 assert.Nil(t, findReservedLabel(labels, rollupLabel)) 1097 1098 // Single label, shorter than the reserved prefix 1099 labels = []prompb.Label{ 1100 {Name: []byte("_"), Value: []byte("1")}, 1101 } 1102 assert.Nil(t, findReservedLabel(labels, nameLabel)) 1103 assert.Nil(t, findReservedLabel(labels, rollupLabel)) 1104 1105 // Multiple labels, only one contains than the reserved prefix 1106 labels = []prompb.Label{ 1107 {Name: []byte("_"), Value: []byte("1")}, 1108 {Name: []byte("__wrong__"), Value: []byte("2")}, 1109 } 1110 assert.Nil(t, findReservedLabel(labels, nameLabel)) 1111 assert.Nil(t, findReservedLabel(labels, rollupLabel)) 1112 1113 // Multiple labels, only one contains an expected value 1114 labels = []prompb.Label{ 1115 {Name: []byte("_"), Value: []byte("1")}, 1116 {Name: []byte("__name__"), Value: []byte("2")}, 1117 {Name: []byte("mymetric"), Value: []byte("3")}, 1118 } 1119 assert.Equal(t, []byte("2"), findReservedLabel(labels, nameLabel)) 1120 assert.Nil(t, findReservedLabel(labels, rollupLabel)) 1121 1122 // Multiple labels, only one contains an expected value 1123 labels = []prompb.Label{ 1124 {Name: []byte("__abc__"), Value: []byte("1")}, 1125 {Name: []byte("__rollup__"), Value: []byte("2")}, 1126 {Name: []byte("metric"), Value: []byte("2")}, 1127 } 1128 assert.Nil(t, findReservedLabel(labels, nameLabel)) 1129 assert.Equal(t, []byte("2"), findReservedLabel(labels, rollupLabel)) 1130 1131 // Multiple labels, all expected values exist contain an expected value 1132 labels = []prompb.Label{ 1133 {Name: []byte("__name__"), Value: []byte("1")}, 1134 {Name: []byte("__rollup__"), Value: []byte("2")}, 1135 {Name: []byte("one"), Value: []byte("2")}, 1136 {Name: []byte("two"), Value: []byte("2")}, 1137 } 1138 assert.Equal(t, []byte("1"), findReservedLabel(labels, nameLabel)) 1139 assert.Equal(t, []byte("2"), findReservedLabel(labels, rollupLabel)) 1140 1141 // Multiple labels, with reserved section, nothing exists. 1142 labels = []prompb.Label{ 1143 {Name: []byte("__a__"), Value: []byte("1")}, 1144 {Name: []byte("__b__"), Value: []byte("2")}, 1145 {Name: []byte("one"), Value: []byte("2")}, 1146 {Name: []byte("two"), Value: []byte("2")}, 1147 } 1148 assert.Nil(t, findReservedLabel(labels, nameLabel)) 1149 assert.Nil(t, findReservedLabel(labels, rollupLabel)) 1150 }