github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/fs/source_index_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 fs 22 23 import ( 24 "fmt" 25 "os" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/require" 30 "github.com/uber-go/tally" 31 32 "github.com/m3db/m3/src/dbnode/namespace" 33 "github.com/m3db/m3/src/dbnode/persist" 34 "github.com/m3db/m3/src/dbnode/persist/fs" 35 "github.com/m3db/m3/src/dbnode/retention" 36 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 37 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 38 "github.com/m3db/m3/src/dbnode/storage/index/convert" 39 "github.com/m3db/m3/src/m3ninx/index/segment/mem" 40 idxpersist "github.com/m3db/m3/src/m3ninx/persist" 41 "github.com/m3db/m3/src/x/ident" 42 xtime "github.com/m3db/m3/src/x/time" 43 ) 44 45 type testTimesOptions struct { 46 numBlocks int 47 } 48 49 type testBootstrapIndexTimes struct { 50 start xtime.UnixNano 51 end xtime.UnixNano 52 shardTimeRanges result.ShardTimeRanges 53 } 54 55 func newTestBootstrapIndexTimes( 56 opts testTimesOptions, 57 ) testBootstrapIndexTimes { 58 var ( 59 start, end xtime.UnixNano 60 at = xtime.Now() 61 ) 62 switch opts.numBlocks { 63 case 2: 64 start = at.Truncate(testBlockSize) 65 indexStart := start.Truncate(testIndexBlockSize) 66 for !start.Equal(indexStart) { 67 // make sure data blocks overlap, test block size is 2h 68 // and test index block size is 4h 69 start = start.Add(testBlockSize) 70 indexStart = start.Truncate(testIndexBlockSize) 71 } 72 end = start.Add(time.Duration(opts.numBlocks) * testIndexBlockSize) 73 case 3: 74 end = at.Truncate(testIndexBlockSize) 75 start = end.Add(time.Duration(-1*opts.numBlocks) * testBlockSize) 76 default: 77 panic("unexpected") 78 } 79 80 shardTimeRanges := result.NewShardTimeRanges().Set( 81 testShard, 82 xtime.NewRanges(xtime.Range{ 83 Start: start, 84 End: end, 85 }), 86 ) 87 88 return testBootstrapIndexTimes{ 89 start: start, 90 end: end, 91 shardTimeRanges: shardTimeRanges, 92 } 93 } 94 95 type testSeriesBlocks [][]testSeries 96 97 func testGoodTaggedSeriesDataBlocks() testSeriesBlocks { 98 fooSeries := struct { 99 id string 100 tags map[string]string 101 }{ 102 "foo", 103 map[string]string{"aaa": "bbb", "ccc": "ddd"}, 104 } 105 dataBlocks := testSeriesBlocks{ 106 []testSeries{ 107 {fooSeries.id, fooSeries.tags, []byte{0x1}}, 108 {"bar", map[string]string{"eee": "fff", "ggg": "hhh"}, []byte{0x1}}, 109 {"baz", map[string]string{"iii": "jjj", "kkk": "lll"}, []byte{0x1}}, 110 }, 111 []testSeries{ 112 {fooSeries.id, fooSeries.tags, []byte{0x2}}, 113 {"qux", map[string]string{"mmm": "nnn", "ooo": "ppp"}, []byte{0x2}}, 114 {"qaz", map[string]string{"qqq": "rrr", "sss": "ttt"}, []byte{0x2}}, 115 }, 116 []testSeries{ 117 {fooSeries.id, fooSeries.tags, []byte{0x3}}, 118 {"qan", map[string]string{"uuu": "vvv", "www": "xxx"}, []byte{0x3}}, 119 {"qam", map[string]string{"yyy": "zzz", "000": "111"}, []byte{0x3}}, 120 }, 121 } 122 return dataBlocks 123 } 124 125 func writeTSDBGoodTaggedSeriesDataFiles( 126 t require.TestingT, 127 dir string, 128 namespaceID ident.ID, 129 start xtime.UnixNano, 130 ) { 131 dataBlocks := testGoodTaggedSeriesDataBlocks() 132 133 writeTSDBFiles(t, dir, namespaceID, testShard, 134 start, dataBlocks[0]) 135 writeTSDBFiles(t, dir, namespaceID, testShard, 136 start.Add(testBlockSize), dataBlocks[1]) 137 writeTSDBFiles(t, dir, namespaceID, testShard, 138 start.Add(2*testBlockSize), dataBlocks[2]) 139 } 140 141 func writeTSDBPersistedIndexBlock( 142 t *testing.T, 143 dir string, 144 namespace namespace.Metadata, 145 start xtime.UnixNano, 146 shards map[uint32]struct{}, 147 block []testSeries, 148 ) { 149 seg, err := mem.NewSegment(mem.NewOptions()) 150 require.NoError(t, err) 151 152 for _, series := range block { 153 d, err := convert.FromSeriesIDAndTags(series.ID(), series.Tags()) 154 require.NoError(t, err) 155 exists, err := seg.ContainsID(series.ID().Bytes()) 156 require.NoError(t, err) 157 if exists { 158 continue // duplicate 159 } 160 _, err = seg.Insert(d) 161 require.NoError(t, err) 162 } 163 164 err = seg.Seal() 165 require.NoError(t, err) 166 167 pm := newTestOptionsWithPersistManager(t, dir).PersistManager() 168 flush, err := pm.StartIndexPersist() 169 require.NoError(t, err) 170 171 preparedPersist, err := flush.PrepareIndex(persist.IndexPrepareOptions{ 172 NamespaceMetadata: namespace, 173 BlockStart: start, 174 FileSetType: persist.FileSetFlushType, 175 Shards: shards, 176 }) 177 require.NoError(t, err) 178 179 err = preparedPersist.Persist(seg) 180 require.NoError(t, err) 181 182 result, err := preparedPersist.Close() 183 require.NoError(t, err) 184 185 for _, r := range result { 186 require.NoError(t, r.Close()) 187 } 188 189 err = flush.DoneIndex() 190 require.NoError(t, err) 191 } 192 193 type expectedTaggedSeries struct { 194 indexBlockStart xtime.UnixNano 195 series map[string]testSeries 196 } 197 198 func expectedTaggedSeriesWithOptions( 199 t require.TestingT, 200 start xtime.UnixNano, 201 opts testTimesOptions, 202 ) []expectedTaggedSeries { 203 dataBlocks := testGoodTaggedSeriesDataBlocks() 204 switch opts.numBlocks { 205 case 2: 206 return []expectedTaggedSeries{ 207 { 208 indexBlockStart: start, 209 series: map[string]testSeries{ 210 dataBlocks[0][0].id: dataBlocks[0][0], 211 dataBlocks[0][1].id: dataBlocks[0][1], 212 dataBlocks[0][2].id: dataBlocks[0][2], 213 dataBlocks[1][1].id: dataBlocks[1][1], 214 dataBlocks[1][2].id: dataBlocks[1][2], 215 }, 216 }, 217 { 218 indexBlockStart: start.Add(testIndexBlockSize), 219 series: map[string]testSeries{ 220 dataBlocks[2][0].id: dataBlocks[2][0], 221 dataBlocks[2][1].id: dataBlocks[2][1], 222 dataBlocks[2][2].id: dataBlocks[2][2], 223 }, 224 }, 225 } 226 case 3: 227 return []expectedTaggedSeries{ 228 { 229 indexBlockStart: start, 230 series: map[string]testSeries{ 231 dataBlocks[0][0].id: dataBlocks[0][0], 232 dataBlocks[0][1].id: dataBlocks[0][1], 233 dataBlocks[0][2].id: dataBlocks[0][2], 234 }, 235 }, 236 { 237 indexBlockStart: start.Add(testIndexBlockSize), 238 series: map[string]testSeries{ 239 dataBlocks[1][1].id: dataBlocks[1][1], 240 dataBlocks[1][2].id: dataBlocks[1][2], 241 dataBlocks[2][0].id: dataBlocks[2][0], 242 dataBlocks[2][1].id: dataBlocks[2][1], 243 dataBlocks[2][2].id: dataBlocks[2][2], 244 }, 245 }, 246 } 247 default: 248 require.FailNow(t, "unsupported test times options") 249 } 250 return nil 251 } 252 253 func validateGoodTaggedSeries( 254 t require.TestingT, 255 start xtime.UnixNano, 256 indexResults result.IndexResults, 257 opts testTimesOptions, 258 ) { 259 require.Equal(t, 2, len(indexResults)) 260 261 expectedSeriesByBlock := expectedTaggedSeriesWithOptions(t, start, opts) 262 for _, expected := range expectedSeriesByBlock { 263 expectedAt := expected.indexBlockStart 264 indexBlockByVolumeType, ok := indexResults[expectedAt] 265 require.True(t, ok) 266 for _, indexBlock := range indexBlockByVolumeType.Iter() { 267 require.Equal(t, 1, len(indexBlock.Segments())) 268 for _, seg := range indexBlock.Segments() { 269 reader, err := seg.Segment().Reader() 270 require.NoError(t, err) 271 272 docs, err := reader.AllDocs() 273 require.NoError(t, err) 274 275 matches := map[string]struct{}{} 276 for docs.Next() { 277 curr := docs.Current() 278 279 _, ok := matches[string(curr.ID)] 280 require.False(t, ok) 281 matches[string(curr.ID)] = struct{}{} 282 283 series, ok := expected.series[string(curr.ID)] 284 require.True(t, ok) 285 286 matchingTags := map[string]struct{}{} 287 for _, tag := range curr.Fields { 288 _, ok := matchingTags[string(tag.Name)] 289 require.False(t, ok) 290 matchingTags[string(tag.Name)] = struct{}{} 291 292 tagValue, ok := series.tags[string(tag.Name)] 293 require.True(t, ok) 294 295 require.Equal(t, tagValue, string(tag.Value)) 296 } 297 require.Equal(t, len(series.tags), len(matchingTags)) 298 } 299 require.NoError(t, docs.Err()) 300 require.NoError(t, docs.Close()) 301 302 require.Equal(t, len(expected.series), len(matches)) 303 } 304 } 305 } 306 } 307 308 func TestBootstrapIndex(t *testing.T) { 309 dir := createTempDir(t) 310 defer os.RemoveAll(dir) 311 312 timesOpts := testTimesOptions{ 313 numBlocks: 2, 314 } 315 times := newTestBootstrapIndexTimes(timesOpts) 316 317 writeTSDBGoodTaggedSeriesDataFiles(t, dir, testNs1ID, times.start) 318 319 opts := newTestOptionsWithPersistManager(t, dir) 320 scope := tally.NewTestScope("", nil) 321 opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetMetricsScope(scope)) 322 323 // Should always be run with persist enabled. 324 runOpts := testDefaultRunOpts. 325 SetPersistConfig(bootstrap.PersistConfig{Enabled: true}) 326 327 fsSrc, err := newFileSystemSource(opts) 328 require.NoError(t, err) 329 330 src, ok := fsSrc.(*fileSystemSource) 331 require.True(t, ok) 332 333 nsMD := testNsMetadata(t) 334 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, runOpts, 335 times.shardTimeRanges, opts.FilesystemOptions(), nsMD) 336 defer tester.Finish() 337 338 tester.TestReadWith(src) 339 indexResults := tester.ResultForNamespace(nsMD.ID()).IndexResult.IndexResults() 340 341 // Check that single persisted segment got written out 342 infoFiles := fs.ReadIndexInfoFiles(fs.ReadIndexInfoFilesOptions{ 343 FilePathPrefix: src.fsopts.FilePathPrefix(), 344 Namespace: testNs1ID, 345 ReaderBufferSize: src.fsopts.InfoReaderBufferSize(), 346 }) 347 require.Equal(t, 1, len(infoFiles)) 348 349 for _, infoFile := range infoFiles { 350 require.NoError(t, infoFile.Err.Error()) 351 require.Equal(t, times.start, xtime.UnixNano(infoFile.Info.BlockStart)) 352 require.Equal(t, testIndexBlockSize, time.Duration(infoFile.Info.BlockSize)) 353 require.Equal(t, persist.FileSetFlushType, persist.FileSetType(infoFile.Info.FileType)) 354 require.Equal(t, 1, len(infoFile.Info.Segments)) 355 require.Equal(t, 1, len(infoFile.Info.Shards)) 356 require.Equal(t, testShard, infoFile.Info.Shards[0]) 357 } 358 359 // Check that the segment is not a mutable segment for this block 360 blockByVolumeType, ok := indexResults[times.start] 361 require.True(t, ok) 362 block, ok := blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 363 require.True(t, ok) 364 require.Equal(t, 1, len(block.Segments())) 365 segment := block.Segments()[0] 366 require.True(t, ok) 367 require.True(t, segment.IsPersisted()) 368 369 // Check that the second segment is mutable and was not written out 370 blockByVolumeType, ok = indexResults[times.start.Add(testIndexBlockSize)] 371 require.True(t, ok) 372 block, ok = blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 373 require.True(t, ok) 374 require.Equal(t, 1, len(block.Segments())) 375 segment = block.Segments()[0] 376 require.True(t, ok) 377 require.False(t, segment.IsPersisted()) 378 379 // Validate results 380 validateGoodTaggedSeries(t, times.start, indexResults, timesOpts) 381 382 // Validate that wrote the block out (and no index blocks 383 // were read as existing index blocks on disk) 384 counters := scope.Snapshot().Counters() 385 require.Equal(t, int64(0), counters["fs-bootstrapper.persist-index-blocks-read+"].Value()) 386 require.Equal(t, int64(1), counters["fs-bootstrapper.persist-index-blocks-write+"].Value()) 387 } 388 389 func TestBootstrapIndexIgnoresPersistConfigIfSnapshotType(t *testing.T) { 390 dir := createTempDir(t) 391 defer os.RemoveAll(dir) 392 393 timesOpts := testTimesOptions{ 394 numBlocks: 2, 395 } 396 times := newTestBootstrapIndexTimes(timesOpts) 397 398 writeTSDBGoodTaggedSeriesDataFiles(t, dir, testNs1ID, times.start) 399 400 opts := newTestOptionsWithPersistManager(t, dir) 401 scope := tally.NewTestScope("", nil) 402 opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetMetricsScope(scope)) 403 404 runOpts := testDefaultRunOpts. 405 SetPersistConfig(bootstrap.PersistConfig{ 406 Enabled: true, 407 FileSetType: persist.FileSetSnapshotType, 408 }) 409 410 fsSrc, err := newFileSystemSource(opts) 411 require.NoError(t, err) 412 413 src, ok := fsSrc.(*fileSystemSource) 414 require.True(t, ok) 415 416 nsMD := testNsMetadata(t) 417 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, runOpts, 418 times.shardTimeRanges, opts.FilesystemOptions(), nsMD) 419 defer tester.Finish() 420 421 tester.TestReadWith(src) 422 indexResults := tester.ResultForNamespace(nsMD.ID()).IndexResult.IndexResults() 423 424 // Check that not segments were written out 425 infoFiles := fs.ReadIndexInfoFiles(fs.ReadIndexInfoFilesOptions{ 426 FilePathPrefix: src.fsopts.FilePathPrefix(), 427 Namespace: testNs1ID, 428 ReaderBufferSize: src.fsopts.InfoReaderBufferSize(), 429 }) 430 require.Equal(t, 0, len(infoFiles)) 431 432 // Check that both segments are mutable 433 blockByVolumeType, ok := indexResults[times.start] 434 require.True(t, ok) 435 block, ok := blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 436 require.True(t, ok) 437 require.Equal(t, 1, len(block.Segments())) 438 segment := block.Segments()[0] 439 require.True(t, ok) 440 require.False(t, segment.IsPersisted()) 441 442 blockByVolumeType, ok = indexResults[times.start.Add(testIndexBlockSize)] 443 require.True(t, ok) 444 block, ok = blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 445 require.True(t, ok) 446 require.Equal(t, 1, len(block.Segments())) 447 segment = block.Segments()[0] 448 require.True(t, ok) 449 require.False(t, segment.IsPersisted()) 450 451 // Validate results 452 validateGoodTaggedSeries(t, times.start, indexResults, timesOpts) 453 454 // Validate that no index blocks were read from disk and that no files were written out 455 counters := scope.Snapshot().Counters() 456 require.Equal(t, int64(0), counters["fs-bootstrapper.persist-index-blocks-read+"].Value()) 457 require.Equal(t, int64(0), counters["fs-bootstrapper.persist-index-blocks-write+"].Value()) 458 tester.EnsureNoWrites() 459 } 460 461 func TestBootstrapIndexWithPersistPrefersPersistedIndexBlocks(t *testing.T) { 462 dir := createTempDir(t) 463 defer os.RemoveAll(dir) 464 465 timesOpts := testTimesOptions{ 466 numBlocks: 2, 467 } 468 times := newTestBootstrapIndexTimes(timesOpts) 469 470 // Write data files 471 writeTSDBGoodTaggedSeriesDataFiles(t, dir, testNs1ID, times.start) 472 473 // Now write index block segment from first two data blocks 474 testData := testGoodTaggedSeriesDataBlocks() 475 shards := map[uint32]struct{}{testShard: {}} 476 writeTSDBPersistedIndexBlock(t, dir, testNsMetadata(t), times.start, shards, 477 append(testData[0], testData[1]...)) 478 479 opts := newTestOptionsWithPersistManager(t, dir) 480 scope := tally.NewTestScope("", nil) 481 opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetMetricsScope(scope)) 482 483 runOpts := testDefaultRunOpts. 484 SetPersistConfig(bootstrap.PersistConfig{Enabled: true}) 485 486 fsSrc, err := newFileSystemSource(opts) 487 require.NoError(t, err) 488 489 src, ok := fsSrc.(*fileSystemSource) 490 require.True(t, ok) 491 492 nsMD := testNsMetadata(t) 493 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, runOpts, 494 times.shardTimeRanges, opts.FilesystemOptions(), nsMD) 495 defer tester.Finish() 496 497 tester.TestReadWith(src) 498 indexResults := tester.ResultForNamespace(nsMD.ID()).IndexResult.IndexResults() 499 500 // Check that the segment is not a mutable segment for this block 501 // and came from disk 502 blockByVolumeType, ok := indexResults[times.start] 503 require.True(t, ok) 504 block, ok := blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 505 require.True(t, ok) 506 require.Equal(t, 1, len(block.Segments())) 507 segment := block.Segments()[0] 508 require.True(t, ok) 509 require.True(t, segment.IsPersisted()) 510 511 // Check that the second segment is mutable 512 blockByVolumeType, ok = indexResults[times.start.Add(testIndexBlockSize)] 513 require.True(t, ok) 514 block, ok = blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 515 require.True(t, ok) 516 require.Equal(t, 1, len(block.Segments())) 517 segment = block.Segments()[0] 518 require.True(t, ok) 519 require.False(t, segment.IsPersisted()) 520 521 // Validate results 522 validateGoodTaggedSeries(t, times.start, indexResults, timesOpts) 523 524 // Validate that read the block and no blocks were written 525 // (ensure persist config didn't write it back out again) 526 counters := scope.Snapshot().Counters() 527 require.Equal(t, int64(1), counters["fs-bootstrapper.persist-index-blocks-read+"].Value()) 528 require.Equal(t, int64(0), counters["fs-bootstrapper.persist-index-blocks-write+"].Value()) 529 tester.EnsureNoWrites() 530 } 531 532 // TODO: Make this test actually exercise the case at the retention edge, 533 // right now it only builds a partial segment for the second of three index 534 // blocks it is trying to build. 535 func TestBootstrapIndexWithPersistForIndexBlockAtRetentionEdge(t *testing.T) { 536 tests := []testOptions{ 537 { 538 name: "now", 539 now: time.Now(), 540 expectedInfoFiles: 2, 541 expectedSegmentsFirstBlock: 1, 542 expectedSegmentsSecondBlock: 1, 543 expectedOutOfRetentionBlocks: 0, 544 }, 545 { 546 name: "now + 4h", 547 now: time.Now().Add(time.Hour * 4), 548 expectedInfoFiles: 1, 549 expectedSegmentsFirstBlock: 0, 550 expectedSegmentsSecondBlock: 1, 551 expectedOutOfRetentionBlocks: 1, 552 }, 553 { 554 name: "now + 8h", 555 now: time.Now().Add(time.Hour * 8), 556 expectedInfoFiles: 0, 557 expectedSegmentsFirstBlock: 0, 558 expectedSegmentsSecondBlock: 0, 559 expectedOutOfRetentionBlocks: 2, 560 }, 561 } 562 for _, test := range tests { 563 // t.Run(test.name, func(t *testing.T) { 564 testBootstrapIndexWithPersistForIndexBlockAtRetentionEdge(t, test) 565 // }) 566 } 567 } 568 569 type testOptions struct { 570 name string 571 now time.Time 572 expectedInfoFiles int 573 expectedSegmentsFirstBlock int 574 expectedSegmentsSecondBlock int 575 expectedOutOfRetentionBlocks int64 576 } 577 578 func testBootstrapIndexWithPersistForIndexBlockAtRetentionEdge(t *testing.T, test testOptions) { 579 dir := createTempDir(t) 580 defer func() { 581 require.NoError(t, os.RemoveAll(dir)) 582 }() 583 584 timesOpts := testTimesOptions{ 585 numBlocks: 3, 586 } 587 times := newTestBootstrapIndexTimes(timesOpts) 588 firstIndexBlockStart := times.start.Truncate(testIndexBlockSize) 589 590 writeTSDBGoodTaggedSeriesDataFiles(t, dir, testNs1ID, times.start) 591 592 opts := newTestOptionsWithPersistManager(t, dir) 593 594 scope := tally.NewTestScope("", nil) 595 opts = opts. 596 SetInstrumentOptions(opts.InstrumentOptions().SetMetricsScope(scope)) 597 598 at := test.now 599 resultOpts := opts.ResultOptions() 600 clockOpts := resultOpts.ClockOptions(). 601 SetNowFn(func() time.Time { 602 return at 603 }) 604 opts = opts.SetResultOptions(resultOpts.SetClockOptions(clockOpts)) 605 606 runOpts := testDefaultRunOpts. 607 SetPersistConfig(bootstrap.PersistConfig{Enabled: true}) 608 609 fsSrc, err := newFileSystemSource(opts) 610 require.NoError(t, err) 611 612 src, ok := fsSrc.(*fileSystemSource) 613 require.True(t, ok) 614 615 retentionPeriod := testBlockSize 616 for { 617 // Make sure that retention is set to end half way through the first block 618 flushStart := retention.FlushTimeStartForRetentionPeriod( 619 retentionPeriod, testBlockSize, xtime.Now()) 620 if flushStart.Before(firstIndexBlockStart.Add(testIndexBlockSize)) { 621 break 622 } 623 retentionPeriod += testBlockSize 624 } 625 ropts := testRetentionOptions. 626 SetBlockSize(testBlockSize). 627 SetRetentionPeriod(retentionPeriod) 628 ns, err := namespace.NewMetadata(testNs1ID, testNamespaceOptions. 629 SetRetentionOptions(ropts). 630 SetIndexOptions(testNamespaceIndexOptions. 631 SetEnabled(true). 632 SetBlockSize(testIndexBlockSize))) 633 require.NoError(t, err) 634 635 // NB(bodu): Simulate requesting bootstrapping of two whole index blocks instead of 3 data blocks (1.5 index blocks). 636 times.shardTimeRanges = result.NewShardTimeRanges().Set( 637 testShard, 638 xtime.NewRanges(xtime.Range{ 639 Start: firstIndexBlockStart, 640 End: times.end, 641 }), 642 ) 643 tester := bootstrap.BuildNamespacesTesterWithFilesystemOptions(t, runOpts, 644 times.shardTimeRanges, opts.FilesystemOptions(), ns) 645 defer tester.Finish() 646 647 tester.TestReadWith(src) 648 indexResults := tester.ResultForNamespace(ns.ID()).IndexResult.IndexResults() 649 650 // Check that single persisted segment got written out 651 infoFiles := fs.ReadIndexInfoFiles(fs.ReadIndexInfoFilesOptions{ 652 FilePathPrefix: src.fsopts.FilePathPrefix(), 653 Namespace: testNs1ID, 654 ReaderBufferSize: src.fsopts.InfoReaderBufferSize(), 655 }) 656 require.Equal(t, test.expectedInfoFiles, len(infoFiles), "index info files") 657 658 for _, infoFile := range infoFiles { 659 require.NoError(t, infoFile.Err.Error()) 660 661 if infoFile.Info.BlockStart == int64(firstIndexBlockStart) { 662 expectedStart := times.end.Add(-2 * testIndexBlockSize) 663 require.Equal(t, int64(expectedStart), infoFile.Info.BlockStart, 664 fmt.Sprintf("expected=%s, actual=%v", 665 expectedStart, xtime.UnixNano(infoFile.Info.BlockStart))) 666 } else { 667 expectedStart := times.end.Add(-1 * testIndexBlockSize) 668 require.Equal(t, int64(expectedStart), infoFile.Info.BlockStart, 669 fmt.Sprintf("expected=%s, actual=%v", 670 expectedStart, xtime.UnixNano(infoFile.Info.BlockStart))) 671 } 672 673 require.Equal(t, testIndexBlockSize, time.Duration(infoFile.Info.BlockSize)) 674 require.Equal(t, persist.FileSetFlushType, persist.FileSetType(infoFile.Info.FileType)) 675 require.Equal(t, 1, len(infoFile.Info.Segments)) 676 require.Equal(t, 1, len(infoFile.Info.Shards)) 677 require.Equal(t, testShard, infoFile.Info.Shards[0]) 678 } 679 680 // Check that the segment is not a mutable segment 681 blockByVolumeType, ok := indexResults[firstIndexBlockStart] 682 require.True(t, ok) 683 block, ok := blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 684 require.True(t, ok) 685 require.Equal(t, test.expectedSegmentsFirstBlock, len(block.Segments()), "first block segment count") 686 if len(block.Segments()) > 0 { 687 segment := block.Segments()[0] 688 require.True(t, segment.IsPersisted(), "should be persisted") 689 } 690 691 // Check that the second is not a mutable segment 692 blockByVolumeType, ok = indexResults[firstIndexBlockStart.Add(testIndexBlockSize)] 693 require.True(t, ok) 694 block, ok = blockByVolumeType.GetBlock(idxpersist.DefaultIndexVolumeType) 695 require.True(t, ok) 696 require.Equal(t, test.expectedSegmentsSecondBlock, len(block.Segments()), "second block segment count") 697 if len(block.Segments()) > 0 { 698 segment := block.Segments()[0] 699 require.True(t, segment.IsPersisted(), "should be persisted") 700 } 701 702 // Validate results 703 if test.expectedSegmentsFirstBlock > 0 { 704 validateGoodTaggedSeries(t, firstIndexBlockStart, indexResults, timesOpts) 705 } 706 707 // Validate that wrote the block out (and no index blocks 708 // were read as existing index blocks on disk) 709 counters := scope.Snapshot().Counters() 710 require.Equal(t, int64(0), 711 counters["fs-bootstrapper.persist-index-blocks-read+"].Value(), "index blocks read") 712 require.Equal(t, int64(test.expectedInfoFiles), 713 counters["fs-bootstrapper.persist-index-blocks-write+"].Value(), "index blocks") 714 require.Equal(t, test.expectedOutOfRetentionBlocks, 715 counters["fs-bootstrapper.persist-index-blocks-out-of-retention+"].Value(), "out of retention blocks") 716 tester.EnsureNoWrites() 717 }