github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/shard_index_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  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/namespace"
    29  	"github.com/m3db/m3/src/dbnode/runtime"
    30  	"github.com/m3db/m3/src/dbnode/storage/index"
    31  	"github.com/m3db/m3/src/dbnode/storage/index/convert"
    32  	"github.com/m3db/m3/src/dbnode/storage/series"
    33  	"github.com/m3db/m3/src/m3ninx/doc"
    34  	"github.com/m3db/m3/src/x/context"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	xtest "github.com/m3db/m3/src/x/test"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  
    39  	"github.com/fortytw2/leaktest"
    40  	"github.com/golang/mock/gomock"
    41  	"github.com/stretchr/testify/assert"
    42  	"github.com/stretchr/testify/require"
    43  )
    44  
    45  func TestShardInsertNamespaceIndex(t *testing.T) {
    46  	defer leaktest.CheckTimeout(t, 2*time.Second)()
    47  	opts := DefaultTestOptions()
    48  
    49  	lock := sync.Mutex{}
    50  	indexWrites := []doc.Metadata{}
    51  
    52  	now := xtime.Now()
    53  	blockSize := namespace.NewIndexOptions().BlockSize()
    54  
    55  	blockStart := now.Truncate(blockSize)
    56  
    57  	ctrl := xtest.NewController(t)
    58  	defer ctrl.Finish()
    59  	idx := NewMockNamespaceIndex(ctrl)
    60  	idx.EXPECT().BlockStartForWriteTime(gomock.Any()).Return(blockStart).AnyTimes()
    61  	idx.EXPECT().WriteBatch(gomock.Any()).Do(
    62  		func(batch *index.WriteBatch) {
    63  
    64  			lock.Lock()
    65  			indexWrites = append(indexWrites, batch.PendingDocs()...)
    66  			lock.Unlock()
    67  			for i, e := range batch.PendingEntries() {
    68  				e.OnIndexSeries.OnIndexSuccess(blockStart)
    69  				e.OnIndexSeries.OnIndexFinalize(blockStart)
    70  				batch.PendingEntries()[i].OnIndexSeries = nil
    71  			}
    72  		}).Return(nil).AnyTimes()
    73  
    74  	shard := testDatabaseShardWithIndexFn(t, opts, idx, false)
    75  	shard.SetRuntimeOptions(runtime.NewOptions().SetWriteNewSeriesAsync(false))
    76  	defer shard.Close()
    77  
    78  	ctx := context.NewBackground()
    79  	defer ctx.Close()
    80  
    81  	seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"),
    82  		convert.NewTagsIterMetadataResolver(ident.NewTagsIterator(ident.NewTags(ident.StringTag("name", "value")))),
    83  		now, 1.0, xtime.Second, nil, series.WriteOptions{})
    84  	require.NoError(t, err)
    85  	require.True(t, seriesWrite.WasWritten)
    86  
    87  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"),
    88  		convert.NewTagsIterMetadataResolver(ident.NewTagsIterator(ident.NewTags(ident.StringTag("name", "value")))),
    89  		now, 2.0, xtime.Second, nil, series.WriteOptions{})
    90  	require.NoError(t, err)
    91  	require.True(t, seriesWrite.WasWritten)
    92  
    93  	seriesWrite, err = shard.Write(
    94  		ctx, ident.StringID("baz"), now, 1.0, xtime.Second, nil, series.WriteOptions{})
    95  	require.NoError(t, err)
    96  	require.True(t, seriesWrite.WasWritten)
    97  
    98  	lock.Lock()
    99  	defer lock.Unlock()
   100  
   101  	require.Len(t, indexWrites, 1)
   102  	require.Equal(t, []byte("foo"), indexWrites[0].ID)
   103  	require.Equal(t, []byte("name"), indexWrites[0].Fields[0].Name)
   104  	require.Equal(t, []byte("value"), indexWrites[0].Fields[0].Value)
   105  }
   106  
   107  func TestShardAsyncInsertMarkIndexedForBlockStart(t *testing.T) {
   108  	ctrl := xtest.NewController(t)
   109  	defer ctrl.Finish()
   110  	defer leaktest.CheckTimeout(t, 2*time.Second)()
   111  
   112  	opts := DefaultTestOptions()
   113  	blockSize := time.Hour
   114  	now := xtime.Now()
   115  	nextWriteTime := now.Truncate(blockSize)
   116  	idx := NewMockNamespaceIndex(ctrl)
   117  	idx.EXPECT().BlockStartForWriteTime(gomock.Any()).
   118  		DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano {
   119  			return t.Truncate(blockSize)
   120  		}).
   121  		AnyTimes()
   122  	shard := testDatabaseShardWithIndexFn(t, opts, idx, false)
   123  	shard.SetRuntimeOptions(runtime.NewOptions().SetWriteNewSeriesAsync(true))
   124  	defer shard.Close()
   125  
   126  	ctx := context.NewBackground()
   127  	defer ctx.Close()
   128  
   129  	// write first time
   130  	seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"),
   131  		convert.NewTagsIterMetadataResolver(ident.NewTagsIterator(ident.NewTags(ident.StringTag("name", "value")))),
   132  		now, 1.0, xtime.Second, nil, series.WriteOptions{})
   133  	assert.NoError(t, err)
   134  	assert.True(t, seriesWrite.WasWritten)
   135  	assert.True(t, seriesWrite.NeedsIndex)
   136  
   137  	// mark as indexed
   138  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(nextWriteTime)
   139  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(nextWriteTime)
   140  
   141  	start := time.Now()
   142  	for time.Since(start) < 10*time.Second {
   143  		entry, _, err := shard.TryRetrieveSeriesAndIncrementReaderWriterCount(ident.StringID("foo"))
   144  		require.NoError(t, err)
   145  		if entry == nil {
   146  			time.Sleep(10 * time.Millisecond)
   147  			continue
   148  		}
   149  		assert.True(t, entry.IndexedForBlockStart(nextWriteTime))
   150  		break // done
   151  	}
   152  }
   153  
   154  func TestShardAsyncIndexIfExpired(t *testing.T) {
   155  	defer leaktest.CheckTimeout(t, 2*time.Second)()
   156  
   157  	// Make now not rounded exactly to the block size
   158  	blockSize := time.Minute
   159  	now := xtime.Now().Truncate(blockSize).Add(time.Second)
   160  
   161  	ctrl := xtest.NewController(t)
   162  	defer ctrl.Finish()
   163  	idx := NewMockNamespaceIndex(ctrl)
   164  	idx.EXPECT().BlockStartForWriteTime(gomock.Any()).
   165  		DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano {
   166  			return t.Truncate(blockSize)
   167  		}).
   168  		AnyTimes()
   169  
   170  	opts := DefaultTestOptions()
   171  	shard := testDatabaseShardWithIndexFn(t, opts, idx, false)
   172  	shard.SetRuntimeOptions(runtime.NewOptions().SetWriteNewSeriesAsync(true))
   173  	defer shard.Close()
   174  
   175  	ctx := context.NewBackground()
   176  	defer ctx.Close()
   177  
   178  	seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"),
   179  		convert.NewTagsIterMetadataResolver(
   180  			ident.NewTagsIterator(ident.NewTags(ident.StringTag("name", "value")))),
   181  		now, 1.0, xtime.Second, nil, series.WriteOptions{})
   182  	assert.NoError(t, err)
   183  	assert.True(t, seriesWrite.WasWritten)
   184  	assert.True(t, seriesWrite.NeedsIndex)
   185  
   186  	// mark as indexed
   187  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(now.Truncate(blockSize))
   188  	seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(now.Truncate(blockSize))
   189  
   190  	// make sure next block not marked as indexed
   191  	start := time.Now()
   192  	for time.Since(start) < 10*time.Second {
   193  		entry, _, err := shard.TryRetrieveSeriesAndIncrementReaderWriterCount(ident.StringID("foo"))
   194  		require.NoError(t, err)
   195  		if entry == nil {
   196  			time.Sleep(10 * time.Millisecond)
   197  			continue
   198  		}
   199  		assert.True(t, entry.IndexedForBlockStart(now.Truncate(blockSize)))
   200  		break // done
   201  	}
   202  
   203  	// ensure we would need to index next block because it's expired
   204  	nextWriteTime := now.Add(blockSize)
   205  	seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"),
   206  		convert.NewTagsIterMetadataResolver(
   207  			ident.NewTagsIterator(ident.NewTags(ident.StringTag("name", "value")))),
   208  		nextWriteTime, 2.0, xtime.Second, nil, series.WriteOptions{})
   209  	assert.NoError(t, err)
   210  	assert.True(t, seriesWrite.WasWritten)
   211  	assert.True(t, seriesWrite.NeedsIndex)
   212  }
   213  
   214  // TODO(prateek): wire tests above to use the field `ts`
   215  // nolint
   216  type testIndexWrite struct {
   217  	id   ident.ID
   218  	tags ident.Tags
   219  	ts   time.Time
   220  }