github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/series/series_test.go (about) 1 // Copyright (c) 2016 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 series 22 23 import ( 24 "errors" 25 "sort" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/encoding" 30 "github.com/m3db/m3/src/dbnode/encoding/m3tsz" 31 "github.com/m3db/m3/src/dbnode/namespace" 32 "github.com/m3db/m3/src/dbnode/persist" 33 "github.com/m3db/m3/src/dbnode/retention" 34 m3dbruntime "github.com/m3db/m3/src/dbnode/runtime" 35 "github.com/m3db/m3/src/dbnode/storage/block" 36 "github.com/m3db/m3/src/dbnode/storage/index/convert" 37 "github.com/m3db/m3/src/dbnode/ts" 38 "github.com/m3db/m3/src/dbnode/x/xio" 39 "github.com/m3db/m3/src/x/clock" 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 xtime "github.com/m3db/m3/src/x/time" 44 45 "github.com/golang/mock/gomock" 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 ) 49 50 func newSeriesTestOptions() Options { 51 encoderPool := encoding.NewEncoderPool(nil) 52 multiReaderIteratorPool := encoding.NewMultiReaderIteratorPool(nil) 53 54 encodingOpts := encoding.NewOptions().SetEncoderPool(encoderPool) 55 56 encoderPool.Init(func() encoding.Encoder { 57 return m3tsz.NewEncoder(0, nil, m3tsz.DefaultIntOptimizationEnabled, encodingOpts) 58 }) 59 multiReaderIteratorPool.Init(m3tsz.DefaultReaderIteratorAllocFn(encodingOpts)) 60 61 bufferBucketPool := NewBufferBucketPool(nil) 62 bufferBucketVersionsPool := NewBufferBucketVersionsPool(nil) 63 64 opts := NewOptions(). 65 SetEncoderPool(encoderPool). 66 SetMultiReaderIteratorPool(multiReaderIteratorPool). 67 SetBufferBucketPool(bufferBucketPool). 68 SetBufferBucketVersionsPool(bufferBucketVersionsPool). 69 SetRuntimeOptionsManager(m3dbruntime.NewOptionsManager()) 70 opts = opts. 71 SetRetentionOptions(opts. 72 RetentionOptions(). 73 SetBlockSize(2 * time.Minute). 74 SetBufferFuture(90 * time.Second). 75 SetBufferPast(90 * time.Second). 76 SetRetentionPeriod(time.Hour)). 77 SetDatabaseBlockOptions(opts. 78 DatabaseBlockOptions(). 79 SetContextPool(opts.ContextPool()). 80 SetEncoderPool(opts.EncoderPool())) 81 return opts 82 } 83 84 func TestSeriesEmpty(t *testing.T) { 85 opts := newSeriesTestOptions() 86 series := NewDatabaseSeries(DatabaseSeriesOptions{ 87 ID: ident.StringID("foo"), 88 Options: opts, 89 }) 90 assert.True(t, series.IsEmpty()) 91 nonEmptyBlocks := map[xtime.UnixNano]struct{}{} 92 series.MarkNonEmptyBlocks(nonEmptyBlocks) 93 assert.Empty(t, nonEmptyBlocks) 94 } 95 96 // Writes to series, verifying no error and that further writes should happen. 97 func verifyWriteToSeries(t *testing.T, series *dbSeries, v DecodedTestValue) { 98 ctx := context.NewBackground() 99 wasWritten, _, err := series.Write(ctx, v.Timestamp, v.Value, 100 v.Unit, v.Annotation, WriteOptions{}) 101 require.NoError(t, err) 102 require.True(t, wasWritten) 103 ctx.Close() 104 } 105 106 func TestSeriesWriteFlush(t *testing.T) { 107 opts := newSeriesTestOptions() 108 curr := xtime.Now().Truncate(opts.RetentionOptions().BlockSize()) 109 start := curr 110 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 111 return curr.ToTime() 112 })) 113 114 ctrl := gomock.NewController(t) 115 defer ctrl.Finish() 116 117 bl := block.NewMockDatabaseBlock(ctrl) 118 bl.EXPECT().StartTime().Return(curr).Times(2) 119 bl.EXPECT().Stream(gomock.Any()).Return(xio.BlockReader{}, nil) 120 bl.EXPECT().Close() 121 122 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 123 blockRetriever.EXPECT().IsBlockRetrievable(gomock.Any()).Return(false, nil) 124 125 series := NewDatabaseSeries(DatabaseSeriesOptions{ 126 ID: ident.StringID("foo"), 127 BlockRetriever: blockRetriever, 128 Options: opts, 129 }).(*dbSeries) 130 err := series.LoadBlock(bl, WarmWrite) 131 assert.NoError(t, err) 132 133 data := []DecodedTestValue{ 134 {curr, 1, xtime.Second, nil}, 135 {curr.Add(mins(1)), 2, xtime.Second, nil}, 136 {curr.Add(mins(2)), 3, xtime.Second, nil}, 137 {curr.Add(mins(3)), 4, xtime.Second, nil}, 138 } 139 140 for _, v := range data { 141 curr = v.Timestamp 142 verifyWriteToSeries(t, series, v) 143 } 144 145 ctx := context.NewBackground() 146 defer ctx.Close() 147 148 buckets, exists := series.buffer.(*dbBuffer).bucketVersionsAt(start) 149 require.True(t, exists) 150 streams, err := buckets.mergeToStreams(ctx, streamsOptions{filterWriteType: false}) 151 require.NoError(t, err) 152 require.Len(t, streams, 1) 153 requireSegmentValuesEqual(t, data[:2], streams, opts, namespace.Context{}) 154 requireBlockNotEmpty(t, series, start) 155 } 156 157 func TestSeriesSamePointDoesNotWrite(t *testing.T) { 158 opts := newSeriesTestOptions() 159 rops := opts.RetentionOptions() 160 curr := xtime.Now().Truncate(rops.BlockSize()) 161 start := curr 162 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 163 return curr.ToTime() 164 })) 165 166 ctrl := gomock.NewController(t) 167 defer ctrl.Finish() 168 169 bl := block.NewMockDatabaseBlock(ctrl) 170 bl.EXPECT().StartTime().Return(curr).Times(2) 171 bl.EXPECT().Stream(gomock.Any()).Return(xio.BlockReader{}, nil) 172 bl.EXPECT().Close() 173 174 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 175 blockRetriever.EXPECT().IsBlockRetrievable(gomock.Any()).Return(false, nil) 176 177 series := NewDatabaseSeries(DatabaseSeriesOptions{ 178 ID: ident.StringID("foo"), 179 BlockRetriever: blockRetriever, 180 Options: opts, 181 }).(*dbSeries) 182 183 err := series.LoadBlock(bl, WarmWrite) 184 assert.NoError(t, err) 185 186 data := []DecodedTestValue{ 187 {curr, 1, xtime.Second, nil}, 188 {curr, 1, xtime.Second, nil}, 189 {curr, 1, xtime.Second, nil}, 190 {curr, 1, xtime.Second, nil}, 191 {curr.Add(rops.BlockSize()).Add(rops.BufferPast() * 2), 1, xtime.Second, nil}, 192 } 193 194 for i, v := range data { 195 curr = v.Timestamp 196 ctx := context.NewBackground() 197 wasWritten, _, err := series.Write(ctx, v.Timestamp, v.Value, v.Unit, v.Annotation, WriteOptions{}) 198 require.NoError(t, err) 199 if i == 0 || i == len(data)-1 { 200 require.True(t, wasWritten) 201 } else { 202 require.False(t, wasWritten) 203 } 204 ctx.Close() 205 } 206 207 ctx := context.NewBackground() 208 defer ctx.Close() 209 210 buckets, exists := series.buffer.(*dbBuffer).bucketVersionsAt(start) 211 require.True(t, exists) 212 streams, err := buckets.mergeToStreams(ctx, streamsOptions{filterWriteType: false}) 213 require.NoError(t, err) 214 require.Len(t, streams, 1) 215 requireSegmentValuesEqual(t, data[:1], streams, opts, namespace.Context{}) 216 } 217 218 func TestSeriesWriteFlushRead(t *testing.T) { 219 opts := newSeriesTestOptions() 220 curr := xtime.Now().Truncate(opts.RetentionOptions().BlockSize()) 221 start := curr 222 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 223 return curr.ToTime() 224 })) 225 226 ctrl := gomock.NewController(t) 227 defer ctrl.Finish() 228 229 bl := block.NewMockDatabaseBlock(ctrl) 230 bl.EXPECT().StartTime().Return(curr).Times(2) 231 bl.EXPECT().Len().Return(0).Times(2) 232 233 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 234 blockRetriever.EXPECT(). 235 IsBlockRetrievable(gomock.Any()). 236 Return(false, nil). 237 AnyTimes() 238 239 series := NewDatabaseSeries(DatabaseSeriesOptions{ 240 ID: ident.StringID("foo"), 241 BlockRetriever: blockRetriever, 242 Options: opts, 243 }).(*dbSeries) 244 245 err := series.LoadBlock(bl, WarmWrite) 246 assert.NoError(t, err) 247 248 data := []DecodedTestValue{ 249 {curr.Add(mins(1)), 2, xtime.Second, nil}, 250 {curr.Add(mins(3)), 3, xtime.Second, nil}, 251 {curr.Add(mins(5)), 4, xtime.Second, nil}, 252 {curr.Add(mins(7)), 5, xtime.Second, nil}, 253 {curr.Add(mins(9)), 6, xtime.Second, nil}, 254 } 255 256 for _, v := range data { 257 curr = v.Timestamp 258 verifyWriteToSeries(t, series, v) 259 } 260 261 ctx := context.NewBackground() 262 defer ctx.Close() 263 nsCtx := namespace.Context{} 264 265 // Test fine grained range 266 iter, err := series.ReadEncoded(ctx, start, start.Add(mins(10)), nsCtx) 267 assert.NoError(t, err) 268 results, err := iter.ToSlices(ctx) 269 assert.NoError(t, err) 270 271 requireReaderValuesEqual(t, data, results, opts, nsCtx) 272 273 // Test wide range 274 iter, err = series.ReadEncoded(ctx, 0, timeDistantFuture, nsCtx) 275 assert.NoError(t, err) 276 results, err = iter.ToSlices(ctx) 277 assert.NoError(t, err) 278 279 requireReaderValuesEqual(t, data, results, opts, nsCtx) 280 } 281 282 // TestSeriesLoad tests the behavior the Bootstrap()/Load()s method by ensuring that they actually load 283 // data into the series and that the data (merged with any existing data) can be retrieved. 284 // 285 // It also ensures that blocks for the bootstrap path blockStarts that have not been warm flushed yet 286 // are loaded as warm writes and block for blockStarts that have already been warm flushed are loaded as 287 // cold writes and that for the load path everything is loaded as cold writes. 288 func TestSeriesBootstrapAndLoad(t *testing.T) { 289 testCases := []struct { 290 title string 291 bootstrapping bool 292 }{ 293 { 294 title: "load", 295 bootstrapping: false, 296 }, 297 { 298 title: "bootstrap", 299 bootstrapping: true, 300 }, 301 } 302 303 for _, tc := range testCases { 304 t.Run(tc.title, func(t *testing.T) { 305 ctrl := gomock.NewController(t) 306 defer ctrl.Finish() 307 308 var ( 309 opts = newSeriesTestOptions() 310 blockSize = opts.RetentionOptions().BlockSize() 311 curr = xtime.Now().Truncate(blockSize) 312 start = curr 313 ) 314 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 315 return curr.ToTime() 316 })) 317 318 var ( 319 loadWrites = []DecodedTestValue{ 320 // Ensure each value is in a separate block so since block.DatabaseSeriesBlocks 321 // can only store a single block per block start). 322 {curr.Add(blockSize), 5, xtime.Second, nil}, 323 {curr.Add(2 * blockSize), 6, xtime.Second, nil}, 324 } 325 nsCtx = namespace.Context{} 326 blockOpts = opts.DatabaseBlockOptions() 327 alreadyWarmFlushedBlockStart = curr.Add(blockSize).Truncate(blockSize) 328 notYetWarmFlushedBlockStart = curr.Add(2 * blockSize).Truncate(blockSize) 329 blockStates = BootstrappedBlockStateSnapshot{ 330 Snapshot: map[xtime.UnixNano]BlockState{ 331 // Exercise both code paths. 332 alreadyWarmFlushedBlockStart: { 333 WarmRetrievable: true, 334 }, 335 notYetWarmFlushedBlockStart: { 336 WarmRetrievable: false, 337 }, 338 }, 339 } 340 ) 341 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 342 blockRetriever.EXPECT(). 343 IsBlockRetrievable(gomock.Any()). 344 DoAndReturn(func(at xtime.UnixNano) (bool, error) { 345 value, exists := blockStates.Snapshot[at] 346 if !exists { 347 // No block exists, should be a warm write. 348 return false, nil 349 } 350 return value.WarmRetrievable, nil 351 }). 352 AnyTimes() 353 blockRetriever.EXPECT(). 354 Stream(gomock.Any(), gomock.Any(), gomock.Any(), 355 gomock.Any(), gomock.Any()). 356 Return(xio.EmptyBlockReader, nil). 357 AnyTimes() 358 359 series := NewDatabaseSeries(DatabaseSeriesOptions{ 360 ID: ident.StringID("foo"), 361 BlockRetriever: blockRetriever, 362 Options: opts, 363 }).(*dbSeries) 364 365 rawWrites := []DecodedTestValue{ 366 {curr.Add(mins(1)), 2, xtime.Second, nil}, 367 {curr.Add(mins(3)), 3, xtime.Second, nil}, 368 {curr.Add(mins(5)), 4, xtime.Second, nil}, 369 } 370 371 for _, v := range rawWrites { 372 curr = v.Timestamp 373 verifyWriteToSeries(t, series, v) 374 } 375 376 for _, v := range loadWrites { 377 curr = v.Timestamp 378 enc := opts.EncoderPool().Get() 379 blockStart := v.Timestamp.Truncate(blockSize) 380 enc.Reset(blockStart, 0, nil) 381 dp := ts.Datapoint{TimestampNanos: v.Timestamp, Value: v.Value} 382 require.NoError(t, enc.Encode(dp, v.Unit, nil)) 383 384 dbBlock := block.NewDatabaseBlock(blockStart, blockSize, enc.Discard(), blockOpts, nsCtx) 385 386 writeType := ColdWrite 387 if tc.bootstrapping { 388 if blockStart.Equal(notYetWarmFlushedBlockStart) { 389 writeType = WarmWrite 390 } 391 } 392 393 err := series.LoadBlock(dbBlock, writeType) 394 require.NoError(t, err) 395 requireBlockNotEmpty(t, series, blockStart) 396 } 397 398 t.Run("Data can be read", func(t *testing.T) { 399 ctx := context.NewBackground() 400 defer ctx.Close() 401 402 iter, err := series.ReadEncoded(ctx, start, start.Add(10*blockSize), nsCtx) 403 require.NoError(t, err) 404 results, err := iter.ToSlices(ctx) 405 require.NoError(t, err) 406 407 var expectedData []DecodedTestValue 408 expectedData = append(expectedData, rawWrites...) 409 expectedData = append(expectedData, loadWrites...) 410 sort.Sort(ValuesByTime(expectedData)) 411 requireReaderValuesEqual(t, expectedData, results, opts, nsCtx) 412 }) 413 414 t.Run("blocks loaded as warm/cold writes correctly", func(t *testing.T) { 415 optimizedTimes := series.ColdFlushBlockStarts(blockStates) 416 coldFlushBlockStarts := []xtime.UnixNano{} 417 optimizedTimes.ForEach(func(blockStart xtime.UnixNano) { 418 coldFlushBlockStarts = append(coldFlushBlockStarts, blockStart) 419 }) 420 // Cold flush block starts don't come back in any particular order so 421 // sort them for easier comparisons. 422 sort.Slice(coldFlushBlockStarts, func(i, j int) bool { 423 return coldFlushBlockStarts[i] < coldFlushBlockStarts[j] 424 }) 425 426 if tc.bootstrapping { 427 // If its a bootstrap then we need to make sure that everything gets loaded as warm/cold writes 428 // correctly based on the flush state. 429 expectedColdFlushBlockStarts := []xtime.UnixNano{alreadyWarmFlushedBlockStart} 430 assert.Equal(t, expectedColdFlushBlockStarts, coldFlushBlockStarts) 431 } else { 432 // If its just a regular load then everything should be loaded as cold writes for correctness 433 // since flushes and loads can happen concurrently. 434 expectedColdFlushBlockStarts := []xtime.UnixNano{ 435 alreadyWarmFlushedBlockStart, 436 notYetWarmFlushedBlockStart, 437 } 438 assert.Equal(t, expectedColdFlushBlockStarts, coldFlushBlockStarts) 439 } 440 }) 441 }) 442 } 443 } 444 445 func TestSeriesReadEndBeforeStart(t *testing.T) { 446 opts := newSeriesTestOptions() 447 448 ctrl := gomock.NewController(t) 449 defer ctrl.Finish() 450 451 bl := block.NewMockDatabaseBlock(ctrl) 452 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 453 454 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 455 blockRetriever.EXPECT(). 456 IsBlockRetrievable(gomock.Any()). 457 Return(false, nil). 458 AnyTimes() 459 460 series := NewDatabaseSeries(DatabaseSeriesOptions{ 461 ID: ident.StringID("foo"), 462 BlockRetriever: blockRetriever, 463 Options: opts, 464 }) 465 466 err := series.LoadBlock(bl, WarmWrite) 467 assert.NoError(t, err) 468 469 ctx := context.NewBackground() 470 defer ctx.Close() 471 nsCtx := namespace.Context{} 472 473 now := xtime.Now() 474 iter, err := series.ReadEncoded(ctx, now, now.Add(-1*time.Second), nsCtx) 475 assert.Error(t, err) 476 assert.True(t, xerrors.IsInvalidParams(err)) 477 assert.Nil(t, iter) 478 } 479 480 func TestSeriesFlushNoBlock(t *testing.T) { 481 ctrl := gomock.NewController(t) 482 defer ctrl.Finish() 483 484 bl := block.NewMockDatabaseBlock(ctrl) 485 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 486 487 opts := newSeriesTestOptions() 488 489 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 490 blockRetriever.EXPECT(). 491 IsBlockRetrievable(gomock.Any()). 492 Return(false, nil). 493 AnyTimes() 494 495 series := NewDatabaseSeries(DatabaseSeriesOptions{ 496 ID: ident.StringID("foo"), 497 BlockRetriever: blockRetriever, 498 Options: opts, 499 }).(*dbSeries) 500 501 err := series.LoadBlock(bl, WarmWrite) 502 require.NoError(t, err) 503 504 flushTime := xtime.FromSeconds(7200) 505 outcome, err := series.WarmFlush(nil, flushTime, nil, namespace.Context{}) 506 require.Nil(t, err) 507 require.Equal(t, FlushOutcomeBlockDoesNotExist, outcome) 508 } 509 510 func TestSeriesFlush(t *testing.T) { 511 ctrl := gomock.NewController(t) 512 defer ctrl.Finish() 513 514 bl := block.NewMockDatabaseBlock(ctrl) 515 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 516 517 curr := xtime.FromSeconds(7200) 518 opts := newSeriesTestOptions() 519 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 520 return curr.ToTime() 521 })) 522 523 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 524 blockRetriever.EXPECT(). 525 IsBlockRetrievable(gomock.Any()). 526 Return(false, nil). 527 AnyTimes() 528 529 series := NewDatabaseSeries(DatabaseSeriesOptions{ 530 BlockRetriever: blockRetriever, 531 Options: opts, 532 }).(*dbSeries) 533 534 err := series.LoadBlock(bl, WarmWrite) 535 assert.NoError(t, err) 536 537 ctx := context.NewBackground() 538 series.buffer.Write(ctx, testID, curr, 1234, xtime.Second, nil, WriteOptions{}) 539 ctx.BlockingClose() 540 541 inputs := []error{errors.New("some error"), nil} 542 for _, input := range inputs { 543 persistFn := func(_ persist.Metadata, _ ts.Segment, _ uint32) error { 544 return input 545 } 546 ctx := context.NewBackground() 547 outcome, err := series.WarmFlush(ctx, curr, persistFn, namespace.Context{}) 548 ctx.BlockingClose() 549 require.Equal(t, input, err) 550 if input == nil { 551 require.Equal(t, FlushOutcomeFlushedToDisk, outcome) 552 } else { 553 require.Equal(t, FlushOutcomeErr, outcome) 554 } 555 } 556 } 557 558 func TestSeriesTickEmptySeries(t *testing.T) { 559 opts := newSeriesTestOptions() 560 series := NewDatabaseSeries(DatabaseSeriesOptions{ 561 ID: ident.StringID("foo"), 562 Options: opts, 563 }).(*dbSeries) 564 _, err := series.Tick(NewShardBlockStateSnapshot(true, BootstrappedBlockStateSnapshot{}), namespace.Context{}) 565 require.Equal(t, ErrSeriesAllDatapointsExpired, err) 566 } 567 568 func TestSeriesTickDrainAndResetBuffer(t *testing.T) { 569 ctrl := gomock.NewController(t) 570 defer ctrl.Finish() 571 572 bl := block.NewMockDatabaseBlock(ctrl) 573 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 574 575 opts := newSeriesTestOptions() 576 577 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 578 blockRetriever.EXPECT(). 579 IsBlockRetrievable(gomock.Any()). 580 Return(false, nil). 581 AnyTimes() 582 583 series := NewDatabaseSeries(DatabaseSeriesOptions{ 584 ID: ident.StringID("foo"), 585 BlockRetriever: blockRetriever, 586 Options: opts, 587 }).(*dbSeries) 588 589 err := series.LoadBlock(bl, WarmWrite) 590 require.NoError(t, err) 591 592 buffer := NewMockdatabaseBuffer(ctrl) 593 series.buffer = buffer 594 buffer.EXPECT().Tick(gomock.Any(), gomock.Any()).Return(bufferTickResult{}) 595 buffer.EXPECT().Stats().Return(bufferStats{wiredBlocks: 1}) 596 r, err := series.Tick(NewShardBlockStateSnapshot(true, BootstrappedBlockStateSnapshot{}), namespace.Context{}) 597 require.NoError(t, err) 598 assert.Equal(t, 1, r.ActiveBlocks) 599 assert.Equal(t, 1, r.WiredBlocks) 600 assert.Equal(t, 0, r.UnwiredBlocks) 601 } 602 603 func TestSeriesTickNeedsBlockExpiry(t *testing.T) { 604 ctrl := gomock.NewController(t) 605 defer ctrl.Finish() 606 607 bl := block.NewMockDatabaseBlock(ctrl) 608 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 609 610 opts := newSeriesTestOptions() 611 opts = opts.SetCachePolicy(CacheRecentlyRead) 612 ropts := opts.RetentionOptions() 613 curr := xtime.Now().Truncate(ropts.BlockSize()) 614 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 615 return curr.ToTime() 616 })) 617 618 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 619 blockRetriever.EXPECT(). 620 IsBlockRetrievable(gomock.Any()). 621 Return(false, nil). 622 AnyTimes() 623 624 series := NewDatabaseSeries(DatabaseSeriesOptions{ 625 ID: ident.StringID("foo"), 626 BlockRetriever: blockRetriever, 627 Options: opts, 628 }).(*dbSeries) 629 630 err := series.LoadBlock(bl, WarmWrite) 631 require.NoError(t, err) 632 633 blockStart := curr.Add(-ropts.RetentionPeriod()).Add(-ropts.BlockSize()) 634 b := block.NewMockDatabaseBlock(ctrl) 635 b.EXPECT().StartTime().Return(blockStart) 636 b.EXPECT().Close() 637 series.cachedBlocks.AddBlock(b) 638 b = block.NewMockDatabaseBlock(ctrl) 639 b.EXPECT().StartTime().Return(curr) 640 b.EXPECT().HasMergeTarget().Return(false) 641 series.cachedBlocks.AddBlock(b) 642 require.Equal(t, blockStart, series.cachedBlocks.MinTime()) 643 require.Equal(t, 2, series.cachedBlocks.Len()) 644 buffer := NewMockdatabaseBuffer(ctrl) 645 series.buffer = buffer 646 buffer.EXPECT().Tick(gomock.Any(), gomock.Any()).Return(bufferTickResult{}) 647 buffer.EXPECT().Stats().Return(bufferStats{wiredBlocks: 1}) 648 blockStates := BootstrappedBlockStateSnapshot{ 649 Snapshot: map[xtime.UnixNano]BlockState{ 650 blockStart: { 651 WarmRetrievable: false, 652 ColdVersion: 0, 653 }, 654 curr: { 655 WarmRetrievable: false, 656 ColdVersion: 0, 657 }, 658 }, 659 } 660 r, err := series.Tick(NewShardBlockStateSnapshot(true, blockStates), namespace.Context{}) 661 require.NoError(t, err) 662 require.Equal(t, 2, r.ActiveBlocks) 663 require.Equal(t, 2, r.WiredBlocks) 664 require.Equal(t, 1, r.MadeExpiredBlocks) 665 require.Equal(t, 1, series.cachedBlocks.Len()) 666 require.Equal(t, curr, series.cachedBlocks.MinTime()) 667 _, exists := series.cachedBlocks.AllBlocks()[curr] 668 require.True(t, exists) 669 } 670 671 func TestSeriesTickRecentlyRead(t *testing.T) { 672 ctrl := gomock.NewController(t) 673 defer ctrl.Finish() 674 675 bl := block.NewMockDatabaseBlock(ctrl) 676 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 677 678 opts := newSeriesTestOptions() 679 opts = opts. 680 SetCachePolicy(CacheRecentlyRead). 681 SetRetentionOptions(opts.RetentionOptions().SetBlockDataExpiryAfterNotAccessedPeriod(10 * time.Minute)) 682 ropts := opts.RetentionOptions() 683 curr := xtime.Now().Truncate(ropts.BlockSize()) 684 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 685 return curr.ToTime() 686 })) 687 688 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 689 blockRetriever.EXPECT(). 690 IsBlockRetrievable(gomock.Any()). 691 Return(false, nil). 692 AnyTimes() 693 694 series := NewDatabaseSeries(DatabaseSeriesOptions{ 695 ID: ident.StringID("foo"), 696 BlockRetriever: blockRetriever, 697 Options: opts, 698 }).(*dbSeries) 699 700 err := series.LoadBlock(bl, WarmWrite) 701 require.NoError(t, err) 702 703 // Test case where block has been read within expiry period - won't be removed 704 b := block.NewMockDatabaseBlock(ctrl) 705 b.EXPECT().StartTime().Return(curr) 706 b.EXPECT().LastReadTime().Return( 707 curr.Add(-opts.RetentionOptions().BlockDataExpiryAfterNotAccessedPeriod() / 2)) 708 b.EXPECT().HasMergeTarget().Return(true) 709 series.cachedBlocks.AddBlock(b) 710 711 blockStates := BootstrappedBlockStateSnapshot{ 712 Snapshot: map[xtime.UnixNano]BlockState{ 713 curr: { 714 WarmRetrievable: true, 715 ColdVersion: 1, 716 }, 717 }, 718 } 719 shardBlockStates := NewShardBlockStateSnapshot(true, blockStates) 720 tickResult, err := series.Tick(shardBlockStates, namespace.Context{}) 721 require.NoError(t, err) 722 require.Equal(t, 0, tickResult.UnwiredBlocks) 723 require.Equal(t, 1, tickResult.PendingMergeBlocks) 724 725 // Test case where block has not been read within expiry period - will be removed 726 b = block.NewMockDatabaseBlock(ctrl) 727 b.EXPECT().StartTime().Return(curr) 728 b.EXPECT().LastReadTime().Return( 729 curr.Add(-opts.RetentionOptions().BlockDataExpiryAfterNotAccessedPeriod() * 2)) 730 b.EXPECT().Close().Return() 731 series.cachedBlocks.AddBlock(b) 732 733 tickResult, err = series.Tick(shardBlockStates, namespace.Context{}) 734 require.NoError(t, err) 735 require.Equal(t, 1, tickResult.UnwiredBlocks) 736 require.Equal(t, 0, tickResult.PendingMergeBlocks) 737 738 // Test case where block is not flushed yet (not retrievable) - Will not be removed 739 b = block.NewMockDatabaseBlock(ctrl) 740 b.EXPECT().StartTime().Return(curr) 741 b.EXPECT().HasMergeTarget().Return(true) 742 series.cachedBlocks.AddBlock(b) 743 744 blockStates = BootstrappedBlockStateSnapshot{ 745 Snapshot: map[xtime.UnixNano]BlockState{ 746 curr: { 747 WarmRetrievable: false, 748 ColdVersion: 0, 749 }, 750 }, 751 } 752 shardBlockStates = NewShardBlockStateSnapshot(true, blockStates) 753 tickResult, err = series.Tick(shardBlockStates, namespace.Context{}) 754 require.NoError(t, err) 755 require.Equal(t, 0, tickResult.UnwiredBlocks) 756 require.Equal(t, 1, tickResult.PendingMergeBlocks) 757 } 758 759 func TestSeriesTickCacheLRU(t *testing.T) { 760 ctrl := gomock.NewController(t) 761 defer ctrl.Finish() 762 763 bl := block.NewMockDatabaseBlock(ctrl) 764 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 765 766 retentionPeriod := time.Hour 767 opts := newSeriesTestOptions() 768 opts = opts. 769 SetCachePolicy(CacheLRU). 770 SetRetentionOptions(opts.RetentionOptions().SetRetentionPeriod(retentionPeriod)) 771 ropts := opts.RetentionOptions() 772 curr := xtime.Now().Truncate(ropts.BlockSize()) 773 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 774 return curr.ToTime() 775 })) 776 777 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 778 blockRetriever.EXPECT(). 779 IsBlockRetrievable(gomock.Any()). 780 Return(false, nil). 781 AnyTimes() 782 783 series := NewDatabaseSeries(DatabaseSeriesOptions{ 784 ID: ident.StringID("foo"), 785 BlockRetriever: blockRetriever, 786 Options: opts, 787 }).(*dbSeries) 788 789 err := series.LoadBlock(bl, WarmWrite) 790 require.NoError(t, err) 791 792 // Test case where block was not retrieved from disk - Will be removed 793 b := block.NewMockDatabaseBlock(ctrl) 794 b.EXPECT().StartTime().Return(curr) 795 b.EXPECT().WasRetrievedFromDisk().Return(false) 796 b.EXPECT().Close().Return() 797 series.cachedBlocks.AddBlock(b) 798 799 blockStates := BootstrappedBlockStateSnapshot{ 800 Snapshot: map[xtime.UnixNano]BlockState{ 801 curr: { 802 WarmRetrievable: true, 803 ColdVersion: 1, 804 }, 805 }, 806 } 807 shardBlockStates := NewShardBlockStateSnapshot(true, blockStates) 808 tickResult, err := series.Tick(shardBlockStates, namespace.Context{}) 809 require.NoError(t, err) 810 require.Equal(t, 1, tickResult.UnwiredBlocks) 811 require.Equal(t, 0, tickResult.PendingMergeBlocks) 812 813 // Test case where block was retrieved from disk - Will not be removed 814 b = block.NewMockDatabaseBlock(ctrl) 815 b.EXPECT().StartTime().Return(curr) 816 b.EXPECT().HasMergeTarget().Return(true) 817 b.EXPECT().WasRetrievedFromDisk().Return(true) 818 series.cachedBlocks.AddBlock(b) 819 820 tickResult, err = series.Tick(shardBlockStates, namespace.Context{}) 821 require.NoError(t, err) 822 require.Equal(t, 0, tickResult.UnwiredBlocks) 823 require.Equal(t, 1, tickResult.PendingMergeBlocks) 824 825 // Test case where block is not flushed yet (not retrievable) - Will not be removed 826 b = block.NewMockDatabaseBlock(ctrl) 827 b.EXPECT().StartTime().Return(curr) 828 b.EXPECT().HasMergeTarget().Return(true) 829 series.cachedBlocks.AddBlock(b) 830 831 // Test case where block was retrieved from disk and is out of retention. Will be removed, but not closed. 832 b = block.NewMockDatabaseBlock(ctrl) 833 b.EXPECT().StartTime().Return(curr.Add(-2 * retentionPeriod)) 834 b.EXPECT().WasRetrievedFromDisk().Return(true) 835 series.cachedBlocks.AddBlock(b) 836 _, expiredBlockExists := series.cachedBlocks.BlockAt(curr.Add(-2 * retentionPeriod)) 837 require.Equal(t, true, expiredBlockExists) 838 839 blockStates = BootstrappedBlockStateSnapshot{ 840 Snapshot: map[xtime.UnixNano]BlockState{ 841 curr: { 842 WarmRetrievable: false, 843 ColdVersion: 0, 844 }, 845 }, 846 } 847 shardBlockStates = NewShardBlockStateSnapshot(true, blockStates) 848 tickResult, err = series.Tick(shardBlockStates, namespace.Context{}) 849 require.NoError(t, err) 850 require.Equal(t, 0, tickResult.UnwiredBlocks) 851 require.Equal(t, 1, tickResult.PendingMergeBlocks) 852 _, expiredBlockExists = series.cachedBlocks.BlockAt(curr.Add(-2 * retentionPeriod)) 853 require.Equal(t, false, expiredBlockExists) 854 } 855 856 func TestSeriesTickCacheNone(t *testing.T) { 857 ctrl := gomock.NewController(t) 858 defer ctrl.Finish() 859 860 bl := block.NewMockDatabaseBlock(ctrl) 861 bl.EXPECT().StartTime().Return(xtime.Now()).Times(2) 862 863 opts := newSeriesTestOptions() 864 opts = opts. 865 SetCachePolicy(CacheNone). 866 SetRetentionOptions(opts.RetentionOptions().SetBlockDataExpiryAfterNotAccessedPeriod(10 * time.Minute)) 867 ropts := opts.RetentionOptions() 868 curr := xtime.Now().Truncate(ropts.BlockSize()) 869 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 870 return curr.ToTime() 871 })) 872 873 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 874 blockRetriever.EXPECT(). 875 IsBlockRetrievable(gomock.Any()). 876 Return(false, nil). 877 AnyTimes() 878 879 series := NewDatabaseSeries(DatabaseSeriesOptions{ 880 ID: ident.StringID("foo"), 881 BlockRetriever: blockRetriever, 882 Options: opts, 883 }).(*dbSeries) 884 885 err := series.LoadBlock(bl, WarmWrite) 886 require.NoError(t, err) 887 888 // Retrievable blocks should be removed 889 b := block.NewMockDatabaseBlock(ctrl) 890 b.EXPECT().StartTime().Return(curr) 891 b.EXPECT().Close().Return() 892 series.cachedBlocks.AddBlock(b) 893 894 blockStates := BootstrappedBlockStateSnapshot{ 895 Snapshot: map[xtime.UnixNano]BlockState{ 896 curr: { 897 WarmRetrievable: true, 898 ColdVersion: 1, 899 }, 900 }, 901 } 902 shardBlockStates := NewShardBlockStateSnapshot(true, blockStates) 903 tickResult, err := series.Tick(shardBlockStates, namespace.Context{}) 904 require.NoError(t, err) 905 require.Equal(t, 1, tickResult.UnwiredBlocks) 906 require.Equal(t, 0, tickResult.PendingMergeBlocks) 907 908 // Non-retrievable blocks should not be removed 909 b = block.NewMockDatabaseBlock(ctrl) 910 b.EXPECT().StartTime().Return(curr) 911 b.EXPECT().HasMergeTarget().Return(true) 912 series.cachedBlocks.AddBlock(b) 913 914 blockStates = BootstrappedBlockStateSnapshot{ 915 Snapshot: map[xtime.UnixNano]BlockState{ 916 curr: { 917 WarmRetrievable: false, 918 ColdVersion: 0, 919 }, 920 }, 921 } 922 shardBlockStates = NewShardBlockStateSnapshot(true, blockStates) 923 tickResult, err = series.Tick(shardBlockStates, namespace.Context{}) 924 require.NoError(t, err) 925 require.Equal(t, 0, tickResult.UnwiredBlocks) 926 require.Equal(t, 1, tickResult.PendingMergeBlocks) 927 } 928 929 func TestSeriesTickCachedBlockRemove(t *testing.T) { 930 ctrl := gomock.NewController(t) 931 defer ctrl.Finish() 932 933 opts := newSeriesTestOptions() 934 opts = opts.SetCachePolicy(CacheAll) 935 ropts := opts.RetentionOptions() 936 curr := xtime.Now().Truncate(ropts.BlockSize()) 937 series := NewDatabaseSeries(DatabaseSeriesOptions{ 938 ID: ident.StringID("foo"), 939 Options: opts, 940 }).(*dbSeries) 941 942 // Add current block 943 b := block.NewMockDatabaseBlock(ctrl) 944 b.EXPECT().StartTime().Return(curr) 945 series.cachedBlocks.AddBlock(b) 946 // Add (current - 1) block 947 b = block.NewMockDatabaseBlock(ctrl) 948 b.EXPECT().StartTime().Return(curr.Add(-ropts.BlockSize())) 949 b.EXPECT().Close().Return() 950 series.cachedBlocks.AddBlock(b) 951 // Add (current - 2) block 952 b = block.NewMockDatabaseBlock(ctrl) 953 b.EXPECT().StartTime().Return(curr.Add(-2 * ropts.BlockSize())) 954 b.EXPECT().Close().Return() 955 series.cachedBlocks.AddBlock(b) 956 957 // Set up the buffer 958 buffer := NewMockdatabaseBuffer(ctrl) 959 buffer.EXPECT(). 960 Stats(). 961 Return(bufferStats{ 962 wiredBlocks: 0, 963 }) 964 buffer.EXPECT(). 965 Tick(gomock.Any(), gomock.Any()). 966 Return(bufferTickResult{ 967 // This means that (curr - 1 block) and (curr - 2 blocks) should 968 // be removed after the tick. 969 evictedBucketTimes: OptimizedTimes{ 970 arrIdx: 2, 971 arr: [optimizedTimesArraySize]xtime.UnixNano{ 972 curr.Add(-ropts.BlockSize()), 973 curr.Add(-2 * ropts.BlockSize()), 974 }, 975 }, 976 }) 977 series.buffer = buffer 978 979 assert.Equal(t, 3, series.cachedBlocks.Len()) 980 blockStates := BootstrappedBlockStateSnapshot{} 981 shardBlockStates := NewShardBlockStateSnapshot(true, blockStates) 982 _, err := series.Tick(shardBlockStates, namespace.Context{}) 983 require.NoError(t, err) 984 assert.Equal(t, 1, series.cachedBlocks.Len()) 985 } 986 987 func TestSeriesFetchBlocks(t *testing.T) { 988 ctrl := gomock.NewController(t) 989 defer ctrl.Finish() 990 991 opts := newSeriesTestOptions() 992 ctx := opts.ContextPool().Get() 993 defer ctx.Close() 994 995 now := xtime.Now() 996 starts := []xtime.UnixNano{now, now.Add(time.Second), now.Add(-time.Second)} 997 blocks := block.NewMockDatabaseSeriesBlocks(ctrl) 998 999 // Set up the blocks 1000 b := block.NewMockDatabaseBlock(ctrl) 1001 b.EXPECT().Stream(ctx).Return(xio.BlockReader{ 1002 SegmentReader: xio.NewSegmentReader(ts.Segment{}), 1003 }, nil) 1004 blocks.EXPECT().BlockAt(starts[0]).Return(b, true) 1005 1006 b = block.NewMockDatabaseBlock(ctrl) 1007 b.EXPECT().StartTime().Return(starts[1]).AnyTimes() 1008 b.EXPECT().Stream(ctx).Return(xio.EmptyBlockReader, errors.New("bar")) 1009 blocks.EXPECT().BlockAt(starts[1]).Return(b, true) 1010 1011 blocks.EXPECT().BlockAt(starts[2]).Return(nil, false) 1012 1013 // Set up the buffer 1014 buffer := NewMockdatabaseBuffer(ctrl) 1015 buffer.EXPECT().IsEmpty().Return(false) 1016 buffer.EXPECT(). 1017 FetchBlocks(ctx, starts, namespace.Context{}). 1018 Return([]block.FetchBlockResult{block.NewFetchBlockResult(starts[2], nil, nil)}) 1019 1020 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 1021 blockRetriever.EXPECT(). 1022 IsBlockRetrievable(gomock.Any()). 1023 Return(false, nil). 1024 AnyTimes() 1025 1026 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1027 ID: ident.StringID("foo"), 1028 BlockRetriever: blockRetriever, 1029 Options: opts, 1030 }).(*dbSeries) 1031 1032 err := series.LoadBlock(b, WarmWrite) 1033 require.NoError(t, err) 1034 1035 series.cachedBlocks = blocks 1036 series.buffer = buffer 1037 res, err := series.FetchBlocks(ctx, starts, namespace.Context{}) 1038 require.NoError(t, err) 1039 1040 expectedTimes := []xtime.UnixNano{starts[2], starts[0], starts[1]} 1041 require.Equal(t, len(expectedTimes), len(res)) 1042 for i := 0; i < len(starts); i++ { 1043 assert.Equal(t, expectedTimes[i], res[i].Start) 1044 if i == 1 { 1045 assert.NotNil(t, res[i].Blocks) 1046 } else { 1047 assert.Nil(t, res[i].Blocks) 1048 } 1049 if i == 2 { 1050 assert.Error(t, res[i].Err) 1051 } else { 1052 assert.NoError(t, res[i].Err) 1053 } 1054 } 1055 } 1056 1057 func TestSeriesFetchBlocksMetadata(t *testing.T) { 1058 ctrl := gomock.NewController(t) 1059 defer ctrl.Finish() 1060 1061 opts := newSeriesTestOptions() 1062 ctx := opts.ContextPool().Get() 1063 defer ctx.Close() 1064 1065 var ( 1066 now = xtime.Now() 1067 start = now.Add(-time.Hour) 1068 end = now.Add(time.Hour) 1069 starts = []xtime.UnixNano{now.Add(-time.Hour), now, now.Add(time.Second), now.Add(time.Hour)} 1070 ) 1071 // Set up the buffer 1072 buffer := NewMockdatabaseBuffer(ctrl) 1073 expectedResults := block.NewFetchBlockMetadataResults() 1074 expectedResults.Add(block.FetchBlockMetadataResult{Start: starts[2]}) 1075 1076 fetchOpts := FetchBlocksMetadataOptions{ 1077 FetchBlocksMetadataOptions: block.FetchBlocksMetadataOptions{ 1078 IncludeSizes: true, 1079 IncludeChecksums: true, 1080 IncludeLastRead: true, 1081 }, 1082 } 1083 buffer.EXPECT().IsEmpty().Return(false) 1084 buffer.EXPECT(). 1085 FetchBlocksMetadata(ctx, start, end, fetchOpts). 1086 Return(expectedResults, nil) 1087 1088 bl := block.NewMockDatabaseBlock(ctrl) 1089 bl.EXPECT().StartTime().Return(start).AnyTimes() 1090 1091 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 1092 blockRetriever.EXPECT(). 1093 IsBlockRetrievable(gomock.Any()). 1094 Return(false, nil). 1095 AnyTimes() 1096 1097 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1098 ID: ident.StringID("bar"), 1099 BlockRetriever: blockRetriever, 1100 Options: opts, 1101 }).(*dbSeries) 1102 1103 err := series.LoadBlock(bl, WarmWrite) 1104 require.NoError(t, err) 1105 1106 series.buffer = buffer 1107 1108 res, err := series.FetchBlocksMetadata(ctx, start, end, fetchOpts) 1109 require.NoError(t, err) 1110 require.Equal(t, "bar", res.ID.String()) 1111 1112 metadata := res.Blocks.Results() 1113 expected := []struct { 1114 start xtime.UnixNano 1115 size int64 1116 checksum *uint32 1117 lastRead xtime.UnixNano 1118 hasError bool 1119 }{ 1120 {starts[2], 0, nil, 0, false}, 1121 } 1122 require.Equal(t, len(expected), len(metadata)) 1123 for i := 0; i < len(expected); i++ { 1124 require.True(t, expected[i].start.Equal(metadata[i].Start)) 1125 require.Equal(t, expected[i].size, metadata[i].Size) 1126 if expected[i].checksum == nil { 1127 require.Nil(t, metadata[i].Checksum) 1128 } else { 1129 require.Equal(t, *expected[i].checksum, *metadata[i].Checksum) 1130 } 1131 require.True(t, expected[i].lastRead.Equal(metadata[i].LastRead)) 1132 if expected[i].hasError { 1133 require.Error(t, metadata[i].Err) 1134 } else { 1135 require.NoError(t, metadata[i].Err) 1136 } 1137 } 1138 } 1139 1140 func TestSeriesOutOfOrderWritesAndRotate(t *testing.T) { 1141 now := xtime.FromSeconds(1477929600) 1142 nowFn := func() time.Time { return now.ToTime() } 1143 clockOpts := clock.NewOptions().SetNowFn(nowFn) 1144 retentionOpts := retention.NewOptions() 1145 opts := newSeriesTestOptions(). 1146 SetClockOptions(clockOpts). 1147 SetRetentionOptions(retentionOpts) 1148 1149 var ( 1150 ctx = context.NewBackground() 1151 id = ident.StringID("foo") 1152 nsID = ident.StringID("bar") 1153 tags = ident.NewTags(ident.StringTag("name", "value")) 1154 startValue = 1.0 1155 blockSize = opts.RetentionOptions().BlockSize() 1156 numPoints = 10 1157 numBlocks = 7 1158 qStart = now 1159 qEnd = qStart.Add(time.Duration(numBlocks) * blockSize) 1160 expected []ts.Datapoint 1161 ) 1162 1163 metadata, err := convert.FromSeriesIDAndTags(id, tags) 1164 require.NoError(t, err) 1165 1166 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1167 ID: id, 1168 Metadata: metadata, 1169 Options: opts, 1170 }).(*dbSeries) 1171 1172 for iter := 0; iter < numBlocks; iter++ { 1173 start := now 1174 value := startValue 1175 1176 for i := 0; i < numPoints; i++ { 1177 wasWritten, _, err := series.Write(ctx, start, value, xtime.Second, nil, WriteOptions{}) 1178 require.NoError(t, err) 1179 assert.True(t, wasWritten) 1180 expected = append(expected, ts.Datapoint{TimestampNanos: start, Value: value}) 1181 start = start.Add(10 * time.Second) 1182 value = value + 1.0 1183 } 1184 1185 // Perform out-of-order writes 1186 start = now 1187 value = startValue 1188 for i := 0; i < numPoints/2; i++ { 1189 wasWritten, _, err := series.Write(ctx, start, value, xtime.Second, nil, WriteOptions{}) 1190 require.NoError(t, err) 1191 assert.True(t, wasWritten) 1192 start = start.Add(10 * time.Second) 1193 value = value + 1.0 1194 } 1195 1196 now = now.Add(blockSize) 1197 } 1198 1199 iter, err := series.ReadEncoded(ctx, qStart, qEnd, namespace.Context{}) 1200 require.NoError(t, err) 1201 encoded, err := iter.ToSlices(ctx) 1202 require.NoError(t, err) 1203 1204 multiIt := opts.MultiReaderIteratorPool().Get() 1205 1206 multiIt.ResetSliceOfSlices(xio.NewReaderSliceOfSlicesFromBlockReadersIterator(encoded), nil) 1207 it := encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{ 1208 ID: id, 1209 Namespace: nsID, 1210 Tags: ident.NewTagsIterator(tags), 1211 StartInclusive: qStart, 1212 EndExclusive: qEnd, 1213 Replicas: []encoding.MultiReaderIterator{multiIt}, 1214 }, nil) 1215 defer it.Close() 1216 1217 var actual []ts.Datapoint 1218 for it.Next() { 1219 dp, _, _ := it.Current() 1220 actual = append(actual, dp) 1221 } 1222 1223 require.NoError(t, it.Err()) 1224 require.Equal(t, expected, actual) 1225 } 1226 1227 func TestSeriesWriteReadFromTheSameBucket(t *testing.T) { 1228 ctrl := gomock.NewController(t) 1229 defer ctrl.Finish() 1230 1231 bl := block.NewMockDatabaseBlock(ctrl) 1232 bl.EXPECT().StartTime().Return(xtime.Now()).AnyTimes() 1233 bl.EXPECT().Len().Return(0).AnyTimes() 1234 1235 opts := newSeriesTestOptions() 1236 opts = opts.SetRetentionOptions(opts.RetentionOptions(). 1237 SetRetentionPeriod(40 * 24 * time.Hour). 1238 // A block size of 5 days is not equally as divisible as seconds from time 1239 // zero and seconds from time epoch. 1240 // now := time.Now() 1241 // blockSize := 5 * 24 * time.Hour 1242 // fmt.Println(now) -> 2018-01-24 14:29:31.624265 -0500 EST m=+0.003810489 1243 // fmt.Println(now.Truncate(blockSize)) -> 2018-01-21 19:00:00 -0500 EST 1244 // fmt.Println(time.Unix(0, now.UnixNano()/int64(blockSize)*int64(blockSize))) 1245 // -> 2018-01-23 19:00:00 -0500 EST 1246 SetBlockSize(5 * 24 * time.Hour). 1247 SetBufferFuture(10 * time.Minute). 1248 SetBufferPast(20 * time.Minute)) 1249 curr := xtime.Now() 1250 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 1251 return curr.ToTime() 1252 })) 1253 1254 blockRetriever := NewMockQueryableBlockRetriever(ctrl) 1255 blockRetriever.EXPECT(). 1256 IsBlockRetrievable(gomock.Any()). 1257 Return(false, nil). 1258 AnyTimes() 1259 1260 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1261 ID: ident.StringID("foo"), 1262 BlockRetriever: blockRetriever, 1263 Options: opts, 1264 }).(*dbSeries) 1265 1266 err := series.LoadBlock(bl, WarmWrite) 1267 require.NoError(t, err) 1268 1269 ctx := context.NewBackground() 1270 defer ctx.Close() 1271 1272 wasWritten, _, err := series.Write(ctx, curr.Add(-3*time.Minute), 1273 1, xtime.Second, nil, WriteOptions{}) 1274 assert.NoError(t, err) 1275 assert.True(t, wasWritten) 1276 wasWritten, _, err = series.Write(ctx, curr.Add(-2*time.Minute), 1277 2, xtime.Second, nil, WriteOptions{}) 1278 assert.NoError(t, err) 1279 assert.True(t, wasWritten) 1280 wasWritten, _, err = series.Write(ctx, curr.Add(-1*time.Minute), 1281 3, xtime.Second, nil, WriteOptions{}) 1282 assert.NoError(t, err) 1283 assert.True(t, wasWritten) 1284 1285 iter, err := series.ReadEncoded(ctx, curr.Add(-5*time.Minute), 1286 curr.Add(time.Minute), namespace.Context{}) 1287 require.NoError(t, err) 1288 results, err := iter.ToSlices(ctx) 1289 require.NoError(t, err) 1290 values, err := decodedReaderValues(results, opts, namespace.Context{}) 1291 require.NoError(t, err) 1292 1293 require.Equal(t, 3, len(values)) 1294 } 1295 1296 func TestSeriesCloseNonCacheLRUPolicy(t *testing.T) { 1297 ctrl := gomock.NewController(t) 1298 defer ctrl.Finish() 1299 1300 opts := newSeriesTestOptions(). 1301 SetCachePolicy(CacheRecentlyRead) 1302 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1303 ID: ident.StringID("foo"), 1304 Options: opts, 1305 }).(*dbSeries) 1306 1307 start := xtime.Now() 1308 blocks := block.NewDatabaseSeriesBlocks(0) 1309 diskBlock := block.NewMockDatabaseBlock(ctrl) 1310 diskBlock.EXPECT().StartTime().Return(start).AnyTimes() 1311 diskBlock.EXPECT().Close() 1312 blocks.AddBlock(diskBlock) 1313 1314 series.cachedBlocks = blocks 1315 series.Close() 1316 } 1317 1318 func TestSeriesCloseCacheLRUPolicy(t *testing.T) { 1319 ctrl := gomock.NewController(t) 1320 defer ctrl.Finish() 1321 1322 opts := newSeriesTestOptions(). 1323 SetCachePolicy(CacheLRU) 1324 series := NewDatabaseSeries(DatabaseSeriesOptions{ 1325 ID: ident.StringID("foo"), 1326 Options: opts, 1327 }).(*dbSeries) 1328 1329 start := xtime.Now() 1330 blocks := block.NewDatabaseSeriesBlocks(0) 1331 // Add a block that was retrieved from disk 1332 diskBlock := block.NewMockDatabaseBlock(ctrl) 1333 diskBlock.EXPECT().StartTime().Return(start).AnyTimes() 1334 blocks.AddBlock(diskBlock) 1335 1336 // Add block that was not retrieved from disk 1337 nonDiskBlock := block.NewMockDatabaseBlock(ctrl) 1338 nonDiskBlock.EXPECT().StartTime(). 1339 Return(start.Add(opts.RetentionOptions().BlockSize())).AnyTimes() 1340 blocks.AddBlock(nonDiskBlock) 1341 1342 series.cachedBlocks = blocks 1343 series.Close() 1344 } 1345 1346 func requireBlockNotEmpty(t *testing.T, series *dbSeries, blockStart xtime.UnixNano) { 1347 nonEmptyBlocks := map[xtime.UnixNano]struct{}{} 1348 series.MarkNonEmptyBlocks(nonEmptyBlocks) 1349 assert.Contains(t, nonEmptyBlocks, blockStart) 1350 }