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

     1  // Copyright (c) 2019 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  	"errors"
    25  	"testing"
    26  
    27  	"github.com/m3db/m3/src/dbnode/namespace"
    28  	"github.com/m3db/m3/src/dbnode/storage/block"
    29  	"github.com/m3db/m3/src/dbnode/storage/series"
    30  	"github.com/m3db/m3/src/dbnode/x/xio"
    31  	"github.com/m3db/m3/src/m3ninx/doc"
    32  	"github.com/m3db/m3/src/x/context"
    33  	"github.com/m3db/m3/src/x/ident"
    34  	xtest "github.com/m3db/m3/src/x/test"
    35  	xtime "github.com/m3db/m3/src/x/time"
    36  
    37  	"github.com/golang/mock/gomock"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  type dirtyData struct {
    43  	id    ident.ID
    44  	start xtime.UnixNano
    45  }
    46  
    47  func TestRead(t *testing.T) {
    48  	ctrl := xtest.NewController(t)
    49  	defer ctrl.Finish()
    50  
    51  	shard := NewMockdatabaseShard(ctrl)
    52  	retriever := series.NewMockQueryableBlockRetriever(ctrl)
    53  	version := 0
    54  	ctx := context.NewBackground()
    55  	nsCtx := namespace.Context{}
    56  	result := block.FetchBlockResult{
    57  		Blocks: []xio.BlockReader{{}},
    58  	}
    59  	retriever.EXPECT().RetrievableBlockColdVersion(gomock.Any()).Return(version, nil).AnyTimes()
    60  
    61  	dirtySeries := newDirtySeriesMap()
    62  	dirtySeriesToWrite := make(map[xtime.UnixNano]*idList)
    63  
    64  	data := []dirtyData{
    65  		{start: 0, id: ident.StringID("id0")},
    66  		{start: 0, id: ident.StringID("id1")},
    67  		{start: 1, id: ident.StringID("id2")},
    68  		{start: 1, id: ident.StringID("id3")},
    69  		{start: 1, id: ident.StringID("id4")},
    70  		{start: 2, id: ident.StringID("id5")},
    71  		{start: 3, id: ident.StringID("id6")},
    72  		{start: 3, id: ident.StringID("id7")},
    73  		{start: 4, id: ident.StringID("id8")},
    74  	}
    75  
    76  	// Populate bookkeeping data structures with above test data.
    77  	for _, d := range data {
    78  		addDirtySeries(dirtySeries, dirtySeriesToWrite, d.id, d.start)
    79  		shard.EXPECT().
    80  			FetchBlocksForColdFlush(gomock.Any(), d.id, d.start, version+1, nsCtx).
    81  			Return(result, nil)
    82  	}
    83  
    84  	mergeWith := newFSMergeWithMem(shard, retriever, dirtySeries, dirtySeriesToWrite)
    85  
    86  	for _, d := range data {
    87  		require.True(t, dirtySeries.Contains(idAndBlockStart{
    88  			blockStart: d.start,
    89  			id:         d.id.Bytes(),
    90  		}))
    91  		beforeLen := dirtySeriesToWrite[d.start].Len()
    92  		res, exists, err := mergeWith.Read(ctx, d.id, d.start, nsCtx)
    93  		require.NoError(t, err)
    94  		assert.True(t, exists)
    95  		assert.Equal(t, result.Blocks, res)
    96  		// Assert that the Read call removes the element from the "to write"
    97  		// list.
    98  		assert.Equal(t, beforeLen-1, dirtySeriesToWrite[d.start].Len())
    99  	}
   100  
   101  	// Test Read with non-existent dirty block/series.
   102  	res, exists, err := mergeWith.Read(ctx, ident.StringID("not-present"), 10, nsCtx)
   103  	assert.Nil(t, res)
   104  	assert.False(t, exists)
   105  	assert.NoError(t, err)
   106  
   107  	// Test Read with error on fetch.
   108  	badFetchID := ident.StringID("bad-fetch")
   109  	addDirtySeries(dirtySeries, dirtySeriesToWrite, badFetchID, 11)
   110  	shard.EXPECT().
   111  		FetchBlocksForColdFlush(gomock.Any(), badFetchID, gomock.Any(), version+1, nsCtx).
   112  		Return(block.FetchBlockResult{}, errors.New("fetch error"))
   113  	res, exists, err = mergeWith.Read(ctx, badFetchID, 11, nsCtx)
   114  	assert.Nil(t, res)
   115  	assert.False(t, exists)
   116  	assert.Error(t, err)
   117  
   118  	// Test Read with no data on fetch.
   119  	emptyDataID := ident.StringID("empty-data")
   120  	addDirtySeries(dirtySeries, dirtySeriesToWrite, emptyDataID, 12)
   121  	shard.EXPECT().
   122  		FetchBlocksForColdFlush(gomock.Any(), emptyDataID, gomock.Any(), version+1, nsCtx).
   123  		Return(block.FetchBlockResult{}, nil)
   124  	res, exists, err = mergeWith.Read(ctx, emptyDataID, 12, nsCtx)
   125  	assert.Nil(t, res)
   126  	assert.False(t, exists)
   127  	assert.NoError(t, err)
   128  }
   129  
   130  func TestForEachRemaining(t *testing.T) {
   131  	ctrl := xtest.NewController(t)
   132  	defer ctrl.Finish()
   133  
   134  	shard := NewMockdatabaseShard(ctrl)
   135  	retriever := series.NewMockQueryableBlockRetriever(ctrl)
   136  	version := 0
   137  	ctx := context.NewBackground()
   138  	nsCtx := namespace.Context{}
   139  	result := block.FetchBlockResult{
   140  		Blocks: []xio.BlockReader{{}},
   141  	}
   142  	retriever.EXPECT().RetrievableBlockColdVersion(gomock.Any()).Return(version, nil).AnyTimes()
   143  
   144  	dirtySeries := newDirtySeriesMap()
   145  	dirtySeriesToWrite := make(map[xtime.UnixNano]*idList)
   146  
   147  	id0 := ident.StringID("id0")
   148  	id1 := ident.StringID("id1")
   149  	id2 := ident.StringID("id2")
   150  	id3 := ident.StringID("id3")
   151  	id4 := ident.StringID("id4")
   152  	id5 := ident.StringID("id5")
   153  	id6 := ident.StringID("id6")
   154  	id7 := ident.StringID("id7")
   155  	id8 := ident.StringID("id8")
   156  	data := []dirtyData{
   157  		{start: 0, id: id0},
   158  		{start: 0, id: id1},
   159  		{start: 1, id: id2},
   160  		{start: 1, id: id3},
   161  		{start: 1, id: id4},
   162  		{start: 2, id: id5},
   163  		{start: 3, id: id6},
   164  		{start: 3, id: id7},
   165  		{start: 4, id: id8},
   166  	}
   167  
   168  	// Populate bookkeeping data structures with above test data.
   169  	for _, d := range data {
   170  		addDirtySeries(dirtySeries, dirtySeriesToWrite, d.id, d.start)
   171  	}
   172  
   173  	mergeWith := newFSMergeWithMem(shard, retriever, dirtySeries, dirtySeriesToWrite)
   174  
   175  	var forEachCalls []doc.Metadata
   176  	shard.EXPECT().
   177  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id0"),
   178  			xtime.UnixNano(0), version+1, gomock.Any()).
   179  		Return(result, nil)
   180  	shard.EXPECT().
   181  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id1"),
   182  			xtime.UnixNano(0), version+1, gomock.Any()).
   183  		Return(result, nil)
   184  	err := mergeWith.ForEachRemaining(ctx, 0,
   185  		func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error {
   186  			forEachCalls = append(forEachCalls, seriesMetadata)
   187  			return nil
   188  		}, nsCtx)
   189  	require.NoError(t, err)
   190  	require.Len(t, forEachCalls, 2)
   191  	assert.Equal(t, id0.Bytes(), forEachCalls[0].ID)
   192  	assert.Equal(t, id1.Bytes(), forEachCalls[1].ID)
   193  
   194  	// Reset expected calls.
   195  	forEachCalls = forEachCalls[:0]
   196  	// Read id3 at block start 1, so id2 and id4 should be remaining for block
   197  	// start 1.
   198  	shard.EXPECT().
   199  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id3"),
   200  			xtime.UnixNano(1), version+1, nsCtx).
   201  		Return(result, nil)
   202  	res, exists, err := mergeWith.Read(ctx, id3, 1, nsCtx)
   203  	require.NoError(t, err)
   204  	assert.True(t, exists)
   205  	assert.Equal(t, result.Blocks, res)
   206  	shard.EXPECT().
   207  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id2"),
   208  			xtime.UnixNano(1), version+1, gomock.Any()).
   209  		Return(result, nil)
   210  	shard.EXPECT().
   211  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id4"),
   212  			xtime.UnixNano(1), version+1, gomock.Any()).
   213  		Return(result, nil)
   214  	err = mergeWith.ForEachRemaining(ctx, 1, func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error {
   215  		forEachCalls = append(forEachCalls, seriesMetadata)
   216  		return nil
   217  	}, nsCtx)
   218  	require.NoError(t, err)
   219  	require.Len(t, forEachCalls, 2)
   220  	assert.Equal(t, id2.Bytes(), forEachCalls[0].ID)
   221  	assert.Equal(t, id4.Bytes(), forEachCalls[1].ID)
   222  
   223  	shard.EXPECT().
   224  		FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id8"),
   225  			xtime.UnixNano(4), version+1, gomock.Any()).
   226  		Return(result, nil)
   227  
   228  	// Test call with bad function execution.
   229  	err = mergeWith.ForEachRemaining(ctx, 4, func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error {
   230  		return errors.New("bad")
   231  	}, nsCtx)
   232  	assert.Error(t, err)
   233  }
   234  
   235  func addDirtySeries(
   236  	dirtySeries *dirtySeriesMap,
   237  	dirtySeriesToWrite map[xtime.UnixNano]*idList,
   238  	id ident.ID,
   239  	start xtime.UnixNano,
   240  ) {
   241  	seriesList := dirtySeriesToWrite[start]
   242  	if seriesList == nil {
   243  		seriesList = newIDList(nil)
   244  		dirtySeriesToWrite[start] = seriesList
   245  	}
   246  	element := seriesList.PushBack(doc.Metadata{ID: id.Bytes()})
   247  
   248  	dirtySeries.Set(idAndBlockStart{blockStart: start, id: id.Bytes()}, element)
   249  }