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  }