github.com/m3db/m3@v1.5.0/src/query/storage/m3/encoded_step_iterator_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package m3 22 23 import ( 24 "fmt" 25 "os" 26 "runtime" 27 "sync" 28 "testing" 29 "time" 30 31 "github.com/m3db/m3/src/dbnode/encoding" 32 "github.com/m3db/m3/src/dbnode/encoding/m3tsz" 33 "github.com/m3db/m3/src/dbnode/namespace" 34 "github.com/m3db/m3/src/dbnode/ts" 35 "github.com/m3db/m3/src/dbnode/x/xio" 36 "github.com/m3db/m3/src/query/block" 37 "github.com/m3db/m3/src/query/models" 38 "github.com/m3db/m3/src/query/pools" 39 "github.com/m3db/m3/src/query/storage/m3/consolidators" 40 "github.com/m3db/m3/src/query/test/compare" 41 "github.com/m3db/m3/src/x/checked" 42 "github.com/m3db/m3/src/x/ident" 43 "github.com/m3db/m3/src/x/pool" 44 xsync "github.com/m3db/m3/src/x/sync" 45 xtime "github.com/m3db/m3/src/x/time" 46 47 "github.com/pkg/profile" 48 "github.com/stretchr/testify/assert" 49 "github.com/stretchr/testify/require" 50 ) 51 52 func withPool(t testing.TB, options Options) Options { 53 opts := xsync. 54 NewPooledWorkerPoolOptions(). 55 SetGrowOnDemand(false). 56 SetKillWorkerProbability(0). 57 SetNumShards(1) 58 59 readWorkerPools, err := xsync.NewPooledWorkerPool(64, opts) 60 require.NoError(t, err) 61 readWorkerPools.Init() 62 63 return options.SetReadWorkerPool(readWorkerPools) 64 } 65 66 var consolidatedStepIteratorTests = []struct { 67 name string 68 stepSize time.Duration 69 expected [][]float64 70 }{ 71 { 72 name: "1 minute", 73 stepSize: time.Minute, 74 expected: [][]float64{ 75 {nan, nan, nan}, 76 {nan, nan, nan}, 77 {nan, 10, nan}, 78 {nan, 20, 100}, 79 {nan, 20, 100}, 80 {nan, nan, nan}, 81 {1, nan, nan}, 82 {1, nan, nan}, 83 {3, nan, nan}, 84 {4, nan, 200}, 85 {5, nan, 200}, 86 {6, nan, nan}, 87 {7, nan, nan}, 88 {7, 30, nan}, 89 {nan, 30, nan}, 90 {nan, nan, 300}, 91 {nan, nan, 300}, 92 {8, nan, nan}, 93 {8, nan, nan}, 94 {nan, 40, nan}, 95 {nan, nan, nan}, 96 {nan, nan, 400}, 97 {9, nan, 400}, 98 {9, nan, nan}, 99 {nan, nan, 500}, 100 {nan, nan, 500}, 101 {nan, nan, nan}, 102 {nan, nan, nan}, 103 {nan, nan, nan}, 104 {nan, nan, nan}, 105 }, 106 }, 107 { 108 name: "2 minute", 109 stepSize: time.Minute * 2, 110 expected: [][]float64{ 111 {nan, nan, nan}, 112 {nan, 10, nan}, 113 {nan, 20, 100}, 114 {1, nan, nan}, 115 {3, nan, nan}, 116 {5, nan, 200}, 117 {7, nan, nan}, 118 {nan, 30, nan}, 119 {nan, nan, 300}, 120 {8, nan, nan}, 121 {nan, nan, nan}, 122 {9, nan, 400}, 123 {nan, nan, 500}, 124 {nan, nan, nan}, 125 {nan, nan, nan}, 126 }, 127 }, 128 { 129 name: "3 minute", 130 stepSize: time.Minute * 3, 131 expected: [][]float64{ 132 {nan, nan, nan}, 133 {nan, 20, 100}, 134 {1, nan, nan}, 135 {4, nan, 200}, 136 {7, nan, nan}, 137 {nan, nan, 300}, 138 {8, nan, nan}, 139 {nan, nan, 400}, 140 {nan, nan, 500}, 141 {nan, nan, nan}, 142 }, 143 }, 144 } 145 146 func testConsolidatedStepIteratorMinuteLookback(t *testing.T, withPools bool) { 147 for _, tt := range consolidatedStepIteratorTests { 148 opts := newTestOpts(). 149 SetLookbackDuration(1 * time.Minute). 150 SetSplitSeriesByBlock(false) 151 require.NoError(t, opts.Validate()) 152 if withPools { 153 opts = withPool(t, opts) 154 } 155 156 blocks, bounds := generateBlocks(t, tt.stepSize, opts) 157 j := 0 158 for i, bl := range blocks { 159 iters, err := bl.StepIter() 160 require.NoError(t, err) 161 assert.Equal(t, block.BlockM3TSZCompressed, bl.Info().Type()) 162 163 require.True(t, bounds.Equals(bl.Meta().Bounds)) 164 verifyMetas(t, i, bl.Meta(), iters.SeriesMeta()) 165 for iters.Next() { 166 step := iters.Current() 167 vals := step.Values() 168 compare.EqualsWithNans(t, tt.expected[j], vals) 169 j++ 170 } 171 172 require.NoError(t, iters.Err()) 173 } 174 } 175 } 176 177 func TestConsolidatedStepIteratorMinuteLookbackParallel(t *testing.T) { 178 testConsolidatedStepIteratorMinuteLookback(t, true) 179 } 180 181 func TestConsolidatedStepIteratorMinuteLookbackSequential(t *testing.T) { 182 testConsolidatedStepIteratorMinuteLookback(t, false) 183 } 184 185 var consolidatedStepIteratorTestsSplitByBlock = []struct { 186 name string 187 stepSize time.Duration 188 expected [][][]float64 189 }{ 190 { 191 name: "1 minute", 192 stepSize: time.Minute, 193 expected: [][][]float64{ 194 { 195 {nan, nan, nan}, 196 {nan, nan, nan}, 197 {nan, 10, nan}, 198 {nan, 20, 100}, 199 {nan, nan, nan}, 200 {nan, nan, nan}, 201 }, 202 { 203 {1, nan, nan}, 204 {nan, nan, nan}, 205 {3, nan, nan}, 206 {4, nan, 200}, 207 {5, nan, nan}, 208 {6, nan, nan}, 209 }, 210 { 211 {7, nan, nan}, 212 {nan, nan, nan}, 213 {nan, nan, nan}, 214 {nan, nan, 300}, 215 {nan, nan, nan}, 216 {8, nan, nan}, 217 }, 218 { 219 {nan, nan, nan}, 220 {nan, nan, nan}, 221 {nan, nan, nan}, 222 {nan, nan, 400}, 223 {9, nan, nan}, 224 {nan, nan, nan}, 225 }, 226 { 227 {nan, nan, 500}, 228 {nan, nan, nan}, 229 {nan, nan, nan}, 230 {nan, nan, nan}, 231 {nan, nan, nan}, 232 {nan, nan, nan}, 233 }, 234 }, 235 }, 236 { 237 name: "2 minute", 238 stepSize: time.Minute * 2, 239 expected: [][][]float64{ 240 { 241 {nan, nan, nan}, 242 {nan, 10, nan}, 243 {nan, nan, nan}, 244 }, 245 { 246 {1, nan, nan}, 247 {3, nan, nan}, 248 {5, nan, nan}, 249 }, 250 { 251 {7, nan, nan}, 252 {nan, nan, nan}, 253 {nan, nan, nan}, 254 }, 255 { 256 {nan, nan, nan}, 257 {nan, nan, nan}, 258 {9, nan, nan}, 259 }, 260 { 261 {nan, nan, 500}, 262 {nan, nan, nan}, 263 {nan, nan, nan}, 264 }, 265 }, 266 }, 267 { 268 name: "3 minute", 269 stepSize: time.Minute * 3, 270 expected: [][][]float64{ 271 { 272 {nan, nan, nan}, 273 {nan, 20, 100}, 274 }, 275 { 276 {1, nan, nan}, 277 {4, nan, 200}, 278 }, 279 { 280 {7, nan, nan}, 281 {nan, nan, 300}, 282 }, 283 { 284 {nan, nan, nan}, 285 {nan, nan, 400}, 286 }, 287 { 288 {nan, nan, 500}, 289 {nan, nan, nan}, 290 }, 291 }, 292 }, 293 } 294 295 func testConsolidatedStepIteratorSplitByBlock(t *testing.T, withPools bool) { 296 for _, tt := range consolidatedStepIteratorTestsSplitByBlock { 297 opts := newTestOpts(). 298 SetLookbackDuration(0). 299 SetSplitSeriesByBlock(true) 300 require.NoError(t, opts.Validate()) 301 if withPools { 302 opts = withPool(t, opts) 303 } 304 305 blocks, bounds := generateBlocks(t, tt.stepSize, opts) 306 for i, block := range blocks { 307 iters, err := block.StepIter() 308 require.NoError(t, err) 309 310 j := 0 311 idx := verifyBoundsAndGetBlockIndex(t, bounds, block.Meta().Bounds) 312 verifyMetas(t, i, block.Meta(), iters.SeriesMeta()) // <- 313 for iters.Next() { 314 step := iters.Current() 315 vals := step.Values() 316 compare.EqualsWithNans(t, tt.expected[idx][j], vals) 317 j++ 318 } 319 320 require.NoError(t, iters.Err()) 321 } 322 } 323 } 324 325 func TestConsolidatedStepIteratorSplitByBlockParallel(t *testing.T) { 326 testConsolidatedStepIteratorSplitByBlock(t, true) 327 } 328 329 func TestConsolidatedStepIteratorSplitByBlockSequential(t *testing.T) { 330 testConsolidatedStepIteratorSplitByBlock(t, false) 331 } 332 333 func benchmarkSingleBlock(b *testing.B, withPools bool) { 334 opts := newTestOpts(). 335 SetLookbackDuration(1 * time.Minute). 336 SetSplitSeriesByBlock(false) 337 require.NoError(b, opts.Validate()) 338 if withPools { 339 opts = withPool(b, opts) 340 } 341 342 for i := 0; i < b.N; i++ { 343 for _, tt := range consolidatedStepIteratorTests { 344 b.StopTimer() 345 blocks, _ := generateBlocks(b, tt.stepSize, opts) 346 b.StartTimer() 347 for _, block := range blocks { 348 iters, _ := block.StepIter() 349 for iters.Next() { 350 // no-op 351 } 352 353 require.NoError(b, iters.Err()) 354 } 355 } 356 } 357 } 358 359 func BenchmarkSingleBlockParallel(b *testing.B) { 360 benchmarkSingleBlock(b, true) 361 } 362 363 func BenchmarkSingleBlockSerialll(b *testing.B) { 364 benchmarkSingleBlock(b, false) 365 } 366 367 type noopCollector struct{} 368 369 func (n noopCollector) AddPoint(dp ts.Datapoint) {} 370 func (n noopCollector) BufferStep() {} 371 func (n noopCollector) BufferStepCount() int { return 0 } 372 373 type iterType uint 374 375 const ( 376 stepSequential iterType = iota 377 stepParallel 378 seriesSequential 379 seriesBatch 380 ) 381 382 func (t iterType) name(name string) string { 383 var n string 384 switch t { 385 case stepParallel: 386 n = "parallel" 387 case stepSequential: 388 n = "sequential" 389 case seriesSequential: 390 n = "series" 391 case seriesBatch: 392 n = "series_batch" 393 default: 394 panic(fmt.Sprint("bad iter type", t)) 395 } 396 397 return fmt.Sprintf("%s_%s", n, name) 398 } 399 400 type reset func() 401 type stop func() 402 403 // newTestOptions provides options with very small/non-existent pools 404 // so that memory profiles don't get cluttered with pooled allocated objects. 405 func newTestOpts() Options { 406 poolOpts := pool.NewObjectPoolOptions().SetSize(1) 407 bytesPool := pool.NewCheckedBytesPool(nil, poolOpts, 408 func(s []pool.Bucket) pool.BytesPool { 409 return pool.NewBytesPool(s, poolOpts) 410 }) 411 bytesPool.Init() 412 413 iteratorPools := pools.BuildIteratorPools(encoding.NewOptions(), 414 pools.BuildIteratorPoolsOptions{ 415 Replicas: 1, 416 SeriesIteratorPoolSize: 1, 417 SeriesIteratorsPoolBuckets: []pool.Bucket{ 418 {Capacity: 1, Count: 1}, 419 }, 420 SeriesIDBytesPoolBuckets: []pool.Bucket{ 421 {Capacity: 1, Count: 1}, 422 }, 423 CheckedBytesWrapperPoolSize: 1, 424 }) 425 return newOptions(bytesPool, iteratorPools) 426 } 427 428 func iterToFetchResult( 429 iters []encoding.SeriesIterator, 430 ) (consolidators.SeriesFetchResult, error) { 431 return consolidators.NewSeriesFetchResult( 432 encoding.NewSeriesIterators(iters), 433 nil, 434 block.NewResultMetadata(), 435 ) 436 } 437 438 func setupBlock(b *testing.B, iterations int, t iterType) (block.Block, reset, stop) { 439 var ( 440 seriesCount = 1000 441 replicasCount = 3 442 start = xtime.Now() 443 stepSize = time.Second * 10 444 window = stepSize * time.Duration(iterations) 445 end = start.Add(window) 446 iters = make([]encoding.SeriesIterator, seriesCount) 447 itersReset = make([]func(), seriesCount) 448 449 encodingOpts = encoding.NewOptions() 450 namespaceID = ident.StringID("namespace") 451 ) 452 453 for i := 0; i < seriesCount; i++ { 454 encoder := m3tsz.NewEncoder(start, checked.NewBytes(nil, nil), 455 m3tsz.DefaultIntOptimizationEnabled, encodingOpts) 456 457 timestamp := start 458 for j := 0; j < iterations; j++ { 459 timestamp = timestamp.Add(time.Duration(j) * stepSize) 460 dp := ts.Datapoint{TimestampNanos: timestamp, Value: float64(j)} 461 err := encoder.Encode(dp, xtime.Second, nil) 462 require.NoError(b, err) 463 } 464 465 data := encoder.Discard() 466 replicas := make([]struct { 467 readers []xio.SegmentReader 468 iter encoding.MultiReaderIterator 469 }, replicasCount) 470 replicasIters := make([]encoding.MultiReaderIterator, replicasCount) 471 for j := 0; j < replicasCount; j++ { 472 readers := []xio.SegmentReader{xio.NewSegmentReader(data)} 473 replicas[j].readers = readers 474 475 // Use the same decoder over and over to avoid allocations. 476 readerIter := m3tsz.NewReaderIterator(nil, 477 m3tsz.DefaultIntOptimizationEnabled, encodingOpts) 478 479 iterAlloc := func( 480 r xio.Reader64, 481 d namespace.SchemaDescr, 482 ) encoding.ReaderIterator { 483 readerIter.Reset(r, d) 484 return readerIter 485 } 486 487 iter := encoding.NewMultiReaderIterator(iterAlloc, nil) 488 iter.Reset(readers, start, window, nil) 489 replicas[j].iter = iter 490 491 replicasIters[j] = iter 492 } 493 494 seriesID := ident.StringID(fmt.Sprintf("foo.%d", i+1)) 495 tags, err := ident.NewTagStringsIterator("foo", "bar", "baz", "qux") 496 require.NoError(b, err) 497 498 iter := encoding.NewSeriesIterator( 499 encoding.SeriesIteratorOptions{}, nil) 500 501 iters[i] = iter 502 itersReset[i] = func() { 503 // Reset the replica iters. 504 for _, replica := range replicas { 505 for _, reader := range replica.readers { 506 reader.Reset(data) 507 } 508 replica.iter.Reset(replica.readers, start, window, nil) 509 } 510 // Reset the series iterator. 511 iter.Reset(encoding.SeriesIteratorOptions{ 512 ID: seriesID, 513 Namespace: namespaceID, 514 Tags: tags, 515 Replicas: replicasIters, 516 StartInclusive: start, 517 EndExclusive: end, 518 }) 519 } 520 } 521 522 usePools := t == stepParallel 523 524 opts := newTestOpts() 525 if usePools { 526 poolOpts := xsync.NewPooledWorkerPoolOptions() 527 readWorkerPools, err := xsync.NewPooledWorkerPool(1024, poolOpts) 528 require.NoError(b, err) 529 readWorkerPools.Init() 530 opts = opts.SetReadWorkerPool(readWorkerPools) 531 } 532 533 for _, reset := range itersReset { 534 reset() 535 } 536 537 res, err := iterToFetchResult(iters) 538 require.NoError(b, err) 539 540 block, err := NewEncodedBlock( 541 res, 542 models.Bounds{ 543 Start: start, 544 StepSize: stepSize, 545 Duration: window, 546 }, false, opts) 547 548 require.NoError(b, err) 549 return block, func() { 550 for _, reset := range itersReset { 551 reset() 552 } 553 }, 554 setupProf(usePools, iterations) 555 } 556 557 func setupProf(usePools bool, iterations int) stop { 558 var prof interface { 559 Stop() 560 } 561 if os.Getenv("PROFILE_TEST_CPU") == "true" { 562 key := profileTakenKey{ 563 profile: "cpu", 564 pools: usePools, 565 iterations: iterations, 566 } 567 if v := profilesTaken[key]; v == 2 { 568 prof = profile.Start(profile.CPUProfile) 569 } 570 571 profilesTaken[key] = profilesTaken[key] + 1 572 } 573 574 if os.Getenv("PROFILE_TEST_MEM") == "true" { 575 key := profileTakenKey{ 576 profile: "mem", 577 pools: usePools, 578 iterations: iterations, 579 } 580 581 if v := profilesTaken[key]; v == 2 { 582 prof = profile.Start(profile.MemProfile) 583 } 584 585 profilesTaken[key] = profilesTaken[key] + 1 586 } 587 return func() { 588 if prof != nil { 589 prof.Stop() 590 } 591 } 592 } 593 594 func benchmarkNextIteration(b *testing.B, iterations int, t iterType) { 595 bl, reset, close := setupBlock(b, iterations, t) 596 defer close() 597 598 if t == seriesSequential { 599 it, err := bl.SeriesIter() 600 require.NoError(b, err) 601 602 b.ResetTimer() 603 for i := 0; i < b.N; i++ { 604 reset() 605 for it.Next() { 606 } 607 608 require.NoError(b, it.Err()) 609 } 610 611 return 612 } 613 614 if t == seriesBatch { 615 batches, err := bl.MultiSeriesIter(runtime.GOMAXPROCS(0)) 616 require.NoError(b, err) 617 618 var wg sync.WaitGroup 619 b.ResetTimer() 620 for i := 0; i < b.N; i++ { 621 reset() 622 623 for _, batch := range batches { 624 it := batch.Iter 625 wg.Add(1) 626 go func() { 627 for it.Next() { 628 } 629 630 wg.Done() 631 }() 632 } 633 634 wg.Wait() 635 for _, batch := range batches { 636 require.NoError(b, batch.Iter.Err()) 637 } 638 } 639 640 return 641 } 642 643 it, err := bl.StepIter() 644 require.NoError(b, err) 645 646 b.ResetTimer() 647 for i := 0; i < b.N; i++ { 648 reset() 649 for it.Next() { 650 } 651 652 require.NoError(b, it.Err()) 653 } 654 } 655 656 type profileTakenKey struct { 657 profile string 658 pools bool 659 iterations int 660 } 661 662 var ( 663 profilesTaken = make(map[profileTakenKey]int) 664 ) 665 666 /* 667 $ go test -v -run none -bench BenchmarkNextIteration 668 goos: darwin 669 goarch: amd64 670 pkg: github.com/m3db/m3/src/query/ts/m3db 671 672 BenchmarkNextIteration/sequential_10-12 4112 282491 ns/op 673 BenchmarkNextIteration/parallel_10-12 4214 249335 ns/op 674 BenchmarkNextIteration/series_10-12 4515 248946 ns/op 675 BenchmarkNextIteration/series_batch_10-12 4434 269776 ns/op 676 677 BenchmarkNextIteration/sequential_100-12 4069 267836 ns/op 678 BenchmarkNextIteration/parallel_100-12 4126 283069 ns/op 679 BenchmarkNextIteration/series_100-12 4146 266928 ns/op 680 BenchmarkNextIteration/series_batch_100-12 4399 255991 ns/op 681 682 BenchmarkNextIteration/sequential_200-12 4267 245249 ns/op 683 BenchmarkNextIteration/parallel_200-12 4233 239597 ns/op 684 BenchmarkNextIteration/series_200-12 4365 245924 ns/op 685 BenchmarkNextIteration/series_batch_200-12 4485 235055 ns/op 686 687 BenchmarkNextIteration/sequential_500-12 5108 230085 ns/op 688 BenchmarkNextIteration/parallel_500-12 4802 230694 ns/op 689 BenchmarkNextIteration/series_500-12 4831 229797 ns/op 690 BenchmarkNextIteration/series_batch_500-12 4880 246588 ns/op 691 692 BenchmarkNextIteration/sequential_1000-12 3807 265449 ns/op 693 BenchmarkNextIteration/parallel_1000-12 5062 254942 ns/op 694 BenchmarkNextIteration/series_1000-12 4423 236796 ns/op 695 BenchmarkNextIteration/series_batch_1000-12 4772 251977 ns/op 696 697 BenchmarkNextIteration/sequential_2000-12 4916 243593 ns/op 698 BenchmarkNextIteration/parallel_2000-12 4743 253677 ns/op 699 BenchmarkNextIteration/series_2000-12 4078 256375 ns/op 700 BenchmarkNextIteration/series_batch_2000-12 4465 242323 ns/op 701 */ 702 func BenchmarkNextIteration(b *testing.B) { 703 iterTypes := []iterType{ 704 stepSequential, 705 stepParallel, 706 seriesSequential, 707 seriesBatch, 708 } 709 710 for _, s := range []int{10, 100, 200, 500, 1000, 2000} { 711 for _, t := range iterTypes { 712 name := t.name(fmt.Sprintf("%d", s)) 713 b.Run(name, func(b *testing.B) { 714 benchmarkNextIteration(b, s, t) 715 }) 716 } 717 718 // NB: this is for clearer groupings. 719 println() 720 } 721 }