github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/shard_ref_count_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package storage
    22  
    23  import (
    24  	"fmt"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/fortytw2/leaktest"
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"github.com/uber-go/tally"
    33  
    34  	"github.com/m3db/m3/src/dbnode/namespace"
    35  	"github.com/m3db/m3/src/dbnode/runtime"
    36  	"github.com/m3db/m3/src/dbnode/storage/index"
    37  	"github.com/m3db/m3/src/dbnode/storage/index/convert"
    38  	"github.com/m3db/m3/src/dbnode/storage/series"
    39  	xmetrics "github.com/m3db/m3/src/dbnode/x/metrics"
    40  	"github.com/m3db/m3/src/x/clock"
    41  	"github.com/m3db/m3/src/x/context"
    42  	"github.com/m3db/m3/src/x/ident"
    43  	xsync "github.com/m3db/m3/src/x/sync"
    44  	xtest "github.com/m3db/m3/src/x/test"
    45  	xtime "github.com/m3db/m3/src/x/time"
    46  )
    47  
    48  func TestShardWriteSyncRefCount(t *testing.T) {
    49  	opts := DefaultTestOptions()
    50  	testShardWriteSyncRefCount(t, opts)
    51  }
    52  
    53  func TestShardWriteSyncRefCountVerifyNoCopyAnnotation(t *testing.T) {
    54  	opts := DefaultTestOptions().
    55  		// Set bytes pool to nil to ensure we're not using it to copy annotations
    56  		// on the sync path.
    57  		SetBytesPool(nil)
    58  	testShardWriteSyncRefCount(t, opts)
    59  }
    60  
    61  func testShardWriteSyncRefCount(t *testing.T, opts Options) {
    62  	now := xtime.Now()
    63  
    64  	shard := testDatabaseShard(t, opts)
    65  	shard.SetRuntimeOptions(runtime.NewOptions().
    66  		SetWriteNewSeriesAsync(false))
    67  	defer shard.Close()
    68  
    69  	ctx := context.NewBackground()
    70  	defer ctx.Close()
    71  
    72  	seriesWrite, err := shard.Write(ctx, ident.StringID("foo"), now, 1.0,
    73  		xtime.Second, nil, series.WriteOptions{})
    74  	assert.NoError(t, err)
    75  	assert.True(t, seriesWrite.WasWritten)
    76  
    77  	seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), now, 1.0,
    78  		xtime.Second, nil, series.WriteOptions{})
    79  	assert.NoError(t, err)
    80  	assert.False(t, seriesWrite.WasWritten)
    81  
    82  	seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), now, 2.0,
    83  		xtime.Second, nil, series.WriteOptions{})
    84  	assert.NoError(t, err)
    85  	assert.True(t, seriesWrite.WasWritten)
    86  
    87  	seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), now, 3.0,
    88  		xtime.Second, nil, series.WriteOptions{})
    89  	assert.NoError(t, err)
    90  	assert.True(t, seriesWrite.WasWritten)
    91  
    92  	// ensure all entries have no references left
    93  	for _, id := range []string{"foo", "bar", "baz"} {
    94  		shard.Lock()
    95  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
    96  		shard.Unlock()
    97  		assert.NoError(t, err)
    98  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
    99  	}
   100  
   101  	// write already inserted series'
   102  	next := now.Add(time.Minute)
   103  
   104  	seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), next, 1.0, xtime.Second, nil, series.WriteOptions{})
   105  	assert.NoError(t, err)
   106  	assert.True(t, seriesWrite.WasWritten)
   107  
   108  	seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), next, 2.0, xtime.Second, nil, series.WriteOptions{})
   109  	assert.NoError(t, err)
   110  	assert.True(t, seriesWrite.WasWritten)
   111  
   112  	seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), next, 3.0, xtime.Second, nil, series.WriteOptions{})
   113  	assert.NoError(t, err)
   114  	assert.True(t, seriesWrite.WasWritten)
   115  
   116  	// ensure all entries have no references left
   117  	for _, id := range []string{"foo", "bar", "baz"} {
   118  		shard.Lock()
   119  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   120  		shard.Unlock()
   121  		assert.NoError(t, err)
   122  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   123  	}
   124  }
   125  
   126  func TestShardWriteTaggedSyncRefCountMockIndex(t *testing.T) {
   127  	ctrl := xtest.NewController(t)
   128  	defer ctrl.Finish()
   129  
   130  	blockSize := namespaceIndexOptions.BlockSize()
   131  
   132  	idx := NewMockNamespaceIndex(ctrl)
   133  	idx.EXPECT().BlockStartForWriteTime(gomock.Any()).
   134  		DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano {
   135  			return t.Truncate(blockSize)
   136  		}).
   137  		AnyTimes()
   138  	idx.EXPECT().WriteBatch(gomock.Any()).
   139  		Return(nil).
   140  		Do(func(batch *index.WriteBatch) {
   141  			if batch.Len() != 1 {
   142  				// require.Equal(...) silently kills goroutines
   143  				panic(fmt.Sprintf("expected batch len 1: len=%d", batch.Len()))
   144  			}
   145  
   146  			entry := batch.PendingEntries()[0]
   147  			blockStart := entry.Timestamp.Truncate(blockSize)
   148  			onIdx := entry.OnIndexSeries
   149  			onIdx.OnIndexSuccess(blockStart)
   150  			onIdx.OnIndexFinalize(blockStart)
   151  		}).
   152  		AnyTimes()
   153  
   154  	testShardWriteTaggedSyncRefCount(t, idx)
   155  }
   156  
   157  func TestShardWriteTaggedSyncRefCountSyncIndex(t *testing.T) {
   158  	defer leaktest.CheckTimeout(t, 10*time.Second)()
   159  	newFn := func(
   160  		fn nsIndexInsertBatchFn,
   161  		md namespace.Metadata,
   162  		nowFn clock.NowFn,
   163  		coreFn xsync.CoreFn,
   164  		s tally.Scope,
   165  	) namespaceIndexInsertQueue {
   166  		q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s)
   167  		q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond
   168  		return q
   169  	}
   170  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   171  	require.NoError(t, err)
   172  
   173  	var (
   174  		opts      = DefaultTestOptions()
   175  		indexOpts = opts.IndexOptions().
   176  				SetInsertMode(index.InsertSync)
   177  	)
   178  	opts = opts.SetIndexOptions(indexOpts)
   179  
   180  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
   181  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   182  		testShardSet, newFn, opts)
   183  	assert.NoError(t, err)
   184  
   185  	defer func() {
   186  		assert.NoError(t, idx.Close())
   187  	}()
   188  
   189  	testShardWriteTaggedSyncRefCount(t, idx)
   190  }
   191  
   192  func testShardWriteTaggedSyncRefCount(t *testing.T, idx NamespaceIndex) {
   193  	var (
   194  		now   = xtime.Now()
   195  		opts  = DefaultTestOptions()
   196  		shard = testDatabaseShardWithIndexFn(t, opts, idx, false)
   197  	)
   198  
   199  	shard.SetRuntimeOptions(runtime.NewOptions().
   200  		SetWriteNewSeriesAsync(false))
   201  	defer shard.Close()
   202  
   203  	ctx := context.NewBackground()
   204  	defer ctx.Close()
   205  
   206  	seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"),
   207  		convert.EmptyTagMetadataResolver, now, 1.0, xtime.Second, nil, series.WriteOptions{})
   208  	assert.NoError(t, err)
   209  	assert.True(t, seriesWrite.WasWritten)
   210  
   211  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"),
   212  		convert.EmptyTagMetadataResolver, now, 2.0, xtime.Second, nil, series.WriteOptions{})
   213  	assert.NoError(t, err)
   214  	assert.True(t, seriesWrite.WasWritten)
   215  
   216  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"),
   217  		convert.EmptyTagMetadataResolver, now, 3.0, xtime.Second, nil, series.WriteOptions{})
   218  	assert.NoError(t, err)
   219  	assert.True(t, seriesWrite.WasWritten)
   220  
   221  	// ensure all entries have no references left
   222  	for _, id := range []string{"foo", "bar", "baz"} {
   223  		shard.Lock()
   224  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   225  		shard.Unlock()
   226  		assert.NoError(t, err)
   227  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   228  	}
   229  
   230  	// write already inserted series'
   231  	next := now.Add(time.Minute)
   232  
   233  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"),
   234  		convert.EmptyTagMetadataResolver, next, 1.0, xtime.Second, nil, series.WriteOptions{})
   235  	assert.NoError(t, err)
   236  	assert.True(t, seriesWrite.WasWritten)
   237  
   238  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"),
   239  		convert.EmptyTagMetadataResolver, next, 2.0, xtime.Second, nil, series.WriteOptions{})
   240  	assert.NoError(t, err)
   241  	assert.True(t, seriesWrite.WasWritten)
   242  
   243  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"),
   244  		convert.EmptyTagMetadataResolver, next, 3.0, xtime.Second, nil, series.WriteOptions{})
   245  	assert.NoError(t, err)
   246  	assert.True(t, seriesWrite.WasWritten)
   247  
   248  	// ensure all entries have no references left
   249  	for _, id := range []string{"foo", "bar", "baz"} {
   250  		shard.Lock()
   251  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   252  		shard.Unlock()
   253  		assert.NoError(t, err)
   254  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   255  	}
   256  }
   257  
   258  func TestShardWriteAsyncRefCount(t *testing.T) {
   259  	testReporter := xmetrics.NewTestStatsReporter(xmetrics.NewTestStatsReporterOptions())
   260  	scope, closer := tally.NewRootScope(tally.ScopeOptions{
   261  		Reporter: testReporter,
   262  	}, 100*time.Millisecond)
   263  	defer closer.Close()
   264  
   265  	now := xtime.Now()
   266  	opts := DefaultTestOptions()
   267  	opts = opts.SetInstrumentOptions(
   268  		opts.InstrumentOptions().
   269  			SetMetricsScope(scope).
   270  			SetReportInterval(100 * time.Millisecond))
   271  
   272  	shard := testDatabaseShard(t, opts)
   273  	shard.SetRuntimeOptions(runtime.NewOptions().
   274  		SetWriteNewSeriesAsync(true))
   275  	defer shard.Close()
   276  
   277  	ctx := context.NewBackground()
   278  	defer ctx.Close()
   279  
   280  	seriesWrite, err := shard.Write(ctx, ident.StringID("foo"), now, 1.0,
   281  		xtime.Second, nil, series.WriteOptions{})
   282  	assert.NoError(t, err)
   283  	assert.True(t, seriesWrite.WasWritten)
   284  
   285  	seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), now, 2.0,
   286  		xtime.Second, nil, series.WriteOptions{})
   287  	assert.NoError(t, err)
   288  	assert.True(t, seriesWrite.WasWritten)
   289  
   290  	seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), now, 3.0,
   291  		xtime.Second, nil, series.WriteOptions{})
   292  	assert.NoError(t, err)
   293  	assert.True(t, seriesWrite.WasWritten)
   294  
   295  	inserted := clock.WaitUntil(func() bool {
   296  		counter, ok := testReporter.Counters()["dbshard.insert-queue.inserts"]
   297  		return ok && counter == 3
   298  	}, 2*time.Second)
   299  	assert.True(t, inserted)
   300  
   301  	// ensure all entries have no references left
   302  	for _, id := range []string{"foo", "bar", "baz"} {
   303  		shard.Lock()
   304  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   305  		shard.Unlock()
   306  		assert.NoError(t, err)
   307  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   308  	}
   309  
   310  	// write already inserted series'
   311  	next := now.Add(time.Minute)
   312  
   313  	seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), next, 1.0, xtime.Second, nil, series.WriteOptions{})
   314  	assert.NoError(t, err)
   315  	assert.True(t, seriesWrite.WasWritten)
   316  
   317  	seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), next, 2.0, xtime.Second, nil, series.WriteOptions{})
   318  	assert.NoError(t, err)
   319  	assert.True(t, seriesWrite.WasWritten)
   320  
   321  	seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), next, 3.0, xtime.Second, nil, series.WriteOptions{})
   322  	assert.NoError(t, err)
   323  	assert.True(t, seriesWrite.WasWritten)
   324  
   325  	// ensure all entries have no references left
   326  	for _, id := range []string{"foo", "bar", "baz"} {
   327  		shard.Lock()
   328  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   329  		shard.Unlock()
   330  		assert.NoError(t, err)
   331  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   332  	}
   333  }
   334  
   335  func newNowFnForWriteTaggedAsyncRefCount() func() time.Time {
   336  	// Explicitly truncate the time to the beginning of the index block size to prevent
   337  	// the ref-counts from being thrown off by needing to re-index entries because the first
   338  	// write happened near a block boundary so that when the second write comes in they need
   339  	// to be re-indexed for the next block time.
   340  	start := time.Now().Truncate(namespaceIndexOptions.BlockSize())
   341  
   342  	return func() time.Time {
   343  		return start
   344  	}
   345  }
   346  
   347  func TestShardWriteTaggedAsyncRefCountMockIndex(t *testing.T) {
   348  	ctrl := xtest.NewController(t)
   349  	defer ctrl.Finish()
   350  
   351  	blockSize := namespaceIndexOptions.BlockSize()
   352  
   353  	idx := NewMockNamespaceIndex(ctrl)
   354  	idx.EXPECT().BlockStartForWriteTime(gomock.Any()).
   355  		DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano {
   356  			return t.Truncate(blockSize)
   357  		}).
   358  		AnyTimes()
   359  	idx.EXPECT().WriteBatch(gomock.Any()).
   360  		Return(nil).
   361  		Do(func(batch *index.WriteBatch) {
   362  			for _, entry := range batch.PendingEntries() {
   363  				blockStart := entry.Timestamp.Truncate(blockSize)
   364  				onIdx := entry.OnIndexSeries
   365  				onIdx.OnIndexSuccess(blockStart)
   366  				onIdx.OnIndexFinalize(blockStart)
   367  			}
   368  		}).
   369  		AnyTimes()
   370  
   371  	nowFn := newNowFnForWriteTaggedAsyncRefCount()
   372  	testShardWriteTaggedAsyncRefCount(t, idx, nowFn)
   373  }
   374  
   375  func TestShardWriteTaggedAsyncRefCountSyncIndex(t *testing.T) {
   376  	defer leaktest.CheckTimeout(t, 10*time.Second)()
   377  	newFn := func(fn nsIndexInsertBatchFn, md namespace.Metadata,
   378  		nowFn clock.NowFn, coreFn xsync.CoreFn, s tally.Scope) namespaceIndexInsertQueue {
   379  		q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s)
   380  		q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond
   381  		return q
   382  	}
   383  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   384  	require.NoError(t, err)
   385  
   386  	nowFn := newNowFnForWriteTaggedAsyncRefCount()
   387  	var (
   388  		opts      = DefaultTestOptions()
   389  		indexOpts = opts.IndexOptions().
   390  				SetInsertMode(index.InsertSync)
   391  	)
   392  	opts = opts.
   393  		SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   394  	indexOpts = indexOpts.
   395  		SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   396  	opts = opts.SetIndexOptions(indexOpts)
   397  
   398  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
   399  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   400  		testShardSet, newFn, opts)
   401  	assert.NoError(t, err)
   402  
   403  	defer func() {
   404  		assert.NoError(t, idx.Close())
   405  	}()
   406  
   407  	testShardWriteTaggedAsyncRefCount(t, idx, nowFn)
   408  }
   409  
   410  func testShardWriteTaggedAsyncRefCount(t *testing.T, idx NamespaceIndex, nowFn func() time.Time) {
   411  	testReporter := xmetrics.NewTestStatsReporter(xmetrics.NewTestStatsReporterOptions())
   412  	scope, closer := tally.NewRootScope(tally.ScopeOptions{
   413  		Reporter: testReporter,
   414  	}, 100*time.Millisecond)
   415  	defer closer.Close()
   416  
   417  	var (
   418  		start = xtime.ToUnixNano(nowFn())
   419  		now   = start
   420  		opts  = DefaultTestOptions()
   421  	)
   422  	opts = opts.
   423  		SetInstrumentOptions(
   424  			opts.InstrumentOptions().
   425  				SetMetricsScope(scope).
   426  				SetReportInterval(100 * time.Millisecond))
   427  	opts = opts.
   428  		SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   429  
   430  	shard := testDatabaseShardWithIndexFn(t, opts, idx, false)
   431  	shard.SetRuntimeOptions(runtime.NewOptions().
   432  		SetWriteNewSeriesAsync(true))
   433  	defer shard.Close()
   434  
   435  	ctx := context.NewBackground()
   436  	defer ctx.Close()
   437  
   438  	seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"),
   439  		convert.EmptyTagMetadataResolver, now, 1.0, xtime.Second, nil, series.WriteOptions{})
   440  	assert.NoError(t, err)
   441  	assert.True(t, seriesWrite.WasWritten)
   442  	assert.True(t, seriesWrite.NeedsIndex)
   443  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now))
   444  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now))
   445  
   446  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"),
   447  		convert.EmptyTagMetadataResolver, now, 2.0, xtime.Second, nil, series.WriteOptions{})
   448  	assert.NoError(t, err)
   449  	assert.True(t, seriesWrite.WasWritten)
   450  	assert.True(t, seriesWrite.NeedsIndex)
   451  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now))
   452  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now))
   453  
   454  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"),
   455  		convert.EmptyTagMetadataResolver, now, 3.0, xtime.Second, nil, series.WriteOptions{})
   456  	assert.NoError(t, err)
   457  	assert.True(t, seriesWrite.WasWritten)
   458  	assert.True(t, seriesWrite.NeedsIndex)
   459  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now))
   460  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now))
   461  
   462  	inserted := clock.WaitUntil(func() bool {
   463  		counter, ok := testReporter.Counters()["dbshard.insert-queue.inserts"]
   464  		return ok && counter == 3
   465  	}, 5*time.Second)
   466  	assert.True(t, inserted)
   467  
   468  	// ensure all entries have no references left
   469  	for _, id := range []string{"foo", "bar", "baz"} {
   470  		shard.Lock()
   471  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   472  		shard.Unlock()
   473  		assert.NoError(t, err)
   474  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   475  	}
   476  
   477  	// write already inserted series'
   478  	next := now.Add(time.Minute)
   479  
   480  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"),
   481  		convert.EmptyTagMetadataResolver, next, 1.0, xtime.Second, nil, series.WriteOptions{})
   482  	assert.NoError(t, err)
   483  	assert.True(t, seriesWrite.WasWritten)
   484  
   485  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"),
   486  		convert.EmptyTagMetadataResolver, next, 2.0, xtime.Second, nil, series.WriteOptions{})
   487  	assert.NoError(t, err)
   488  	assert.True(t, seriesWrite.WasWritten)
   489  
   490  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"),
   491  		convert.EmptyTagMetadataResolver, next, 3.0, xtime.Second, nil, series.WriteOptions{})
   492  	assert.NoError(t, err)
   493  	assert.True(t, seriesWrite.WasWritten)
   494  
   495  	// ensure all entries have no references left
   496  	for _, id := range []string{"foo", "bar", "baz"} {
   497  		shard.Lock()
   498  		entry, err := shard.lookupEntryWithLock(ident.StringID(id))
   499  		shard.Unlock()
   500  		assert.NoError(t, err)
   501  		assert.Equal(t, int32(0), entry.ReaderWriterCount(), id)
   502  	}
   503  }