github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/namespace_readers_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/persist/fs"
    29  	"github.com/m3db/m3/src/dbnode/storage/block"
    30  	"github.com/m3db/m3/src/x/checked"
    31  	"github.com/m3db/m3/src/x/ident"
    32  	"github.com/m3db/m3/src/x/pool"
    33  	xtest "github.com/m3db/m3/src/x/test"
    34  	xtime "github.com/m3db/m3/src/x/time"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/require"
    38  	"github.com/uber-go/tally"
    39  )
    40  
    41  func TestNamespaceReadersGet(t *testing.T) {
    42  	ctrl := xtest.NewController(t)
    43  	defer ctrl.Finish()
    44  
    45  	metadata, err := namespace.NewMetadata(defaultTestNs1ID,
    46  		defaultTestNs1Opts.SetColdWritesEnabled(true))
    47  	require.NoError(t, err)
    48  	shard := uint32(0)
    49  	blockSize := metadata.Options().RetentionOptions().BlockSize()
    50  	start := xtime.Now().Truncate(blockSize)
    51  
    52  	mockBlockLeaseMgr := block.NewMockLeaseManager(ctrl)
    53  	mockBlockLeaseMgr.EXPECT().RegisterLeaser(gomock.Any()).Return(nil)
    54  	// Case 1: open new reader.
    55  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
    56  		Return(block.LeaseState{Volume: 0}, nil)
    57  	// Case 2: open new reader and fast forward to the middle of the volume as
    58  	// specified by the reader position.
    59  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
    60  		Return(block.LeaseState{Volume: 0}, nil)
    61  	// Case 3: request a reader for the middle of volume 1, but the latest
    62  	// volume is 3, so open a new reader for volume 3 from the beginning.
    63  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
    64  		Return(block.LeaseState{Volume: 3}, nil)
    65  	// Case 4: request for a reader that we have cached, so make sure we use
    66  	// the cached reader.
    67  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
    68  		Return(block.LeaseState{Volume: 2}, nil)
    69  	// Case 5: request for a reader that we don't have cached. However, we do
    70  	// have a closed reader so we should reuse that one.
    71  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
    72  		Return(block.LeaseState{Volume: 1}, nil)
    73  
    74  	nsReaderMgr := newNamespaceReaderManager(metadata, tally.NoopScope,
    75  		DefaultTestOptions().SetBlockLeaseManager(mockBlockLeaseMgr))
    76  	nsReaderMgrImpl := nsReaderMgr.(*namespaceReaderManager)
    77  	// Grabbing specific ID here so that the test can match on gomock arguments.
    78  	nsID := nsReaderMgrImpl.namespace.ID()
    79  	mockFSReader := fs.NewMockDataFileSetReader(ctrl)
    80  	// Case 1.
    81  	mockFSReader.EXPECT().Open(fs.DataReaderOpenOptions{
    82  		Identifier: fs.FileSetFileIdentifier{
    83  			Namespace: nsID, Shard: shard, BlockStart: start,
    84  			VolumeIndex: 0,
    85  		},
    86  	}).Return(nil)
    87  	mockFSReader.EXPECT().ValidateMetadata().Return(nil)
    88  	// Case 2.
    89  	mockFSReader.EXPECT().Open(fs.DataReaderOpenOptions{
    90  		Identifier: fs.FileSetFileIdentifier{
    91  			Namespace: nsID, Shard: shard, BlockStart: start,
    92  			VolumeIndex: 0,
    93  		},
    94  	}).Return(nil)
    95  	mockFSReader.EXPECT().ValidateMetadata().Return(nil)
    96  	mockFSReader.EXPECT().Read().Return(ident.StringID("id"),
    97  		ident.NewTagsIterator(ident.Tags{}), checked.NewBytes([]byte{}, nil),
    98  		uint32(0), nil).Times(2)
    99  	mockFSReader.EXPECT().ReadMetadata().Return(ident.StringID("id"),
   100  		ident.NewTagsIterator(ident.Tags{}), 0, uint32(0), nil).Times(3)
   101  	// Case 3.
   102  	mockFSReader.EXPECT().Open(fs.DataReaderOpenOptions{
   103  		Identifier: fs.FileSetFileIdentifier{
   104  			Namespace: nsID, Shard: shard, BlockStart: start,
   105  			VolumeIndex: 3,
   106  		},
   107  	}).Return(nil)
   108  	mockFSReader.EXPECT().ValidateMetadata().Return(nil)
   109  
   110  	nsReaderMgrImpl.newReaderFn = func(
   111  		bytesPool pool.CheckedBytesPool,
   112  		opts fs.Options,
   113  	) (fs.DataFileSetReader, error) {
   114  		return mockFSReader, nil
   115  	}
   116  
   117  	// Case 1.
   118  	_, err = nsReaderMgr.get(shard, start, readerPosition{
   119  		volume: 0, dataIdx: 0, metadataIdx: 0,
   120  	})
   121  	require.NoError(t, err)
   122  
   123  	// Case 2.
   124  	_, err = nsReaderMgr.get(shard, start, readerPosition{
   125  		volume: 0, dataIdx: 2, metadataIdx: 3,
   126  	})
   127  	require.NoError(t, err)
   128  
   129  	// Case 3.
   130  	_, err = nsReaderMgr.get(shard, start, readerPosition{
   131  		volume: 1, dataIdx: 1, metadataIdx: 1,
   132  	})
   133  	require.NoError(t, err)
   134  
   135  	// Case 4.
   136  	case4CachedReader := fs.NewMockDataFileSetReader(ctrl)
   137  	pos := readerPosition{
   138  		volume: 2, dataIdx: 4, metadataIdx: 5,
   139  	}
   140  	nsReaderMgrImpl.openReaders[cachedOpenReaderKey{
   141  		shard:      shard,
   142  		blockStart: start,
   143  		position:   pos,
   144  	}] = cachedReader{reader: case4CachedReader}
   145  	case4Reader, err := nsReaderMgr.get(shard, start, pos)
   146  	require.NoError(t, err)
   147  	require.Equal(t, case4CachedReader, case4Reader)
   148  
   149  	// Case 5.
   150  	case5CachedReader := fs.NewMockDataFileSetReader(ctrl)
   151  	case5CachedReader.EXPECT().Open(fs.DataReaderOpenOptions{
   152  		Identifier: fs.FileSetFileIdentifier{
   153  			Namespace: nsID, Shard: shard, BlockStart: start,
   154  			VolumeIndex: 1,
   155  		},
   156  	}).Return(nil)
   157  	case5CachedReader.EXPECT().ValidateMetadata().Return(nil)
   158  	nsReaderMgrImpl.closedReaders = []cachedReader{
   159  		{reader: case5CachedReader},
   160  	}
   161  	case5Reader, err := nsReaderMgr.get(shard, start, readerPosition{
   162  		volume: 1, dataIdx: 0, metadataIdx: 0,
   163  	})
   164  	require.NoError(t, err)
   165  	require.Equal(t, case5CachedReader, case5Reader)
   166  	require.Len(t, nsReaderMgrImpl.closedReaders, 0)
   167  }
   168  
   169  func TestNamespaceReadersPutTickClose(t *testing.T) {
   170  	ctrl := xtest.NewController(t)
   171  	defer ctrl.Finish()
   172  
   173  	metadata, err := namespace.NewMetadata(defaultTestNs1ID,
   174  		defaultTestNs1Opts.SetColdWritesEnabled(true))
   175  	require.NoError(t, err)
   176  	shard := uint32(0)
   177  	blockSize := metadata.Options().RetentionOptions().BlockSize()
   178  	start := xtime.Now().Truncate(blockSize)
   179  
   180  	mockFSReader := fs.NewMockDataFileSetReader(ctrl)
   181  	mockBlockLeaseMgr := block.NewMockLeaseManager(ctrl)
   182  	mockBlockLeaseMgr.EXPECT().RegisterLeaser(gomock.Any()).Return(nil)
   183  	mockBlockLeaseMgr.EXPECT().UnregisterLeaser(gomock.Any()).Return(nil)
   184  	// Case 2.
   185  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
   186  		Return(block.LeaseState{Volume: 1}, nil)
   187  	// Case 3.
   188  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
   189  		Return(block.LeaseState{Volume: 2}, nil)
   190  	// Case 4.
   191  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
   192  		Return(block.LeaseState{Volume: 3}, nil)
   193  
   194  	nsReaderMgr := newNamespaceReaderManager(metadata, tally.NoopScope,
   195  		DefaultTestOptions().SetBlockLeaseManager(mockBlockLeaseMgr))
   196  	nsReaderMgrImpl := nsReaderMgr.(*namespaceReaderManager)
   197  	// Grabbing specific ID here so that the test can match on gomock arguments.
   198  	nsID := nsReaderMgrImpl.namespace.ID()
   199  
   200  	// Case 1: putting a closed reader should add it to the slice of closed
   201  	// readers.
   202  	mockFSReader.EXPECT().Status().Return(fs.DataFileSetReaderStatus{
   203  		Namespace: nsID, BlockStart: start, Shard: shard,
   204  		Volume: 0,
   205  		Open:   false,
   206  	})
   207  	require.Len(t, nsReaderMgrImpl.closedReaders, 0)
   208  	require.NoError(t, nsReaderMgr.put(mockFSReader))
   209  	require.Len(t, nsReaderMgrImpl.closedReaders, 1)
   210  
   211  	// Case 2: a reader with a volume lower than the latest volume should be
   212  	// closed and added in the slice of closed readers.
   213  	mockFSReader.EXPECT().Status().Return(fs.DataFileSetReaderStatus{
   214  		Namespace: nsID, BlockStart: start, Shard: shard,
   215  		Volume: 0,
   216  		Open:   true,
   217  	})
   218  	mockFSReader.EXPECT().Close().Return(nil)
   219  	require.Len(t, nsReaderMgrImpl.closedReaders, 1)
   220  	require.NoError(t, nsReaderMgr.put(mockFSReader))
   221  	require.Len(t, nsReaderMgrImpl.closedReaders, 2)
   222  
   223  	// Case 3: an open reader with the correct volume gets added to the open
   224  	// readers.
   225  	mockFSReader.EXPECT().Status().Return(fs.DataFileSetReaderStatus{
   226  		Namespace: nsID, BlockStart: start, Shard: shard,
   227  		Volume: 2,
   228  		Open:   true,
   229  	})
   230  	mockFSReader.EXPECT().EntriesRead().Return(5)
   231  	mockFSReader.EXPECT().MetadataRead().Return(6)
   232  	require.Len(t, nsReaderMgrImpl.openReaders, 0)
   233  	require.NoError(t, nsReaderMgr.put(mockFSReader))
   234  	require.Len(t, nsReaderMgrImpl.openReaders, 1)
   235  
   236  	// Case 4: if trying to put a reader that happens to already have an open
   237  	// cached version for its exact key, close the reader and add to the slice
   238  	// of closed readers.
   239  	mockFSReader.EXPECT().Status().Return(fs.DataFileSetReaderStatus{
   240  		Namespace: nsID, BlockStart: start, Shard: shard,
   241  		Volume: 3,
   242  		Open:   true,
   243  	})
   244  	mockFSReader.EXPECT().EntriesRead().Return(7)
   245  	mockFSReader.EXPECT().MetadataRead().Return(8)
   246  	mockFSReader.EXPECT().Close()
   247  	mockExistingFSReader := fs.NewMockDataFileSetReader(ctrl)
   248  	nsReaderMgrImpl.openReaders[cachedOpenReaderKey{
   249  		shard:      shard,
   250  		blockStart: start,
   251  		position: readerPosition{
   252  			volume:      3,
   253  			dataIdx:     7,
   254  			metadataIdx: 8,
   255  		},
   256  	}] = cachedReader{reader: mockExistingFSReader}
   257  	require.Len(t, nsReaderMgrImpl.closedReaders, 2)
   258  	require.NoError(t, nsReaderMgr.put(mockFSReader))
   259  	require.Len(t, nsReaderMgrImpl.closedReaders, 3)
   260  
   261  	// Closing the reader manager should close any open readers and remove all
   262  	// readers.
   263  	require.Len(t, nsReaderMgrImpl.openReaders, 2)
   264  	require.Len(t, nsReaderMgrImpl.closedReaders, 3)
   265  	mockFSReader.EXPECT().Close()
   266  	mockExistingFSReader.EXPECT().Close()
   267  	nsReaderMgr.close()
   268  	require.Len(t, nsReaderMgrImpl.openReaders, 0)
   269  	require.Len(t, nsReaderMgrImpl.closedReaders, 0)
   270  }
   271  
   272  func TestNamespaceReadersUpdateOpenLease(t *testing.T) {
   273  	ctrl := xtest.NewController(t)
   274  	defer ctrl.Finish()
   275  
   276  	metadata, err := namespace.NewMetadata(defaultTestNs1ID,
   277  		defaultTestNs1Opts.SetColdWritesEnabled(true))
   278  	require.NoError(t, err)
   279  	shard := uint32(0)
   280  	blockSize := metadata.Options().RetentionOptions().BlockSize()
   281  	start := xtime.Now().Truncate(blockSize)
   282  
   283  	mockFSReader := fs.NewMockDataFileSetReader(ctrl)
   284  	mockFSReader.EXPECT().Close().Return(nil).Times(2)
   285  	mockBlockLeaseMgr := block.NewMockLeaseManager(ctrl)
   286  	mockBlockLeaseMgr.EXPECT().RegisterLeaser(gomock.Any()).Return(nil)
   287  	nsReaderMgr := newNamespaceReaderManager(metadata, tally.NoopScope,
   288  		DefaultTestOptions().SetBlockLeaseManager(mockBlockLeaseMgr))
   289  	nsReaderMgrImpl := nsReaderMgr.(*namespaceReaderManager)
   290  	// Grabbing specific ID here so that the test can match on gomock arguments.
   291  	nsID := nsReaderMgrImpl.namespace.ID()
   292  
   293  	nsReaderMgrImpl.openReaders[cachedOpenReaderKey{
   294  		shard:      shard,
   295  		blockStart: start,
   296  		position: readerPosition{
   297  			volume:      0,
   298  			dataIdx:     1,
   299  			metadataIdx: 2,
   300  		},
   301  	}] = cachedReader{reader: mockFSReader}
   302  	nsReaderMgrImpl.openReaders[cachedOpenReaderKey{
   303  		shard:      shard,
   304  		blockStart: start,
   305  		position: readerPosition{
   306  			volume:      1,
   307  			dataIdx:     2,
   308  			metadataIdx: 3,
   309  		},
   310  	}] = cachedReader{reader: mockFSReader}
   311  	remainingKey := cachedOpenReaderKey{
   312  		shard:      shard,
   313  		blockStart: start,
   314  		position: readerPosition{
   315  			volume:      2,
   316  			dataIdx:     4,
   317  			metadataIdx: 5,
   318  		},
   319  	}
   320  	nsReaderMgrImpl.openReaders[remainingKey] = cachedReader{reader: mockFSReader}
   321  
   322  	require.Len(t, nsReaderMgrImpl.openReaders, 3)
   323  	require.Len(t, nsReaderMgrImpl.closedReaders, 0)
   324  	// Of the three existing open readers, only two of them have volumes lower
   325  	// than the update lease volume, so those are expected to be closed, and
   326  	// put in the slice of closed readers.
   327  	res, err := nsReaderMgrImpl.UpdateOpenLease(block.LeaseDescriptor{
   328  		Namespace: nsID, Shard: shard, BlockStart: start,
   329  	}, block.LeaseState{Volume: 2})
   330  	require.NoError(t, err)
   331  	require.Equal(t, block.UpdateOpenLease, res)
   332  	require.Len(t, nsReaderMgrImpl.openReaders, 1)
   333  	require.Len(t, nsReaderMgrImpl.closedReaders, 2)
   334  	_, exists := nsReaderMgrImpl.openReaders[remainingKey]
   335  	require.True(t, exists)
   336  
   337  	// Test attempting update lease on wrong namespace.
   338  	res, err = nsReaderMgrImpl.UpdateOpenLease(block.LeaseDescriptor{
   339  		Namespace: ident.StringID("wrong-ns"), Shard: shard, BlockStart: start,
   340  	}, block.LeaseState{Volume: 2})
   341  	require.NoError(t, err)
   342  	require.Equal(t, block.NoOpenLease, res)
   343  }
   344  
   345  func TestNamespaceReadersFilesetExistsAt(t *testing.T) {
   346  	ctrl := xtest.NewController(t)
   347  	defer ctrl.Finish()
   348  
   349  	metadata, err := namespace.NewMetadata(defaultTestNs1ID,
   350  		defaultTestNs1Opts.SetColdWritesEnabled(true))
   351  	require.NoError(t, err)
   352  
   353  	mockBlockLeaseMgr := block.NewMockLeaseManager(ctrl)
   354  	mockBlockLeaseMgr.EXPECT().RegisterLeaser(gomock.Any()).Return(nil)
   355  	// First open lease is good.
   356  	open1 := mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
   357  		Return(block.LeaseState{}, nil)
   358  	// Second open lease returns error.
   359  	mockBlockLeaseMgr.EXPECT().OpenLatestLease(gomock.Any(), gomock.Any()).
   360  		Return(block.LeaseState{}, errors.New("bad open")).After(open1)
   361  	nsReaderMgr := newNamespaceReaderManager(metadata, tally.NoopScope,
   362  		DefaultTestOptions().SetBlockLeaseManager(mockBlockLeaseMgr))
   363  
   364  	exists, err := nsReaderMgr.filesetExistsAt(0, 0)
   365  	require.NoError(t, err)
   366  	require.False(t, exists)
   367  
   368  	_, err = nsReaderMgr.filesetExistsAt(0, 0)
   369  	require.Error(t, err)
   370  }