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  }