github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index_block_test.go (about) 1 // Copyright (c) 2020 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 storage 22 23 import ( 24 stdlibctx "context" 25 "fmt" 26 "sync" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/cluster/shard" 31 "github.com/m3db/m3/src/dbnode/namespace" 32 "github.com/m3db/m3/src/dbnode/sharding" 33 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 34 "github.com/m3db/m3/src/dbnode/storage/index" 35 "github.com/m3db/m3/src/dbnode/storage/limits" 36 "github.com/m3db/m3/src/m3ninx/doc" 37 "github.com/m3db/m3/src/m3ninx/idx" 38 "github.com/m3db/m3/src/m3ninx/index/segment" 39 idxpersist "github.com/m3db/m3/src/m3ninx/persist" 40 "github.com/m3db/m3/src/x/context" 41 xerrors "github.com/m3db/m3/src/x/errors" 42 "github.com/m3db/m3/src/x/ident" 43 "github.com/m3db/m3/src/x/instrument" 44 xtest "github.com/m3db/m3/src/x/test" 45 xtime "github.com/m3db/m3/src/x/time" 46 47 "github.com/golang/mock/gomock" 48 opentracing "github.com/opentracing/opentracing-go" 49 opentracinglog "github.com/opentracing/opentracing-go/log" 50 "github.com/opentracing/opentracing-go/mocktracer" 51 "github.com/stretchr/testify/require" 52 ) 53 54 var ( 55 namespaceIndexOptions = namespace.NewIndexOptions() 56 57 defaultQuery = index.Query{ 58 Query: idx.NewTermQuery([]byte("foo"), []byte("bar")), 59 } 60 61 testShardSet sharding.ShardSet 62 ) 63 64 func init() { 65 shards := sharding.NewShards([]uint32{0, 1, 2, 3}, shard.Available) 66 hashFn := sharding.DefaultHashFn(len(shards)) 67 shardSet, err := sharding.NewShardSet(shards, hashFn) 68 if err != nil { 69 panic(err) 70 } 71 testShardSet = shardSet 72 } 73 74 type testWriteBatchOption func(index.WriteBatchOptions) index.WriteBatchOptions 75 76 func testWriteBatchBlockSizeOption(blockSize time.Duration) testWriteBatchOption { 77 return func(o index.WriteBatchOptions) index.WriteBatchOptions { 78 o.IndexBlockSize = blockSize 79 return o 80 } 81 } 82 83 func testWriteBatch( 84 e index.WriteBatchEntry, 85 d doc.Metadata, 86 opts ...testWriteBatchOption, 87 ) *index.WriteBatch { 88 options := index.WriteBatchOptions{} 89 for _, opt := range opts { 90 options = opt(options) 91 } 92 b := index.NewWriteBatch(options) 93 b.Append(e, d) 94 return b 95 } 96 97 func testWriteBatchEntry( 98 id ident.ID, 99 tags ident.Tags, 100 timestamp xtime.UnixNano, 101 fns doc.OnIndexSeries, 102 ) (index.WriteBatchEntry, doc.Metadata) { 103 d := doc.Metadata{ID: copyBytes(id.Bytes())} 104 for _, tag := range tags.Values() { 105 d.Fields = append(d.Fields, doc.Field{ 106 Name: copyBytes(tag.Name.Bytes()), 107 Value: copyBytes(tag.Value.Bytes()), 108 }) 109 } 110 return index.WriteBatchEntry{ 111 Timestamp: timestamp, 112 OnIndexSeries: fns, 113 }, d 114 } 115 116 func copyBytes(b []byte) []byte { 117 return append([]byte(nil), b...) 118 } 119 120 func testNamespaceMetadata(blockSize, period time.Duration) namespace.Metadata { 121 nopts := namespaceOptions. 122 SetRetentionOptions(namespaceOptions.RetentionOptions(). 123 SetRetentionPeriod(period)). 124 SetIndexOptions( 125 namespaceIndexOptions. 126 SetBlockSize(blockSize)) 127 md, err := namespace.NewMetadata(ident.StringID("testns"), nopts) 128 if err != nil { 129 panic(err) 130 } 131 return md 132 } 133 134 func TestNamespaceIndexNewBlockFn(t *testing.T) { 135 ctrl := xtest.NewController(t) 136 defer ctrl.Finish() 137 138 blockSize := time.Hour 139 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 140 nowFn := func() time.Time { return now.ToTime() } 141 opts := DefaultTestOptions() 142 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 143 144 mockBlock := index.NewMockBlock(ctrl) 145 mockBlock.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 146 mockBlock.EXPECT().StartTime().Return(now.Truncate(blockSize)).AnyTimes() 147 mockBlock.EXPECT().Close().Return(nil).AnyTimes() 148 newBlockFn := func( 149 ts xtime.UnixNano, 150 md namespace.Metadata, 151 opts index.BlockOptions, 152 _ namespace.RuntimeOptionsManager, 153 io index.Options, 154 ) (index.Block, error) { 155 // If active block, the blockStart should be zero. 156 // Otherwise, it should match the actual time. 157 if opts.ActiveBlock { 158 require.Equal(t, xtime.UnixNano(0), ts) 159 } else { 160 require.Equal(t, now.Truncate(blockSize), ts) 161 } 162 return mockBlock, nil 163 } 164 md := testNamespaceMetadata(blockSize, 4*time.Hour) 165 index, err := newNamespaceIndexWithNewBlockFn(md, 166 namespace.NewRuntimeOptionsManager(md.ID().String()), 167 testShardSet, newBlockFn, opts) 168 require.NoError(t, err) 169 170 defer func() { 171 require.NoError(t, index.Close()) 172 }() 173 174 blocksSlice := index.(*nsIndex).state.blocksDescOrderImmutable 175 176 require.Equal(t, 1, len(blocksSlice)) 177 require.Equal(t, now.Truncate(blockSize), blocksSlice[0].blockStart) 178 179 require.Equal(t, mockBlock, index.(*nsIndex).state.latestBlock) 180 181 blocksMap := index.(*nsIndex).state.blocksByTime 182 require.Equal(t, 1, len(blocksMap)) 183 blk, ok := blocksMap[now.Truncate(blockSize)] 184 require.True(t, ok) 185 require.Equal(t, mockBlock, blk) 186 } 187 188 func TestNamespaceIndexNewBlockFnRandomErr(t *testing.T) { 189 ctrl := xtest.NewController(t) 190 defer ctrl.Finish() 191 192 blockSize := time.Hour 193 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 194 nowFn := func() time.Time { return now.ToTime() } 195 opts := DefaultTestOptions() 196 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 197 198 newBlockFn := func( 199 ts xtime.UnixNano, 200 md namespace.Metadata, 201 _ index.BlockOptions, 202 _ namespace.RuntimeOptionsManager, 203 io index.Options, 204 ) (index.Block, error) { 205 return nil, fmt.Errorf("randomerr") 206 } 207 defer instrument.SetShouldPanicEnvironmentVariable(true)() 208 md := testNamespaceMetadata(blockSize, 4*time.Hour) 209 require.Panics(t, func() { 210 _, _ = newNamespaceIndexWithNewBlockFn(md, 211 namespace.NewRuntimeOptionsManager(md.ID().String()), 212 testShardSet, newBlockFn, opts) 213 }) 214 } 215 216 func TestNamespaceIndexWrite(t *testing.T) { 217 ctrl := xtest.NewController(t) 218 defer ctrl.Finish() 219 220 blockSize := time.Hour 221 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 222 nowFn := func() time.Time { return now.ToTime() } 223 opts := DefaultTestOptions() 224 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 225 226 mockBlock := index.NewMockBlock(ctrl) 227 mockBlock.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 228 mockBlock.EXPECT().Close().Return(nil).Times(2) // active and normal 229 mockBlock.EXPECT().StartTime().Return(now.Truncate(blockSize)).AnyTimes() 230 newBlockFn := func( 231 ts xtime.UnixNano, 232 md namespace.Metadata, 233 opts index.BlockOptions, 234 _ namespace.RuntimeOptionsManager, 235 io index.Options, 236 ) (index.Block, error) { 237 // If active block, the blockStart should be zero. 238 // Otherwise, it should match the actual time. 239 if opts.ActiveBlock { 240 require.Equal(t, xtime.UnixNano(0), ts) 241 } else { 242 require.Equal(t, now.Truncate(blockSize), ts) 243 } 244 return mockBlock, nil 245 } 246 md := testNamespaceMetadata(blockSize, 4*time.Hour) 247 idx, err := newNamespaceIndexWithNewBlockFn(md, 248 namespace.NewRuntimeOptionsManager(md.ID().String()), 249 testShardSet, newBlockFn, opts) 250 require.NoError(t, err) 251 252 defer func() { 253 require.NoError(t, idx.Close()) 254 }() 255 256 id := ident.StringID("foo") 257 tag := ident.StringTag("name", "value") 258 tags := ident.NewTags(tag) 259 lifecycle := doc.NewMockOnIndexSeries(ctrl) 260 mockWriteBatch(t, &now, lifecycle, mockBlock, &tag) 261 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false) 262 batch := index.NewWriteBatch(index.WriteBatchOptions{ 263 IndexBlockSize: blockSize, 264 }) 265 batch.Append(testWriteBatchEntry(id, tags, now, lifecycle)) 266 require.NoError(t, idx.WriteBatch(batch)) 267 } 268 269 func TestNamespaceIndexWriteCreatesBlock(t *testing.T) { 270 ctrl := xtest.NewController(t) 271 defer ctrl.Finish() 272 273 blockSize := time.Hour 274 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 275 t0 := now.Truncate(blockSize) 276 t1 := t0.Add(blockSize) 277 var nowLock sync.Mutex 278 nowFn := func() time.Time { 279 nowLock.Lock() 280 defer nowLock.Unlock() 281 return now.ToTime() 282 } 283 opts := DefaultTestOptions() 284 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 285 286 bActive := index.NewMockBlock(ctrl) 287 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 288 bActive.EXPECT().Close().Return(nil) 289 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 290 b0 := index.NewMockBlock(ctrl) 291 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 292 b0.EXPECT().Close().Return(nil) 293 b0.EXPECT().StartTime().Return(t0).AnyTimes() 294 b1 := index.NewMockBlock(ctrl) 295 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 296 b1.EXPECT().StartTime().Return(t1).AnyTimes() 297 newBlockFn := func( 298 ts xtime.UnixNano, 299 md namespace.Metadata, 300 opts index.BlockOptions, 301 _ namespace.RuntimeOptionsManager, 302 io index.Options, 303 ) (index.Block, error) { 304 if opts.ActiveBlock { 305 return bActive, nil 306 } 307 if ts.Equal(t0) { 308 return b0, nil 309 } 310 if ts.Equal(t1) { 311 return b1, nil 312 } 313 panic("should never get here") 314 } 315 md := testNamespaceMetadata(blockSize, 4*time.Hour) 316 idx, err := newNamespaceIndexWithNewBlockFn(md, 317 namespace.NewRuntimeOptionsManager(md.ID().String()), 318 testShardSet, newBlockFn, opts) 319 require.NoError(t, err) 320 321 defer func() { 322 require.NoError(t, idx.Close()) 323 }() 324 325 id := ident.StringID("foo") 326 tag := ident.StringTag("name", "value") 327 tags := ident.NewTags(tag) 328 lifecycle := doc.NewMockOnIndexSeries(ctrl) 329 mockWriteBatch(t, &now, lifecycle, bActive, &tag) 330 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()). 331 Return(false). 332 AnyTimes() 333 nowLock.Lock() 334 now = now.Add(blockSize) 335 nowLock.Unlock() 336 337 entry, doc := testWriteBatchEntry(id, tags, now, lifecycle) 338 batch := testWriteBatch(entry, doc, testWriteBatchBlockSizeOption(blockSize)) 339 require.NoError(t, idx.WriteBatch(batch)) 340 } 341 342 func TestNamespaceIndexBootstrap(t *testing.T) { 343 ctrl := xtest.NewController(t) 344 defer ctrl.Finish() 345 346 blockSize := time.Hour 347 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 348 t0 := now.Truncate(blockSize) 349 t1 := t0.Add(1 * blockSize) 350 t2 := t1.Add(1 * blockSize) 351 var nowLock sync.Mutex 352 nowFn := func() time.Time { 353 nowLock.Lock() 354 defer nowLock.Unlock() 355 return now.ToTime() 356 } 357 opts := DefaultTestOptions() 358 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 359 360 bActive := index.NewMockBlock(ctrl) 361 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 362 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 363 b0 := index.NewMockBlock(ctrl) 364 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 365 b0.EXPECT().StartTime().Return(t0).AnyTimes() 366 b1 := index.NewMockBlock(ctrl) 367 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 368 b1.EXPECT().StartTime().Return(t1).AnyTimes() 369 newBlockFn := func( 370 ts xtime.UnixNano, 371 md namespace.Metadata, 372 opts index.BlockOptions, 373 _ namespace.RuntimeOptionsManager, 374 io index.Options, 375 ) (index.Block, error) { 376 if opts.ActiveBlock { 377 return bActive, nil 378 } 379 if ts.Equal(t0) { 380 return b0, nil 381 } 382 if ts.Equal(t1) { 383 return b1, nil 384 } 385 panic("should never get here") 386 } 387 md := testNamespaceMetadata(blockSize, 4*time.Hour) 388 idx, err := newNamespaceIndexWithNewBlockFn(md, 389 namespace.NewRuntimeOptionsManager(md.ID().String()), 390 testShardSet, newBlockFn, opts) 391 require.NoError(t, err) 392 393 seg1 := segment.NewMockSegment(ctrl) 394 seg2 := segment.NewMockSegment(ctrl) 395 seg3 := segment.NewMockSegment(ctrl) 396 t0Results := result.NewIndexBlockByVolumeType(t0) 397 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 398 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 399 t1Results := result.NewIndexBlockByVolumeType(t1) 400 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 401 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 402 bootstrapResults := result.IndexResults{ 403 t0: t0Results, 404 t1: t1Results, 405 } 406 407 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 408 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 409 require.NoError(t, idx.Bootstrap(bootstrapResults)) 410 } 411 412 func TestNamespaceIndexTickExpire(t *testing.T) { 413 ctrl := xtest.NewController(t) 414 defer ctrl.Finish() 415 416 retentionPeriod := 4 * time.Hour 417 blockSize := time.Hour 418 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 419 t0 := now.Truncate(blockSize) 420 var nowLock sync.Mutex 421 nowFn := func() time.Time { 422 nowLock.Lock() 423 defer nowLock.Unlock() 424 return now.ToTime() 425 } 426 opts := DefaultTestOptions() 427 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 428 429 bActive := index.NewMockBlock(ctrl) 430 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 431 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 432 b0 := index.NewMockBlock(ctrl) 433 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 434 b0.EXPECT().StartTime().Return(t0).AnyTimes() 435 newBlockFn := func( 436 ts xtime.UnixNano, 437 md namespace.Metadata, 438 opts index.BlockOptions, 439 _ namespace.RuntimeOptionsManager, 440 io index.Options, 441 ) (index.Block, error) { 442 if opts.ActiveBlock { 443 return bActive, nil 444 } 445 if ts.Equal(t0) { 446 return b0, nil 447 } 448 panic("should never get here") 449 } 450 md := testNamespaceMetadata(blockSize, retentionPeriod) 451 idx, err := newNamespaceIndexWithNewBlockFn(md, 452 namespace.NewRuntimeOptionsManager(md.ID().String()), 453 testShardSet, newBlockFn, opts) 454 require.NoError(t, err) 455 456 nowLock.Lock() 457 now = now.Add(retentionPeriod).Add(blockSize) 458 nowLock.Unlock() 459 460 c := context.NewCancellable() 461 462 bActive.EXPECT().Tick(c).Return(index.BlockTickResult{}, nil) 463 464 b0.EXPECT().Close().Return(nil) 465 466 result, err := idx.Tick(c, xtime.ToUnixNano(nowFn())) 467 require.NoError(t, err) 468 require.Equal(t, namespaceIndexTickResult{ 469 NumBlocks: 0, 470 NumBlocksEvicted: 0, 471 }, result) 472 } 473 474 func TestNamespaceIndexTick(t *testing.T) { 475 ctrl := xtest.NewController(t) 476 defer ctrl.Finish() 477 478 retentionPeriod := 4 * time.Hour 479 blockSize := time.Hour 480 now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute) 481 t0 := now.Truncate(blockSize) 482 var nowLock sync.Mutex 483 nowFn := func() time.Time { 484 nowLock.Lock() 485 defer nowLock.Unlock() 486 return now.ToTime() 487 } 488 opts := DefaultTestOptions() 489 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 490 491 bActive := index.NewMockBlock(ctrl) 492 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 493 bActive.EXPECT().Close().Return(nil) 494 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 495 b0 := index.NewMockBlock(ctrl) 496 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 497 b0.EXPECT().Close().Return(nil) 498 b0.EXPECT().StartTime().Return(t0).AnyTimes() 499 newBlockFn := func( 500 ts xtime.UnixNano, 501 md namespace.Metadata, 502 opts index.BlockOptions, 503 _ namespace.RuntimeOptionsManager, 504 io index.Options, 505 ) (index.Block, error) { 506 if opts.ActiveBlock { 507 return bActive, nil 508 } 509 if ts.Equal(t0) { 510 return b0, nil 511 } 512 panic("should never get here") 513 } 514 md := testNamespaceMetadata(blockSize, retentionPeriod) 515 idx, err := newNamespaceIndexWithNewBlockFn(md, 516 namespace.NewRuntimeOptionsManager(md.ID().String()), 517 testShardSet, newBlockFn, opts) 518 require.NoError(t, err) 519 520 defer func() { 521 require.NoError(t, idx.Close()) 522 }() 523 524 c := context.NewCancellable() 525 526 bActive.EXPECT().Tick(c). 527 Return(index.BlockTickResult{ 528 NumDocs: 10, 529 NumSegments: 2, 530 }, nil). 531 AnyTimes() 532 bActive.EXPECT().IsSealed().Return(false).AnyTimes() 533 534 b0.EXPECT().Tick(c). 535 Return(index.BlockTickResult{ 536 NumDocs: 10, 537 NumSegments: 2, 538 }, nil) 539 b0.EXPECT().IsSealed().Return(false) 540 541 result, err := idx.Tick(c, xtime.ToUnixNano(nowFn())) 542 require.NoError(t, err) 543 require.Equal(t, namespaceIndexTickResult{ 544 NumBlocks: 1, 545 NumSegments: 4, 546 NumTotalDocs: 20, 547 }, result) 548 549 nowLock.Lock() 550 now = now.Add(2 * blockSize) 551 nowLock.Unlock() 552 553 b0.EXPECT().Tick(c).Return(index.BlockTickResult{ 554 NumDocs: 10, 555 NumSegments: 2, 556 }, nil) 557 b0.EXPECT().IsSealed().Return(false).Times(1) 558 b0.EXPECT().Seal().Return(nil).AnyTimes() 559 result, err = idx.Tick(c, xtime.ToUnixNano(nowFn())) 560 require.NoError(t, err) 561 require.Equal(t, namespaceIndexTickResult{ 562 NumBlocks: 1, 563 NumBlocksSealed: 0, 564 NumSegments: 4, 565 NumTotalDocs: 20, 566 }, result) 567 568 b0.EXPECT().Tick(c).Return(index.BlockTickResult{ 569 NumDocs: 10, 570 NumSegments: 2, 571 }, nil) 572 result, err = idx.Tick(c, xtime.ToUnixNano(nowFn())) 573 require.NoError(t, err) 574 require.Equal(t, namespaceIndexTickResult{ 575 NumBlocks: 1, 576 NumSegments: 4, 577 NumTotalDocs: 20, 578 }, result) 579 } 580 581 func TestNamespaceIndexBlockQuery(t *testing.T) { 582 ctrl := xtest.NewController(t) 583 defer ctrl.Finish() 584 585 retention := 2 * time.Hour 586 blockSize := time.Hour 587 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 588 t0 := now.Truncate(blockSize) 589 t1 := t0.Add(1 * blockSize) 590 t2 := t1.Add(1 * blockSize) 591 var nowLock sync.Mutex 592 nowFn := func() time.Time { 593 nowLock.Lock() 594 defer nowLock.Unlock() 595 return now.ToTime() 596 } 597 opts := DefaultTestOptions() 598 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 599 600 bActive := index.NewMockBlock(ctrl) 601 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 602 bActive.EXPECT().Close().Return(nil) 603 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 604 bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 605 b0 := index.NewMockBlock(ctrl) 606 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 607 b0.EXPECT().Close().Return(nil) 608 b0.EXPECT().StartTime().Return(t0).AnyTimes() 609 b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 610 b1 := index.NewMockBlock(ctrl) 611 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 612 b1.EXPECT().Close().Return(nil) 613 b1.EXPECT().StartTime().Return(t1).AnyTimes() 614 b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes() 615 newBlockFn := func( 616 ts xtime.UnixNano, 617 md namespace.Metadata, 618 opts index.BlockOptions, 619 _ namespace.RuntimeOptionsManager, 620 io index.Options, 621 ) (index.Block, error) { 622 if opts.ActiveBlock { 623 return bActive, nil 624 } 625 if ts.Equal(t0) { 626 return b0, nil 627 } 628 if ts.Equal(t1) { 629 return b1, nil 630 } 631 panic("should never get here") 632 } 633 md := testNamespaceMetadata(blockSize, retention) 634 idx, err := newNamespaceIndexWithNewBlockFn(md, 635 namespace.NewRuntimeOptionsManager(md.ID().String()), 636 testShardSet, newBlockFn, opts) 637 require.NoError(t, err) 638 639 defer func() { 640 require.NoError(t, idx.Close()) 641 }() 642 643 seg1 := segment.NewMockSegment(ctrl) 644 seg2 := segment.NewMockSegment(ctrl) 645 seg3 := segment.NewMockSegment(ctrl) 646 t0Results := result.NewIndexBlockByVolumeType(t0) 647 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 648 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 649 t1Results := result.NewIndexBlockByVolumeType(t1) 650 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 651 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 652 bootstrapResults := result.IndexResults{ 653 t0: t0Results, 654 t1: t1Results, 655 } 656 657 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 658 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 659 require.NoError(t, idx.Bootstrap(bootstrapResults)) 660 661 for _, test := range []struct { 662 name string 663 requireExhaustive bool 664 }{ 665 {"allow non-exhaustive", false}, 666 {"require exhaustive", true}, 667 } { 668 t.Run(test.name, func(t *testing.T) { 669 // only queries as much as is needed (wrt to time) 670 ctx := context.NewBackground() 671 q := defaultQuery 672 qOpts := index.QueryOptions{ 673 StartInclusive: t0, 674 EndExclusive: now.Add(time.Minute), 675 } 676 677 // Lock to prevent race given these blocks are processed concurrently. 678 var resultLock sync.Mutex 679 680 // create initial span from a mock tracer and get ctx 681 mtr := mocktracer.New() 682 sp := mtr.StartSpan("root") 683 ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp)) 684 685 mockIterActive := index.NewMockQueryIterator(ctrl) 686 mockIter0 := index.NewMockQueryIterator(ctrl) 687 bActive.EXPECT().QueryIter(gomock.Any(), q).Return(mockIterActive, nil) 688 mockIterActive.EXPECT().Done().Return(true) 689 mockIterActive.EXPECT().Close().Return(nil) 690 b0.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter0, nil) 691 mockIter0.EXPECT().Done().Return(true) 692 mockIter0.EXPECT().Close().Return(nil) 693 694 result, err := idx.Query(ctx, q, qOpts) 695 require.NoError(t, err) 696 require.True(t, result.Exhaustive) 697 698 // queries multiple blocks if needed 699 qOpts = index.QueryOptions{ 700 StartInclusive: t0, 701 EndExclusive: t2.Add(time.Minute), 702 RequireExhaustive: test.requireExhaustive, 703 } 704 bActive.EXPECT().QueryIter(gomock.Any(), q).Return(mockIterActive, nil) 705 mockIterActive.EXPECT().Done().Return(true) 706 mockIterActive.EXPECT().Close().Return(nil) 707 b0.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter0, nil) 708 mockIter0.EXPECT().Done().Return(true) 709 mockIter0.EXPECT().Close().Return(nil) 710 711 mockIter1 := index.NewMockQueryIterator(ctrl) 712 b1.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter1, nil) 713 mockIter1.EXPECT().Done().Return(true) 714 mockIter1.EXPECT().Close().Return(nil) 715 716 result, err = idx.Query(ctx, q, qOpts) 717 require.NoError(t, err) 718 require.True(t, result.Exhaustive) 719 720 // stops querying once a block returns non-exhaustive 721 qOpts = index.QueryOptions{ 722 StartInclusive: t0, 723 EndExclusive: t0.Add(time.Minute), 724 RequireExhaustive: test.requireExhaustive, 725 SeriesLimit: 1, 726 } 727 728 docs := []doc.Document{ 729 doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}), 730 doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("B")}), 731 } 732 mockQueryWithIter(t, mockIterActive, bActive, q, qOpts, &resultLock, docs) 733 mockQueryWithIter(t, mockIter0, b0, q, qOpts, &resultLock, docs) 734 735 result, err = idx.Query(ctx, q, qOpts) 736 if test.requireExhaustive { 737 require.Error(t, err) 738 require.False(t, xerrors.IsRetryableError(err)) 739 } else { 740 require.NoError(t, err) 741 require.False(t, result.Exhaustive) 742 } 743 744 sp.Finish() 745 spans := mtr.FinishedSpans() 746 require.Len(t, spans, 9) 747 }) 748 } 749 } 750 751 func TestLimits(t *testing.T) { 752 ctrl := xtest.NewController(t) 753 defer ctrl.Finish() 754 755 retention := 2 * time.Hour 756 blockSize := time.Hour 757 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 758 t0 := now.Truncate(blockSize) 759 t1 := t0.Add(1 * blockSize) 760 var nowLock sync.Mutex 761 nowFn := func() time.Time { 762 nowLock.Lock() 763 defer nowLock.Unlock() 764 return now.ToTime() 765 } 766 opts := DefaultTestOptions() 767 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 768 769 bActive := index.NewMockBlock(ctrl) 770 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 771 bActive.EXPECT().Close().Return(nil).AnyTimes() 772 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 773 bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 774 b0 := index.NewMockBlock(ctrl) 775 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 776 b0.EXPECT().Close().Return(nil).AnyTimes() 777 b0.EXPECT().StartTime().Return(t0).AnyTimes() 778 b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 779 newBlockFn := func( 780 ts xtime.UnixNano, 781 md namespace.Metadata, 782 opts index.BlockOptions, 783 _ namespace.RuntimeOptionsManager, 784 io index.Options, 785 ) (index.Block, error) { 786 if opts.ActiveBlock { 787 return bActive, nil 788 } 789 if ts.Equal(t0) { 790 return b0, nil 791 } 792 panic("should never get here") 793 } 794 md := testNamespaceMetadata(blockSize, retention) 795 idx, err := newNamespaceIndexWithNewBlockFn(md, 796 namespace.NewRuntimeOptionsManager(md.ID().String()), 797 testShardSet, newBlockFn, opts) 798 require.NoError(t, err) 799 800 defer func() { 801 require.NoError(t, idx.Close()) 802 }() 803 804 seg1 := segment.NewMockSegment(ctrl) 805 t0Results := result.NewIndexBlockByVolumeType(t0) 806 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 807 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 808 bootstrapResults := result.IndexResults{ 809 t0: t0Results, 810 } 811 812 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 813 require.NoError(t, idx.Bootstrap(bootstrapResults)) 814 815 for _, test := range []struct { 816 name string 817 seriesLimit int 818 docsLimit int 819 requireExhaustive bool 820 expectedErr string 821 expectedQueryLimitExceededError bool 822 }{ 823 { 824 name: "no limits", 825 seriesLimit: 0, 826 docsLimit: 0, 827 requireExhaustive: false, 828 expectedErr: "", 829 }, 830 { 831 name: "series limit only", 832 seriesLimit: 1, 833 docsLimit: 0, 834 requireExhaustive: false, 835 expectedErr: "", 836 }, 837 { 838 name: "docs limit only", 839 seriesLimit: 0, 840 docsLimit: 1, 841 requireExhaustive: false, 842 expectedErr: "", 843 }, 844 { 845 name: "both series and docs limit", 846 seriesLimit: 1, 847 docsLimit: 1, 848 requireExhaustive: false, 849 expectedErr: "", 850 }, 851 { 852 name: "series limit only", 853 seriesLimit: 1, 854 docsLimit: 0, 855 requireExhaustive: true, 856 expectedErr: "query exceeded limit: require_exhaustive=true, " + 857 "series_limit=1, series_matched=1, docs_limit=0, docs_matched=4", 858 expectedQueryLimitExceededError: true, 859 }, 860 { 861 name: "docs limit only", 862 seriesLimit: 0, 863 docsLimit: 1, 864 requireExhaustive: true, 865 expectedErr: "query exceeded limit: require_exhaustive=true, " + 866 "series_limit=0, series_matched=1, docs_limit=1, docs_matched=4", 867 expectedQueryLimitExceededError: true, 868 }, 869 { 870 name: "both series and docs limit", 871 seriesLimit: 1, 872 docsLimit: 1, 873 requireExhaustive: true, 874 expectedErr: "query exceeded limit: require_exhaustive=true, " + 875 "series_limit=1, series_matched=1, docs_limit=1, docs_matched=4", 876 expectedQueryLimitExceededError: true, 877 }, 878 } { 879 t.Run(test.name, func(t *testing.T) { 880 // only queries as much as is needed (wrt to time) 881 ctx := context.NewBackground() 882 q := defaultQuery 883 qOpts := index.QueryOptions{ 884 StartInclusive: t0, 885 EndExclusive: t1.Add(time.Minute), 886 SeriesLimit: test.seriesLimit, 887 DocsLimit: test.docsLimit, 888 RequireExhaustive: test.requireExhaustive, 889 } 890 891 // Lock to prevent race given these blocks are processed concurrently. 892 var resultLock sync.Mutex 893 894 // create initial span from a mock tracer and get ctx 895 mtr := mocktracer.New() 896 sp := mtr.StartSpan("root") 897 ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp)) 898 899 mockIterActive := index.NewMockQueryIterator(ctrl) 900 mockIter := index.NewMockQueryIterator(ctrl) 901 902 docs := []doc.Document{ 903 // Results in size=1 and docs=2. 904 // Byte array represents ID encoded as bytes. 905 // 1 represents the ID length in bytes, 49 is the ID itself which is 906 // the ASCII value for A 907 doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}), 908 doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}), 909 } 910 mockQueryWithIter(t, mockIterActive, bActive, q, qOpts, &resultLock, docs) 911 mockQueryWithIter(t, mockIter, b0, q, qOpts, &resultLock, docs) 912 913 result, err := idx.Query(ctx, q, qOpts) 914 if test.seriesLimit == 0 && test.docsLimit == 0 { 915 require.True(t, result.Exhaustive) 916 } else { 917 require.False(t, result.Exhaustive) 918 } 919 920 if test.requireExhaustive { 921 require.Error(t, err) 922 require.Equal(t, test.expectedErr, err.Error()) 923 require.Equal(t, test.expectedQueryLimitExceededError, limits.IsQueryLimitExceededError(err)) 924 require.Equal(t, test.expectedQueryLimitExceededError, xerrors.IsInvalidParams(err)) 925 } else { 926 require.NoError(t, err) 927 } 928 }) 929 } 930 } 931 932 func TestNamespaceIndexBlockQueryReleasingContext(t *testing.T) { 933 ctrl := xtest.NewController(t) 934 defer ctrl.Finish() 935 936 retention := 2 * time.Hour 937 blockSize := time.Hour 938 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 939 t0 := now.Truncate(blockSize) 940 t1 := t0.Add(1 * blockSize) 941 t2 := t1.Add(1 * blockSize) 942 var nowLock sync.Mutex 943 nowFn := func() time.Time { 944 nowLock.Lock() 945 defer nowLock.Unlock() 946 return now.ToTime() 947 } 948 opts := DefaultTestOptions() 949 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 950 951 bActive := index.NewMockBlock(ctrl) 952 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 953 bActive.EXPECT().Close().Return(nil) 954 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 955 bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 956 b0 := index.NewMockBlock(ctrl) 957 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 958 b0.EXPECT().Close().Return(nil) 959 b0.EXPECT().StartTime().Return(t0).AnyTimes() 960 b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 961 b1 := index.NewMockBlock(ctrl) 962 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 963 b1.EXPECT().Close().Return(nil) 964 b1.EXPECT().StartTime().Return(t1).AnyTimes() 965 b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes() 966 newBlockFn := func( 967 ts xtime.UnixNano, 968 md namespace.Metadata, 969 opts index.BlockOptions, 970 _ namespace.RuntimeOptionsManager, 971 io index.Options, 972 ) (index.Block, error) { 973 if opts.ActiveBlock { 974 return bActive, nil 975 } 976 if ts.Equal(t0) { 977 return b0, nil 978 } 979 if ts.Equal(t1) { 980 return b1, nil 981 } 982 panic("should never get here") 983 } 984 985 iopts := opts.IndexOptions() 986 mockPool := index.NewMockQueryResultsPool(ctrl) 987 iopts = iopts.SetQueryResultsPool(mockPool) 988 stubResult := index.NewQueryResults(ident.StringID("ns"), index.QueryResultsOptions{}, iopts) 989 990 md := testNamespaceMetadata(blockSize, retention) 991 idxIface, err := newNamespaceIndexWithNewBlockFn(md, 992 namespace.NewRuntimeOptionsManager(md.ID().String()), 993 testShardSet, newBlockFn, opts) 994 require.NoError(t, err) 995 996 idx, ok := idxIface.(*nsIndex) 997 require.True(t, ok) 998 idx.resultsPool = mockPool 999 1000 defer func() { 1001 require.NoError(t, idx.Close()) 1002 }() 1003 1004 seg1 := segment.NewMockSegment(ctrl) 1005 seg2 := segment.NewMockSegment(ctrl) 1006 seg3 := segment.NewMockSegment(ctrl) 1007 t0Results := result.NewIndexBlockByVolumeType(t0) 1008 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 1009 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 1010 t1Results := result.NewIndexBlockByVolumeType(t1) 1011 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 1012 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 1013 bootstrapResults := result.IndexResults{ 1014 t0: t0Results, 1015 t1: t1Results, 1016 } 1017 1018 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 1019 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 1020 require.NoError(t, idx.Bootstrap(bootstrapResults)) 1021 1022 ctx := context.NewBackground() 1023 q := defaultQuery 1024 qOpts := index.QueryOptions{ 1025 StartInclusive: t0, 1026 EndExclusive: now.Add(time.Minute), 1027 } 1028 mockIterActive := index.NewMockQueryIterator(ctrl) 1029 mockIter := index.NewMockQueryIterator(ctrl) 1030 gomock.InOrder( 1031 mockPool.EXPECT().Get().Return(stubResult), 1032 bActive.EXPECT().QueryIter(ctx, q).Return(mockIterActive, nil), 1033 b0.EXPECT().QueryIter(ctx, q).Return(mockIter, nil), 1034 mockPool.EXPECT().Put(stubResult), 1035 ) 1036 1037 mockIter.EXPECT().Done().Return(true) 1038 mockIterActive.EXPECT().Done().Return(true) 1039 mockIter.EXPECT().Close().Return(nil) 1040 mockIterActive.EXPECT().Close().Return(nil) 1041 1042 _, err = idx.Query(ctx, q, qOpts) 1043 require.NoError(t, err) 1044 ctx.BlockingClose() 1045 } 1046 1047 func TestNamespaceIndexBlockAggregateQuery(t *testing.T) { 1048 ctrl := xtest.NewController(t) 1049 defer ctrl.Finish() 1050 1051 query := idx.NewTermQuery([]byte("a"), []byte("b")) 1052 retention := 2 * time.Hour 1053 blockSize := time.Hour 1054 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 1055 t0 := now.Truncate(blockSize) 1056 t1 := t0.Add(1 * blockSize) 1057 t2 := t1.Add(1 * blockSize) 1058 var nowLock sync.Mutex 1059 nowFn := func() time.Time { 1060 nowLock.Lock() 1061 defer nowLock.Unlock() 1062 return now.ToTime() 1063 } 1064 opts := DefaultTestOptions() 1065 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 1066 1067 bActive := index.NewMockBlock(ctrl) 1068 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1069 bActive.EXPECT().Close().Return(nil) 1070 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 1071 bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 1072 b0 := index.NewMockBlock(ctrl) 1073 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1074 b0.EXPECT().Close().Return(nil) 1075 b0.EXPECT().StartTime().Return(t0).AnyTimes() 1076 b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 1077 b1 := index.NewMockBlock(ctrl) 1078 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1079 b1.EXPECT().Close().Return(nil) 1080 b1.EXPECT().StartTime().Return(t1).AnyTimes() 1081 b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes() 1082 newBlockFn := func( 1083 ts xtime.UnixNano, 1084 md namespace.Metadata, 1085 opts index.BlockOptions, 1086 _ namespace.RuntimeOptionsManager, 1087 io index.Options, 1088 ) (index.Block, error) { 1089 if opts.ActiveBlock { 1090 return bActive, nil 1091 } 1092 if ts.Equal(t0) { 1093 return b0, nil 1094 } 1095 if ts.Equal(t1) { 1096 return b1, nil 1097 } 1098 panic("should never get here") 1099 } 1100 md := testNamespaceMetadata(blockSize, retention) 1101 idx, err := newNamespaceIndexWithNewBlockFn(md, 1102 namespace.NewRuntimeOptionsManager(md.ID().String()), 1103 testShardSet, newBlockFn, opts) 1104 require.NoError(t, err) 1105 1106 defer func() { 1107 require.NoError(t, idx.Close()) 1108 }() 1109 1110 seg1 := segment.NewMockSegment(ctrl) 1111 seg2 := segment.NewMockSegment(ctrl) 1112 seg3 := segment.NewMockSegment(ctrl) 1113 t0Results := result.NewIndexBlockByVolumeType(t0) 1114 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 1115 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 1116 t1Results := result.NewIndexBlockByVolumeType(t1) 1117 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 1118 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 1119 bootstrapResults := result.IndexResults{ 1120 t0: t0Results, 1121 t1: t1Results, 1122 } 1123 1124 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 1125 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 1126 require.NoError(t, idx.Bootstrap(bootstrapResults)) 1127 1128 for _, test := range []struct { 1129 name string 1130 requireExhaustive bool 1131 }{ 1132 {"allow non-exhaustive", false}, 1133 {"require exhaustive", true}, 1134 } { 1135 t.Run(test.name, func(t *testing.T) { 1136 // only queries as much as is needed (wrt to time) 1137 ctx := context.NewBackground() 1138 1139 // create initial span from a mock tracer and get ctx 1140 mtr := mocktracer.New() 1141 sp := mtr.StartSpan("root") 1142 ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp)) 1143 1144 q := index.Query{ 1145 Query: query, 1146 } 1147 qOpts := index.QueryOptions{ 1148 StartInclusive: t0, 1149 EndExclusive: now.Add(time.Minute), 1150 RequireExhaustive: test.requireExhaustive, 1151 } 1152 aggOpts := index.AggregationOptions{QueryOptions: qOpts} 1153 1154 mockIterActive := index.NewMockAggregateIterator(ctrl) 1155 bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil) 1156 mockIterActive.EXPECT().Done().Return(true) 1157 mockIterActive.EXPECT().Close().Return(nil) 1158 mockIter0 := index.NewMockAggregateIterator(ctrl) 1159 b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil) 1160 mockIter0.EXPECT().Done().Return(true) 1161 mockIter0.EXPECT().Close().Return(nil) 1162 result, err := idx.AggregateQuery(ctx, q, aggOpts) 1163 require.NoError(t, err) 1164 require.True(t, result.Exhaustive) 1165 1166 // queries multiple blocks if needed 1167 qOpts = index.QueryOptions{ 1168 StartInclusive: t0, 1169 EndExclusive: t2.Add(time.Minute), 1170 RequireExhaustive: test.requireExhaustive, 1171 } 1172 aggOpts = index.AggregationOptions{QueryOptions: qOpts} 1173 bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil) 1174 mockIterActive.EXPECT().Done().Return(true) 1175 mockIterActive.EXPECT().Close().Return(nil) 1176 b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil) 1177 mockIter0.EXPECT().Done().Return(true) 1178 mockIter0.EXPECT().Close().Return(nil) 1179 1180 mockIter1 := index.NewMockAggregateIterator(ctrl) 1181 b1.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter1, nil) 1182 mockIter1.EXPECT().Done().Return(true) 1183 mockIter1.EXPECT().Close().Return(nil) 1184 result, err = idx.AggregateQuery(ctx, q, aggOpts) 1185 require.NoError(t, err) 1186 require.True(t, result.Exhaustive) 1187 1188 // stops querying once a block returns non-exhaustive 1189 qOpts = index.QueryOptions{ 1190 StartInclusive: t0, 1191 EndExclusive: t0.Add(time.Minute), 1192 RequireExhaustive: test.requireExhaustive, 1193 DocsLimit: 1, 1194 } 1195 bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil) 1196 //nolint: dupl 1197 bActive.EXPECT(). 1198 AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 1199 DoAndReturn(func( 1200 ctx context.Context, 1201 iter index.AggregateIterator, 1202 opts index.QueryOptions, 1203 results index.AggregateResults, 1204 deadline time.Time, 1205 logFields []opentracinglog.Field, 1206 ) error { 1207 _, _ = results.AddFields([]index.AggregateResultsEntry{{ 1208 Field: ident.StringID("A"), 1209 Terms: []ident.ID{ident.StringID("foo")}, 1210 }, { 1211 Field: ident.StringID("B"), 1212 Terms: []ident.ID{ident.StringID("bar")}, 1213 }}) 1214 return nil 1215 }) 1216 gomock.InOrder( 1217 mockIterActive.EXPECT().Done().Return(false), 1218 mockIterActive.EXPECT().Done().Return(true), 1219 mockIterActive.EXPECT().Close().Return(nil), 1220 ) 1221 b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil) 1222 //nolint: dupl 1223 b0.EXPECT(). 1224 AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 1225 DoAndReturn(func( 1226 ctx context.Context, 1227 iter index.AggregateIterator, 1228 opts index.QueryOptions, 1229 results index.AggregateResults, 1230 deadline time.Time, 1231 logFields []opentracinglog.Field, 1232 ) error { 1233 _, _ = results.AddFields([]index.AggregateResultsEntry{{ 1234 Field: ident.StringID("A"), 1235 Terms: []ident.ID{ident.StringID("foo")}, 1236 }, { 1237 Field: ident.StringID("B"), 1238 Terms: []ident.ID{ident.StringID("bar")}, 1239 }}) 1240 return nil 1241 }) 1242 gomock.InOrder( 1243 mockIter0.EXPECT().Done().Return(false), 1244 mockIter0.EXPECT().Done().Return(true), 1245 mockIter0.EXPECT().Close().Return(nil), 1246 ) 1247 aggOpts = index.AggregationOptions{QueryOptions: qOpts} 1248 result, err = idx.AggregateQuery(ctx, q, aggOpts) 1249 if test.requireExhaustive { 1250 require.Error(t, err) 1251 require.False(t, xerrors.IsRetryableError(err)) 1252 } else { 1253 require.NoError(t, err) 1254 require.False(t, result.Exhaustive) 1255 } 1256 1257 sp.Finish() 1258 spans := mtr.FinishedSpans() 1259 require.Len(t, spans, 9) 1260 }) 1261 } 1262 } 1263 1264 func TestNamespaceIndexBlockAggregateQueryReleasingContext(t *testing.T) { 1265 ctrl := xtest.NewController(t) 1266 defer ctrl.Finish() 1267 1268 retention := 2 * time.Hour 1269 blockSize := time.Hour 1270 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 1271 t0 := now.Truncate(blockSize) 1272 t1 := t0.Add(1 * blockSize) 1273 t2 := t1.Add(1 * blockSize) 1274 var nowLock sync.Mutex 1275 nowFn := func() time.Time { 1276 nowLock.Lock() 1277 defer nowLock.Unlock() 1278 return now.ToTime() 1279 } 1280 opts := DefaultTestOptions() 1281 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 1282 1283 query := idx.NewTermQuery([]byte("a"), []byte("b")) 1284 bActive := index.NewMockBlock(ctrl) 1285 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1286 bActive.EXPECT().Close().Return(nil) 1287 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 1288 bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 1289 b0 := index.NewMockBlock(ctrl) 1290 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1291 b0.EXPECT().Close().Return(nil) 1292 b0.EXPECT().StartTime().Return(t0).AnyTimes() 1293 b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes() 1294 b1 := index.NewMockBlock(ctrl) 1295 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1296 b1.EXPECT().Close().Return(nil) 1297 b1.EXPECT().StartTime().Return(t1).AnyTimes() 1298 b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes() 1299 newBlockFn := func( 1300 ts xtime.UnixNano, 1301 md namespace.Metadata, 1302 opts index.BlockOptions, 1303 _ namespace.RuntimeOptionsManager, 1304 io index.Options, 1305 ) (index.Block, error) { 1306 if opts.ActiveBlock { 1307 return bActive, nil 1308 } 1309 if ts.Equal(t0) { 1310 return b0, nil 1311 } 1312 if ts.Equal(t1) { 1313 return b1, nil 1314 } 1315 panic("should never get here") 1316 } 1317 1318 iopts := opts.IndexOptions() 1319 mockPool := index.NewMockAggregateResultsPool(ctrl) 1320 iopts = iopts.SetAggregateResultsPool(mockPool) 1321 stubResult := index.NewAggregateResults(ident.StringID("ns"), 1322 index.AggregateResultsOptions{}, iopts) 1323 1324 md := testNamespaceMetadata(blockSize, retention) 1325 idxIface, err := newNamespaceIndexWithNewBlockFn(md, 1326 namespace.NewRuntimeOptionsManager(md.ID().String()), 1327 testShardSet, newBlockFn, opts) 1328 require.NoError(t, err) 1329 1330 idx, ok := idxIface.(*nsIndex) 1331 require.True(t, ok) 1332 idx.aggregateResultsPool = mockPool 1333 1334 defer func() { 1335 require.NoError(t, idx.Close()) 1336 }() 1337 1338 seg1 := segment.NewMockSegment(ctrl) 1339 seg2 := segment.NewMockSegment(ctrl) 1340 seg3 := segment.NewMockSegment(ctrl) 1341 t0Results := result.NewIndexBlockByVolumeType(t0) 1342 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 1343 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 1344 t1Results := result.NewIndexBlockByVolumeType(t1) 1345 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 1346 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 1347 bootstrapResults := result.IndexResults{ 1348 t0: t0Results, 1349 t1: t1Results, 1350 } 1351 1352 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 1353 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 1354 require.NoError(t, idx.Bootstrap(bootstrapResults)) 1355 1356 // only queries as much as is needed (wrt to time) 1357 ctx := context.NewBackground() 1358 q := index.Query{ 1359 Query: query, 1360 } 1361 qOpts := index.QueryOptions{ 1362 StartInclusive: t0, 1363 EndExclusive: now.Add(time.Minute), 1364 } 1365 aggOpts := index.AggregationOptions{QueryOptions: qOpts} 1366 1367 mockIterActive := index.NewMockAggregateIterator(ctrl) 1368 mockIter := index.NewMockAggregateIterator(ctrl) 1369 gomock.InOrder( 1370 mockPool.EXPECT().Get().Return(stubResult), 1371 bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil), 1372 b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter, nil), 1373 mockPool.EXPECT().Put(stubResult), 1374 ) 1375 mockIter.EXPECT().Done().Return(true) 1376 mockIterActive.EXPECT().Done().Return(true) 1377 mockIter.EXPECT().Close().Return(nil) 1378 mockIterActive.EXPECT().Close().Return(nil) 1379 1380 _, err = idx.AggregateQuery(ctx, q, aggOpts) 1381 require.NoError(t, err) 1382 ctx.BlockingClose() 1383 } 1384 1385 func TestNamespaceIndexBlockAggregateQueryAggPath(t *testing.T) { 1386 ctrl := xtest.NewController(t) 1387 defer ctrl.Finish() 1388 1389 queries := []idx.Query{idx.NewAllQuery(), idx.NewFieldQuery([]byte("field"))} 1390 retention := 2 * time.Hour 1391 blockSize := time.Hour 1392 now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute) 1393 t0 := now.Truncate(blockSize) 1394 t1 := t0.Add(1 * blockSize) 1395 t2 := t1.Add(1 * blockSize) 1396 var nowLock sync.Mutex 1397 nowFn := func() time.Time { 1398 nowLock.Lock() 1399 defer nowLock.Unlock() 1400 return now.ToTime() 1401 } 1402 opts := DefaultTestOptions() 1403 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 1404 1405 bActive := index.NewMockBlock(ctrl) 1406 bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1407 bActive.EXPECT().Close().Return(nil) 1408 bActive.EXPECT().StartTime().Return(t0).AnyTimes() 1409 bActive.EXPECT().EndTime().Return(t1).AnyTimes() 1410 b0 := index.NewMockBlock(ctrl) 1411 b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1412 b0.EXPECT().Close().Return(nil) 1413 b0.EXPECT().StartTime().Return(t0).AnyTimes() 1414 b0.EXPECT().EndTime().Return(t1).AnyTimes() 1415 b1 := index.NewMockBlock(ctrl) 1416 b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes() 1417 b1.EXPECT().Close().Return(nil) 1418 b1.EXPECT().StartTime().Return(t1).AnyTimes() 1419 b1.EXPECT().EndTime().Return(t2).AnyTimes() 1420 newBlockFn := func( 1421 ts xtime.UnixNano, 1422 md namespace.Metadata, 1423 opts index.BlockOptions, 1424 _ namespace.RuntimeOptionsManager, 1425 io index.Options, 1426 ) (index.Block, error) { 1427 if opts.ActiveBlock { 1428 return bActive, nil 1429 } 1430 if ts.Equal(t0) { 1431 return b0, nil 1432 } 1433 if ts.Equal(t1) { 1434 return b1, nil 1435 } 1436 panic("should never get here") 1437 } 1438 md := testNamespaceMetadata(blockSize, retention) 1439 idx, err := newNamespaceIndexWithNewBlockFn(md, 1440 namespace.NewRuntimeOptionsManager(md.ID().String()), 1441 testShardSet, newBlockFn, opts) 1442 require.NoError(t, err) 1443 1444 defer func() { 1445 require.NoError(t, idx.Close()) 1446 }() 1447 1448 seg1 := segment.NewMockSegment(ctrl) 1449 seg2 := segment.NewMockSegment(ctrl) 1450 seg3 := segment.NewMockSegment(ctrl) 1451 t0Results := result.NewIndexBlockByVolumeType(t0) 1452 t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)}, 1453 result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3))) 1454 t1Results := result.NewIndexBlockByVolumeType(t1) 1455 t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)}, 1456 result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3))) 1457 bootstrapResults := result.IndexResults{ 1458 t0: t0Results, 1459 t1: t1Results, 1460 } 1461 1462 b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil) 1463 b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil) 1464 require.NoError(t, idx.Bootstrap(bootstrapResults)) 1465 1466 // only queries as much as is needed (wrt to time) 1467 ctx := context.NewBackground() 1468 1469 qOpts := index.QueryOptions{ 1470 StartInclusive: t0, 1471 EndExclusive: now.Add(time.Minute), 1472 } 1473 aggOpts := index.AggregationOptions{QueryOptions: qOpts} 1474 1475 for _, test := range []struct { 1476 name string 1477 requireExhaustive bool 1478 }{ 1479 {"allow non-exhaustive", false}, 1480 {"require exhaustive", true}, 1481 } { 1482 t.Run(test.name, func(t *testing.T) { 1483 for _, query := range queries { 1484 q := index.Query{ 1485 Query: query, 1486 } 1487 mockIterActive := index.NewMockAggregateIterator(ctrl) 1488 mockIterActive.EXPECT().Done().Return(true) 1489 mockIterActive.EXPECT().Close().Return(nil) 1490 bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil) 1491 mockIter0 := index.NewMockAggregateIterator(ctrl) 1492 mockIter0.EXPECT().Done().Return(true) 1493 mockIter0.EXPECT().Close().Return(nil) 1494 b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter0, nil) 1495 result, err := idx.AggregateQuery(ctx, q, aggOpts) 1496 require.NoError(t, err) 1497 require.True(t, result.Exhaustive) 1498 1499 // queries multiple blocks if needed 1500 qOpts = index.QueryOptions{ 1501 StartInclusive: t0, 1502 EndExclusive: t2.Add(time.Minute), 1503 RequireExhaustive: test.requireExhaustive, 1504 } 1505 aggOpts = index.AggregationOptions{QueryOptions: qOpts} 1506 1507 mockIterActive.EXPECT().Done().Return(true) 1508 mockIterActive.EXPECT().Close().Return(nil) 1509 bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil) 1510 1511 mockIter0.EXPECT().Done().Return(true) 1512 mockIter0.EXPECT().Close().Return(nil) 1513 b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter0, nil) 1514 1515 mockIter1 := index.NewMockAggregateIterator(ctrl) 1516 mockIter1.EXPECT().Done().Return(true) 1517 mockIter1.EXPECT().Close().Return(nil) 1518 b1.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter1, nil) 1519 result, err = idx.AggregateQuery(ctx, q, aggOpts) 1520 require.NoError(t, err) 1521 require.True(t, result.Exhaustive) 1522 1523 // stops querying once a block returns non-exhaustive 1524 qOpts = index.QueryOptions{ 1525 StartInclusive: t0, 1526 EndExclusive: t0.Add(time.Minute), 1527 RequireExhaustive: test.requireExhaustive, 1528 DocsLimit: 1, 1529 } 1530 bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil) 1531 //nolint: dupl 1532 bActive.EXPECT(). 1533 AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 1534 DoAndReturn(func( 1535 ctx context.Context, 1536 iter index.AggregateIterator, 1537 opts index.QueryOptions, 1538 results index.AggregateResults, 1539 deadline time.Time, 1540 logFields []opentracinglog.Field, 1541 ) error { 1542 _, _ = results.AddFields([]index.AggregateResultsEntry{{ 1543 Field: ident.StringID("A"), 1544 Terms: []ident.ID{ident.StringID("foo")}, 1545 }, { 1546 Field: ident.StringID("B"), 1547 Terms: []ident.ID{ident.StringID("bar")}, 1548 }}) 1549 return nil 1550 }) 1551 gomock.InOrder( 1552 mockIterActive.EXPECT().Done().Return(false), 1553 mockIterActive.EXPECT().Done().Return(true), 1554 mockIterActive.EXPECT().Close().Return(nil), 1555 ) 1556 b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil) 1557 //nolint: dupl 1558 b0.EXPECT(). 1559 AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 1560 DoAndReturn(func( 1561 ctx context.Context, 1562 iter index.AggregateIterator, 1563 opts index.QueryOptions, 1564 results index.AggregateResults, 1565 deadline time.Time, 1566 logFields []opentracinglog.Field, 1567 ) error { 1568 _, _ = results.AddFields([]index.AggregateResultsEntry{{ 1569 Field: ident.StringID("A"), 1570 Terms: []ident.ID{ident.StringID("foo")}, 1571 }, { 1572 Field: ident.StringID("B"), 1573 Terms: []ident.ID{ident.StringID("bar")}, 1574 }}) 1575 return nil 1576 }) 1577 gomock.InOrder( 1578 mockIter0.EXPECT().Done().Return(false), 1579 mockIter0.EXPECT().Done().Return(true), 1580 mockIter0.EXPECT().Close().Return(nil), 1581 ) 1582 aggOpts = index.AggregationOptions{QueryOptions: qOpts} 1583 result, err = idx.AggregateQuery(ctx, q, aggOpts) 1584 if test.requireExhaustive { 1585 require.Error(t, err) 1586 require.False(t, xerrors.IsRetryableError(err)) 1587 } else { 1588 require.NoError(t, err) 1589 require.False(t, result.Exhaustive) 1590 } 1591 } 1592 }) 1593 } 1594 } 1595 1596 func mockWriteBatch(t *testing.T, 1597 now *xtime.UnixNano, 1598 lifecycle *doc.MockOnIndexSeries, 1599 block *index.MockBlock, 1600 tag *ident.Tag, 1601 ) { 1602 block.EXPECT(). 1603 WriteBatch(gomock.Any()). 1604 Return(index.WriteBatchResult{}, nil). 1605 Do(func(batch *index.WriteBatch) { 1606 docs := batch.PendingDocs() 1607 require.Equal(t, 1, len(docs)) 1608 require.Equal(t, doc.Metadata{ 1609 ID: id.Bytes(), 1610 Fields: doc.Fields{{Name: tag.Name.Bytes(), Value: tag.Value.Bytes()}}, 1611 }, docs[0]) 1612 entries := batch.PendingEntries() 1613 require.Equal(t, 1, len(entries)) 1614 require.True(t, entries[0].Timestamp.Equal(*now)) 1615 require.True(t, entries[0].OnIndexSeries == lifecycle) // Just ptr equality 1616 }) 1617 } 1618 1619 func mockQueryWithIter(t *testing.T, 1620 iter *index.MockQueryIterator, 1621 block *index.MockBlock, 1622 q index.Query, 1623 qOpts index.QueryOptions, 1624 resultLock *sync.Mutex, 1625 docsToAdd []doc.Document, 1626 ) { 1627 block.EXPECT().QueryIter(gomock.Any(), q).Return(iter, nil) 1628 block.EXPECT().QueryWithIter(gomock.Any(), qOpts, iter, gomock.Any(), gomock.Any(), gomock.Any()). 1629 DoAndReturn(func( 1630 ctx context.Context, 1631 opts index.QueryOptions, 1632 iter index.QueryIterator, 1633 r index.QueryResults, 1634 deadline time.Time, 1635 logFields []opentracinglog.Field, 1636 ) error { 1637 resultLock.Lock() 1638 defer resultLock.Unlock() 1639 _, _, err := r.AddDocuments(docsToAdd) 1640 require.NoError(t, err) 1641 return nil 1642 }) 1643 gomock.InOrder( 1644 iter.EXPECT().Done().Return(false), 1645 iter.EXPECT().Done().Return(true), 1646 iter.EXPECT().Close().Return(nil), 1647 ) 1648 }