github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/series/series.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 "fmt" 26 "sync" 27 28 "go.uber.org/zap" 29 30 "github.com/m3db/m3/src/dbnode/namespace" 31 "github.com/m3db/m3/src/dbnode/persist" 32 "github.com/m3db/m3/src/dbnode/storage/block" 33 "github.com/m3db/m3/src/dbnode/ts" 34 "github.com/m3db/m3/src/m3ninx/doc" 35 "github.com/m3db/m3/src/x/context" 36 "github.com/m3db/m3/src/x/ident" 37 "github.com/m3db/m3/src/x/instrument" 38 xtime "github.com/m3db/m3/src/x/time" 39 ) 40 41 var ( 42 // ErrSeriesAllDatapointsExpired is returned on tick when all datapoints are expired 43 ErrSeriesAllDatapointsExpired = errors.New("series datapoints are all expired") 44 45 errSeriesAlreadyBootstrapped = errors.New("series is already bootstrapped") 46 errSeriesNotBootstrapped = errors.New("series is not yet bootstrapped") 47 errBlockStateSnapshotNotBootstrapped = errors.New("block state snapshot is not bootstrapped") 48 49 // Placeholder for a timeseries being bootstrapped which does not 50 // have access to the TS ID. 51 bootstrapWriteID = ident.StringID("bootstrap_timeseries") 52 ) 53 54 type dbSeries struct { 55 sync.RWMutex 56 opts Options 57 58 // NB(r): One should audit all places that access the 59 // series metadata before changing ownership semantics (e.g. 60 // pooling the ID rather than releasing it to the GC on 61 // calling series.Reset()). 62 // Note: The bytes that back "id ident.ID" are the same bytes 63 // that are behind the ID in "metadata doc.Metadata", the whole 64 // reason we keep an ident.ID on the series is since there's a lot 65 // of existing callsites that require the ID as an ident.ID. 66 id ident.ID 67 metadata doc.Metadata 68 uniqueIndex uint64 69 70 bootstrap dbSeriesBootstrap 71 72 buffer databaseBuffer 73 cachedBlocks block.DatabaseSeriesBlocks 74 blockRetriever QueryableBlockRetriever 75 onRetrieveBlock block.OnRetrieveBlock 76 blockOnEvictedFromWiredList block.OnEvictedFromWiredList 77 pool DatabaseSeriesPool 78 } 79 80 type dbSeriesBootstrap struct { 81 sync.Mutex 82 83 // buffer should be nil unless this series 84 // has taken bootstrap writes. 85 buffer databaseBuffer 86 } 87 88 // NewDatabaseSeries creates a new database series. 89 func NewDatabaseSeries(opts DatabaseSeriesOptions) DatabaseSeries { 90 s := newDatabaseSeries() 91 s.Reset(opts) 92 return s 93 } 94 95 // newPooledDatabaseSeries creates a new pooled database series. 96 func newPooledDatabaseSeries(pool DatabaseSeriesPool) DatabaseSeries { 97 series := newDatabaseSeries() 98 series.pool = pool 99 return series 100 } 101 102 // NB(prateek): dbSeries.Reset(...) must be called upon the returned 103 // object prior to use. 104 func newDatabaseSeries() *dbSeries { 105 series := &dbSeries{ 106 cachedBlocks: block.NewDatabaseSeriesBlocks(0), 107 } 108 series.buffer = newDatabaseBuffer() 109 return series 110 } 111 112 func (s *dbSeries) now() xtime.UnixNano { 113 nowFn := s.opts.ClockOptions().NowFn() 114 return xtime.ToUnixNano(nowFn()) 115 } 116 117 func (s *dbSeries) ID() ident.ID { 118 s.RLock() 119 id := s.id 120 s.RUnlock() 121 return id 122 } 123 124 func (s *dbSeries) Metadata() doc.Metadata { 125 s.RLock() 126 metadata := s.metadata 127 s.RUnlock() 128 return metadata 129 } 130 131 func (s *dbSeries) UniqueIndex() uint64 { 132 s.RLock() 133 uniqueIndex := s.uniqueIndex 134 s.RUnlock() 135 return uniqueIndex 136 } 137 138 func (s *dbSeries) Tick(blockStates ShardBlockStateSnapshot, nsCtx namespace.Context) (TickResult, error) { 139 var r TickResult 140 141 s.Lock() 142 143 bufferResult := s.buffer.Tick(blockStates, nsCtx) 144 r.MergedOutOfOrderBlocks = bufferResult.mergedOutOfOrderBlocks 145 r.EvictedBuckets = bufferResult.evictedBucketTimes.Len() 146 update, err := s.updateBlocksWithLock(blockStates, bufferResult.evictedBucketTimes) 147 if err != nil { 148 s.Unlock() 149 return r, err 150 } 151 r.TickStatus = update.TickStatus 152 r.MadeExpiredBlocks, r.MadeUnwiredBlocks = update.madeExpiredBlocks, update.madeUnwiredBlocks 153 154 s.Unlock() 155 156 if update.ActiveBlocks > 0 { 157 return r, nil 158 } 159 160 // Check if any bootstrap writes that hasn't been merged yet. 161 s.bootstrap.Lock() 162 unmergedBootstrapDatapoints := s.bootstrap.buffer != nil 163 s.bootstrap.Unlock() 164 165 if unmergedBootstrapDatapoints { 166 return r, nil 167 } 168 169 // Everything expired. 170 return r, ErrSeriesAllDatapointsExpired 171 } 172 173 type updateBlocksResult struct { 174 TickStatus 175 madeExpiredBlocks int 176 madeUnwiredBlocks int 177 } 178 179 func (s *dbSeries) updateBlocksWithLock( 180 blockStates ShardBlockStateSnapshot, 181 evictedBucketTimes OptimizedTimes, 182 ) (updateBlocksResult, error) { 183 var ( 184 result updateBlocksResult 185 now = s.now() 186 ropts = s.opts.RetentionOptions() 187 cachePolicy = s.opts.CachePolicy() 188 expireCutoff = now.Add(-ropts.RetentionPeriod()).Truncate(ropts.BlockSize()) 189 wiredTimeout = ropts.BlockDataExpiryAfterNotAccessedPeriod() 190 ) 191 for start, currBlock := range s.cachedBlocks.AllBlocks() { 192 if start.Before(expireCutoff) || evictedBucketTimes.Contains(start) { 193 s.cachedBlocks.RemoveBlockAt(start) 194 // If we're using the LRU policy and the block was retrieved from disk, 195 // then don't close the block because that is the WiredList's 196 // responsibility. The block will hang around the WiredList until 197 // it is evicted to make room for something else at which point it 198 // will be closed. 199 // 200 // Note that while we don't close the block, we do remove it from the list 201 // of blocks. This is so that the series itself can still be expired if this 202 // was the last block. The WiredList will still notify the shard/series via 203 // the OnEvictedFromWiredList method when it closes the block, but those 204 // methods are noops for series/blocks that have already been removed. 205 // 206 // Also note that while technically the DatabaseBlock protects against double 207 // closes, they can be problematic due to pooling. I.E if the following sequence 208 // of actions happens: 209 // 1) Tick closes expired block 210 // 2) Block is re-inserted into pool 211 // 3) Block is pulled out of pool and used for critical data 212 // 4) WiredList tries to close the block, not knowing that it has 213 // already been closed, and re-opened / re-used leading to 214 // unexpected behavior or data loss. 215 if cachePolicy == CacheLRU && currBlock.WasRetrievedFromDisk() { 216 // Do nothing 217 } else { 218 currBlock.Close() 219 } 220 result.madeExpiredBlocks++ 221 continue 222 } 223 224 result.ActiveBlocks++ 225 226 if cachePolicy == CacheAll { 227 // Never unwire 228 result.WiredBlocks++ 229 continue 230 } 231 232 // Potentially unwire 233 var unwired, shouldUnwire bool 234 blockStatesSnapshot, bootstrapped := blockStates.UnwrapValue() 235 // Only use block state snapshot information to make eviction decisions if the block state 236 // has been properly bootstrapped already. 237 if bootstrapped { 238 // Makes sure that the block has been flushed, which 239 // prevents us from unwiring blocks that haven't been flushed yet which 240 // would cause data loss. 241 if blockState := blockStatesSnapshot.Snapshot[start]; blockState.WarmRetrievable { 242 switch cachePolicy { 243 case CacheNone: 244 shouldUnwire = true 245 case CacheRecentlyRead: 246 sinceLastRead := now.Sub(currBlock.LastReadTime()) 247 shouldUnwire = sinceLastRead >= wiredTimeout 248 case CacheLRU: 249 // The tick is responsible for managing the lifecycle of blocks that were not 250 // read from disk (not retrieved), and the WiredList will manage those that were 251 // retrieved from disk. 252 shouldUnwire = !currBlock.WasRetrievedFromDisk() 253 default: 254 s.opts.InstrumentOptions().Logger().Fatal( 255 "unhandled cache policy in series tick", zap.Any("policy", cachePolicy)) 256 } 257 } 258 } 259 260 if shouldUnwire { 261 // Remove the block and it will be looked up later 262 s.cachedBlocks.RemoveBlockAt(start) 263 currBlock.Close() 264 unwired = true 265 result.madeUnwiredBlocks++ 266 } 267 268 if unwired { 269 result.UnwiredBlocks++ 270 } else { 271 result.WiredBlocks++ 272 if currBlock.HasMergeTarget() { 273 result.PendingMergeBlocks++ 274 } 275 } 276 } 277 278 bufferStats := s.buffer.Stats() 279 result.ActiveBlocks += bufferStats.wiredBlocks 280 result.WiredBlocks += bufferStats.wiredBlocks 281 282 return result, nil 283 } 284 285 func (s *dbSeries) IsEmpty() bool { 286 s.RLock() 287 blocksLen := s.cachedBlocks.Len() 288 bufferEmpty := s.buffer.IsEmpty() 289 s.RUnlock() 290 if blocksLen == 0 && bufferEmpty { 291 return true 292 } 293 return false 294 } 295 296 func (s *dbSeries) MarkNonEmptyBlocks(nonEmptyBlockStarts map[xtime.UnixNano]struct{}) { 297 s.RLock() 298 s.buffer.MarkNonEmptyBlocks(nonEmptyBlockStarts) 299 s.RUnlock() 300 } 301 302 func (s *dbSeries) NumActiveBlocks() int { 303 s.RLock() 304 value := s.cachedBlocks.Len() + s.buffer.Stats().wiredBlocks 305 s.RUnlock() 306 return value 307 } 308 309 func (s *dbSeries) Write( 310 ctx context.Context, 311 timestamp xtime.UnixNano, 312 value float64, 313 unit xtime.Unit, 314 annotation []byte, 315 wOpts WriteOptions, 316 ) (bool, WriteType, error) { 317 if wOpts.BootstrapWrite { 318 // NB(r): If this is a bootstrap write we store this in a 319 // side buffer so that we don't need to take the series lock 320 // and contend with normal writes that are flowing into the DB 321 // while bootstrapping which can significantly interrupt 322 // write latency and cause entire DB to stall/degrade in performance. 323 return s.bootstrapWrite(ctx, timestamp, value, unit, annotation, wOpts) 324 } 325 326 s.Lock() 327 written, writeType, err := s.buffer.Write(ctx, s.id, timestamp, value, 328 unit, annotation, wOpts) 329 s.Unlock() 330 331 return written, writeType, err 332 } 333 334 func (s *dbSeries) bootstrapWrite( 335 ctx context.Context, 336 timestamp xtime.UnixNano, 337 value float64, 338 unit xtime.Unit, 339 annotation []byte, 340 wOpts WriteOptions, 341 ) (bool, WriteType, error) { 342 s.bootstrap.Lock() 343 defer s.bootstrap.Unlock() 344 345 if s.bootstrap.buffer == nil { 346 // Temporarily release bootstrap lock. 347 s.bootstrap.Unlock() 348 349 // Get reset opts. 350 resetOpts, err := s.bufferResetOpts() 351 352 // Re-lock bootstrap lock. 353 s.bootstrap.Lock() 354 355 if err != nil { 356 // Abort if failed to get buffer opts. 357 var writeType WriteType 358 return false, writeType, err 359 } 360 361 // If buffer still nil then set it. 362 if s.bootstrap.buffer == nil { 363 s.bootstrap.buffer = newDatabaseBuffer() 364 s.bootstrap.buffer.Reset(resetOpts) 365 } 366 } 367 368 return s.bootstrap.buffer.Write(ctx, bootstrapWriteID, timestamp, 369 value, unit, annotation, wOpts) 370 } 371 372 func (s *dbSeries) bufferResetOpts() (databaseBufferResetOptions, error) { 373 // Grab series lock. 374 s.RLock() 375 defer s.RUnlock() 376 377 if s.id == nil { 378 // Not active, expired series. 379 return databaseBufferResetOptions{}, ErrSeriesAllDatapointsExpired 380 } 381 382 return databaseBufferResetOptions{ 383 BlockRetriever: s.blockRetriever, 384 Options: s.opts, 385 }, nil 386 } 387 388 func (s *dbSeries) ReadEncoded( 389 ctx context.Context, 390 start, end xtime.UnixNano, 391 nsCtx namespace.Context, 392 ) (BlockReaderIter, error) { 393 s.RLock() 394 reader := NewReaderUsingRetriever(s.id, s.blockRetriever, s.onRetrieveBlock, s, s.opts) 395 iter, err := reader.readersWithBlocksMapAndBuffer(ctx, start, end, s.cachedBlocks, s.buffer, nsCtx) 396 s.RUnlock() 397 return iter, err 398 } 399 400 func (s *dbSeries) FetchBlocksForColdFlush( 401 ctx context.Context, 402 start xtime.UnixNano, 403 version int, 404 nsCtx namespace.Context, 405 ) (block.FetchBlockResult, error) { 406 // This needs a write lock because the version on underlying buckets need 407 // to be modified. 408 s.Lock() 409 result, err := s.buffer.FetchBlocksForColdFlush(ctx, start, version, nsCtx) 410 s.Unlock() 411 412 return result, err 413 } 414 415 func (s *dbSeries) FetchBlocks( 416 ctx context.Context, 417 starts []xtime.UnixNano, 418 nsCtx namespace.Context, 419 ) ([]block.FetchBlockResult, error) { 420 s.RLock() 421 reader := &Reader{ 422 opts: s.opts, 423 id: s.id, 424 retriever: s.blockRetriever, 425 onRetrieve: s.onRetrieveBlock, 426 } 427 428 r, err := reader.fetchBlocksWithBlocksMapAndBuffer(ctx, starts, s.cachedBlocks, s.buffer, nsCtx) 429 s.RUnlock() 430 return r, err 431 } 432 433 func (s *dbSeries) FetchBlocksMetadata( 434 ctx context.Context, 435 start, end xtime.UnixNano, 436 opts FetchBlocksMetadataOptions, 437 ) (block.FetchBlocksMetadataResult, error) { 438 s.RLock() 439 defer s.RUnlock() 440 441 res := s.opts.FetchBlockMetadataResultsPool().Get() 442 // Iterate over the encoders in the database buffer 443 if !s.buffer.IsEmpty() { 444 bufferResults, err := s.buffer.FetchBlocksMetadata(ctx, start, end, opts) 445 if err != nil { 446 return block.FetchBlocksMetadataResult{}, err 447 } 448 for _, result := range bufferResults.Results() { 449 res.Add(result) 450 } 451 bufferResults.Close() 452 } 453 454 res.Sort() 455 456 // NB(r): Since ID and Tags are garbage collected we can safely 457 // return refs. 458 tagsIter := s.opts.IdentifierPool().TagsIterator() 459 tagsIter.ResetFields(s.metadata.Fields) 460 return block.NewFetchBlocksMetadataResult(s.id, tagsIter, res), nil 461 } 462 463 func (s *dbSeries) addBlockWithLock(b block.DatabaseBlock) { 464 b.SetOnEvictedFromWiredList(s.blockOnEvictedFromWiredList) 465 s.cachedBlocks.AddBlock(b) 466 } 467 468 func (s *dbSeries) LoadBlock( 469 block block.DatabaseBlock, 470 writeType WriteType, 471 ) error { 472 switch writeType { 473 case WarmWrite: 474 at := block.StartTime() 475 alreadyExists, err := s.blockRetriever.IsBlockRetrievable(at) 476 if err != nil { 477 err = fmt.Errorf("error checking warm block load valid: %v", err) 478 instrument.EmitAndLogInvariantViolation(s.opts.InstrumentOptions(), 479 func(l *zap.Logger) { 480 l.Error("warm load block invariant", zap.Error(err)) 481 }) 482 return err 483 } 484 if alreadyExists { 485 err = fmt.Errorf( 486 "warm block load for block that exists: block_start=%s", at) 487 instrument.EmitAndLogInvariantViolation(s.opts.InstrumentOptions(), 488 func(l *zap.Logger) { 489 l.Error("warm load block invariant", zap.Error(err)) 490 }) 491 return err 492 } 493 } 494 495 s.Lock() 496 s.buffer.Load(block, writeType) 497 s.Unlock() 498 return nil 499 } 500 501 func (s *dbSeries) OnRetrieveBlock( 502 id ident.ID, 503 tags ident.TagIterator, 504 startTime xtime.UnixNano, 505 segment ts.Segment, 506 nsCtx namespace.Context, 507 ) { 508 var ( 509 b block.DatabaseBlock 510 list *block.WiredList 511 ) 512 s.Lock() 513 defer func() { 514 s.Unlock() 515 if b != nil && list != nil { 516 // 1) We need to update the WiredList so that blocks that were read from disk 517 // can enter the list (OnReadBlock is only called for blocks that 518 // were read from memory, regardless of whether the data originated 519 // from disk or a buffer rotation.) 520 // 2) We must perform this action outside of the lock to prevent deadlock 521 // with the WiredList itself when it tries to call OnEvictedFromWiredList 522 // on the same series that is trying to perform a blocking update. 523 // 3) Doing this outside of the lock is safe because updating the 524 // wired list is asynchronous already (Update just puts the block in 525 // a channel to be processed later.) 526 // 4) We have to perform a blocking update because in this flow, the block 527 // is not already in the wired list so we need to make sure that the WiredList 528 // takes control of its lifecycle. 529 list.BlockingUpdate(b) 530 } 531 }() 532 533 if !id.Equal(s.id) { 534 return 535 } 536 537 b = s.opts.DatabaseBlockOptions().DatabaseBlockPool().Get() 538 blockSize := s.opts.RetentionOptions().BlockSize() 539 b.ResetFromDisk(startTime, blockSize, segment, s.id, nsCtx) 540 541 // NB(r): Blocks retrieved have been triggered by a read, so set the last 542 // read time as now so caching policies are followed. 543 b.SetLastReadTime(s.now()) 544 545 // If we retrieved this from disk then we directly emplace it 546 s.addBlockWithLock(b) 547 548 list = s.opts.DatabaseBlockOptions().WiredList() 549 } 550 551 // OnReadBlock is only called for blocks that were read from memory, regardless of 552 // whether the data originated from disk or buffer rotation. 553 func (s *dbSeries) OnReadBlock(b block.DatabaseBlock) { 554 if list := s.opts.DatabaseBlockOptions().WiredList(); list != nil { 555 // The WiredList is only responsible for managing the lifecycle of blocks 556 // retrieved from disk. 557 if b.WasRetrievedFromDisk() { 558 // 1) Need to update the WiredList so it knows which blocks have been 559 // most recently read. 560 // 2) We do a non-blocking update here to prevent deadlock with the 561 // WiredList calling OnEvictedFromWiredList on the same series since 562 // OnReadBlock is usually called within the context of a read lock 563 // on this series. 564 // 3) Its safe to do a non-blocking update because the wired list has 565 // already been exposed to this block, so even if the wired list drops 566 // this update, it will still manage this blocks lifecycle. 567 list.NonBlockingUpdate(b) 568 } 569 } 570 } 571 572 func (s *dbSeries) OnEvictedFromWiredList(id ident.ID, blockStart xtime.UnixNano) { 573 s.Lock() 574 defer s.Unlock() 575 576 // id can be nil at this point if this dbSeries gets closed just before it 577 // gets evicted from the wiredlist. 578 if id == nil || s.id == nil || !id.Equal(s.id) { 579 return 580 } 581 582 block, ok := s.cachedBlocks.BlockAt(blockStart) 583 if ok { 584 if !block.WasRetrievedFromDisk() { 585 // Should never happen - invalid application state could cause data loss 586 instrument.EmitAndLogInvariantViolation( 587 s.opts.InstrumentOptions(), func(l *zap.Logger) { 588 l.With( 589 zap.String("id", id.String()), 590 zap.Time("blockStart", blockStart.ToTime()), 591 ).Error("tried to evict block that was not retrieved from disk") 592 }) 593 return 594 } 595 596 s.cachedBlocks.RemoveBlockAt(blockStart) 597 } 598 } 599 600 func (s *dbSeries) WarmFlush( 601 ctx context.Context, 602 blockStart xtime.UnixNano, 603 persistFn persist.DataFn, 604 nsCtx namespace.Context, 605 ) (FlushOutcome, error) { 606 // Need a write lock because the buffer WarmFlush method mutates 607 // state (by performing a pro-active merge). 608 s.Lock() 609 outcome, err := s.buffer.WarmFlush(ctx, blockStart, 610 persist.NewMetadata(s.metadata), persistFn, nsCtx) 611 s.Unlock() 612 return outcome, err 613 } 614 615 func (s *dbSeries) Snapshot( 616 ctx context.Context, 617 blockStart xtime.UnixNano, 618 persistFn persist.DataFn, 619 nsCtx namespace.Context, 620 ) (SnapshotResult, error) { 621 // Need a write lock because the buffer Snapshot method mutates 622 // state (by performing a pro-active merge). 623 s.Lock() 624 result, err := s.buffer.Snapshot(ctx, blockStart, 625 persist.NewMetadata(s.metadata), persistFn, nsCtx) 626 s.Unlock() 627 return result, err 628 } 629 630 func (s *dbSeries) ColdFlushBlockStarts(blockStates BootstrappedBlockStateSnapshot) OptimizedTimes { 631 s.RLock() 632 defer s.RUnlock() 633 634 return s.buffer.ColdFlushBlockStarts(blockStates.Snapshot) 635 } 636 637 func (s *dbSeries) Bootstrap(nsCtx namespace.Context) error { 638 // NB(r): Need to hold the lock the whole time since 639 // this needs to be consistent view for a tick to see. 640 s.Lock() 641 defer s.Unlock() 642 643 s.bootstrap.Lock() 644 bootstrapBuffer := s.bootstrap.buffer 645 s.bootstrap.buffer = nil 646 s.bootstrap.Unlock() 647 648 if bootstrapBuffer == nil { 649 return nil 650 } 651 652 // NB(r): Now bootstrapped need to move bootstrap writes to the 653 // normal series buffer to make them visible to DB. 654 // We store these bootstrap writes in a side buffer so that we don't 655 // need to take the series lock and contend with normal writes 656 // that flow into the DB while bootstrapping which can significantly 657 // interrupt write latency and cause entire DB to stall/degrade in performance. 658 return bootstrapBuffer.MoveTo(s.buffer, nsCtx) 659 } 660 661 func (s *dbSeries) Close() { 662 s.bootstrap.Lock() 663 if s.bootstrap.buffer != nil { 664 s.bootstrap.buffer = nil 665 } 666 s.bootstrap.Unlock() 667 668 s.Lock() 669 defer s.Unlock() 670 671 // See Reset() for why these aren't finalized. 672 s.id = nil 673 s.metadata = doc.Metadata{} 674 s.uniqueIndex = 0 675 676 switch s.opts.CachePolicy() { 677 case CacheLRU: 678 // In the CacheLRU case, blocks that were retrieved from disk are owned 679 // by the WiredList and should not be closed here. They will eventually 680 // be evicted and closed by the WiredList when it needs to make room 681 // for new blocks. 682 default: 683 // This call closes the blocks as well. 684 s.cachedBlocks.RemoveAll() 685 } 686 687 // Reset (not close) underlying resources because the series will go 688 // back into the pool and be re-used. 689 s.buffer.Reset(databaseBufferResetOptions{Options: s.opts}) 690 s.cachedBlocks.Reset() 691 692 if s.pool != nil { 693 s.pool.Put(s) 694 } 695 } 696 697 func (s *dbSeries) Reset(opts DatabaseSeriesOptions) { 698 // NB(r): We explicitly do not place the ID back into an 699 // existing pool as high frequency users of series IDs such 700 // as the commit log need to use the reference without the 701 // overhead of ownership tracking. In addition, the blocks 702 // themselves have a reference to the ID which is required 703 // for the LRU/WiredList caching strategy eviction process. 704 // Since the wired list can still have a reference to a 705 // DatabaseBlock for which the corresponding DatabaseSeries 706 // has been closed, its important that the ID itself is still 707 // available because the process of kicking a DatabaseBlock 708 // out of the WiredList requires the ID. 709 // 710 // Since series are purged so infrequently the overhead 711 // of not releasing back an ID to a pool is amortized over 712 // a long period of time. 713 // 714 // The same goes for the series tags. 715 s.Lock() 716 s.id = opts.ID 717 s.metadata = opts.Metadata 718 s.uniqueIndex = opts.UniqueIndex 719 s.cachedBlocks.Reset() 720 s.buffer.Reset(databaseBufferResetOptions{ 721 BlockRetriever: opts.BlockRetriever, 722 Options: opts.Options, 723 }) 724 s.opts = opts.Options 725 s.blockRetriever = opts.BlockRetriever 726 s.onRetrieveBlock = opts.OnRetrieveBlock 727 s.blockOnEvictedFromWiredList = opts.OnEvictedFromWiredList 728 s.Unlock() 729 }