github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/persist_manager_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 fs
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"io/ioutil"
    27  	"os"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/dbnode/digest"
    32  	"github.com/m3db/m3/src/dbnode/persist"
    33  	"github.com/m3db/m3/src/dbnode/ts"
    34  	"github.com/m3db/m3/src/m3ninx/index/segment"
    35  	m3ninxfs "github.com/m3db/m3/src/m3ninx/index/segment/fst"
    36  	m3ninxpersist "github.com/m3db/m3/src/m3ninx/persist"
    37  	"github.com/m3db/m3/src/x/checked"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	"github.com/m3db/m3/src/x/instrument"
    40  	m3test "github.com/m3db/m3/src/x/test"
    41  	xtest "github.com/m3db/m3/src/x/test"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  
    44  	"github.com/golang/mock/gomock"
    45  	"github.com/stretchr/testify/assert"
    46  	"github.com/stretchr/testify/require"
    47  )
    48  
    49  func TestPersistenceManagerPrepareDataFileExistsNoDelete(t *testing.T) {
    50  	ctrl := gomock.NewController(t)
    51  	defer ctrl.Finish()
    52  
    53  	pm, _, _, _ := testDataPersistManager(t, ctrl)
    54  	defer os.RemoveAll(pm.filePathPrefix)
    55  
    56  	var (
    57  		shard              = uint32(0)
    58  		blockStart         = xtime.FromSeconds(1000)
    59  		shardDir           = createDataShardDir(t, pm.filePathPrefix, testNs1ID, shard)
    60  		checkpointFilePath = filesetPathFromTimeLegacy(shardDir, blockStart, CheckpointFileSuffix)
    61  		checkpointFileBuf  = make([]byte, CheckpointFileSizeBytes)
    62  	)
    63  	createFile(t, checkpointFilePath, checkpointFileBuf)
    64  
    65  	flush, err := pm.StartFlushPersist()
    66  	require.NoError(t, err)
    67  
    68  	defer func() {
    69  		assert.NoError(t, flush.DoneFlush())
    70  	}()
    71  
    72  	prepareOpts := persist.DataPrepareOptions{
    73  		NamespaceMetadata: testNs1Metadata(t),
    74  		Shard:             shard,
    75  		BlockStart:        blockStart,
    76  	}
    77  
    78  	defer instrument.SetShouldPanicEnvironmentVariable(true)()
    79  	require.Panics(t, func() { _, _ = flush.PrepareData(prepareOpts) })
    80  }
    81  
    82  func TestPersistenceManagerPrepareDataFileExistsWithDelete(t *testing.T) {
    83  	ctrl := gomock.NewController(t)
    84  	defer ctrl.Finish()
    85  
    86  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
    87  	defer os.RemoveAll(pm.filePathPrefix)
    88  
    89  	var (
    90  		shard      = uint32(0)
    91  		blockStart = xtime.FromSeconds(1000)
    92  	)
    93  
    94  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
    95  		Identifier: FileSetFileIdentifier{
    96  			Namespace:  testNs1ID,
    97  			Shard:      shard,
    98  			BlockStart: blockStart,
    99  		},
   100  		BlockSize: testBlockSize,
   101  	}, m3test.IdentTransformer)
   102  	writer.EXPECT().Open(writerOpts).Return(nil)
   103  
   104  	var (
   105  		shardDir           = createDataShardDir(t, pm.filePathPrefix, testNs1ID, shard)
   106  		checkpointFilePath = filesetPathFromTimeLegacy(shardDir, blockStart, CheckpointFileSuffix)
   107  		checkpointFileBuf  = make([]byte, CheckpointFileSizeBytes)
   108  	)
   109  	createFile(t, checkpointFilePath, checkpointFileBuf)
   110  
   111  	flush, err := pm.StartFlushPersist()
   112  	require.NoError(t, err)
   113  
   114  	defer func() {
   115  		assert.NoError(t, flush.DoneFlush())
   116  	}()
   117  
   118  	prepareOpts := persist.DataPrepareOptions{
   119  		NamespaceMetadata: testNs1Metadata(t),
   120  		Shard:             shard,
   121  		BlockStart:        blockStart,
   122  		DeleteIfExists:    true,
   123  	}
   124  	prepared, err := flush.PrepareData(prepareOpts)
   125  	require.NoError(t, err)
   126  	require.NotNil(t, prepared.Persist)
   127  	require.NotNil(t, prepared.Close)
   128  
   129  	_, err = os.Open(checkpointFilePath)
   130  	require.True(t, os.IsNotExist(err))
   131  }
   132  
   133  func TestPersistenceManagerPrepareOpenError(t *testing.T) {
   134  	ctrl := gomock.NewController(t)
   135  	defer ctrl.Finish()
   136  
   137  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
   138  	defer os.RemoveAll(pm.filePathPrefix)
   139  
   140  	ns1Md := testNs1Metadata(t)
   141  	shard := uint32(0)
   142  	blockStart := xtime.FromSeconds(1000)
   143  	expectedErr := errors.New("foo")
   144  
   145  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   146  		Identifier: FileSetFileIdentifier{
   147  			Namespace:  testNs1ID,
   148  			Shard:      shard,
   149  			BlockStart: blockStart,
   150  		},
   151  		BlockSize: testBlockSize,
   152  	}, m3test.IdentTransformer)
   153  	writer.EXPECT().Open(writerOpts).Return(expectedErr)
   154  
   155  	flush, err := pm.StartFlushPersist()
   156  	require.NoError(t, err)
   157  
   158  	defer func() {
   159  		assert.NoError(t, flush.DoneFlush())
   160  	}()
   161  
   162  	prepareOpts := persist.DataPrepareOptions{
   163  		NamespaceMetadata: ns1Md,
   164  		Shard:             shard,
   165  		BlockStart:        blockStart,
   166  	}
   167  	prepared, err := flush.PrepareData(prepareOpts)
   168  	require.Equal(t, expectedErr, err)
   169  	require.Nil(t, prepared.Persist)
   170  	require.Nil(t, prepared.Close)
   171  }
   172  
   173  func TestPersistenceManagerPrepareSuccess(t *testing.T) {
   174  	ctrl := gomock.NewController(t)
   175  	defer ctrl.Finish()
   176  
   177  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
   178  	defer os.RemoveAll(pm.filePathPrefix)
   179  
   180  	shard := uint32(0)
   181  	blockStart := xtime.FromSeconds(1000)
   182  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   183  		Identifier: FileSetFileIdentifier{
   184  			Namespace:  testNs1ID,
   185  			Shard:      shard,
   186  			BlockStart: blockStart,
   187  		},
   188  		BlockSize: testBlockSize,
   189  	}, m3test.IdentTransformer)
   190  	writer.EXPECT().Open(writerOpts).Return(nil)
   191  
   192  	var (
   193  		id       = ident.StringID("foo")
   194  		tags     = ident.NewTags(ident.StringTag("bar", "baz"))
   195  		head     = checked.NewBytes([]byte{0x1, 0x2}, nil)
   196  		tail     = checked.NewBytes([]byte{0x3, 0x4}, nil)
   197  		segment  = ts.NewSegment(head, tail, 0, ts.FinalizeNone)
   198  		checksum = segment.CalculateChecksum()
   199  	)
   200  	metadata := persist.NewMetadataFromIDAndTags(id, tags,
   201  		persist.MetadataOptions{})
   202  	writer.EXPECT().WriteAll(metadata, gomock.Any(), checksum).Return(nil)
   203  	writer.EXPECT().Close()
   204  
   205  	flush, err := pm.StartFlushPersist()
   206  	require.NoError(t, err)
   207  
   208  	defer func() {
   209  		assert.NoError(t, flush.DoneFlush())
   210  	}()
   211  
   212  	now := time.Now()
   213  	pm.start = now
   214  	pm.count = 123
   215  	pm.bytesWritten = 100
   216  
   217  	prepareOpts := persist.DataPrepareOptions{
   218  		NamespaceMetadata: testNs1Metadata(t),
   219  		Shard:             shard,
   220  		BlockStart:        blockStart,
   221  	}
   222  	prepared, err := flush.PrepareData(prepareOpts)
   223  	defer prepared.Close()
   224  
   225  	require.Nil(t, err)
   226  
   227  	require.Nil(t, prepared.Persist(metadata, segment, checksum))
   228  
   229  	require.True(t, pm.start.Equal(now))
   230  	require.Equal(t, 124, pm.count)
   231  	require.Equal(t, int64(104), pm.bytesWritten)
   232  }
   233  
   234  func TestPersistenceManagerPrepareSnapshotSuccess(t *testing.T) {
   235  	ctrl := gomock.NewController(t)
   236  	defer ctrl.Finish()
   237  
   238  	pm, writer, snapshotMetadataWriter, _ := testDataPersistManager(t, ctrl)
   239  	defer os.RemoveAll(pm.filePathPrefix)
   240  
   241  	shard := uint32(0)
   242  	blockStart := xtime.FromSeconds(1000)
   243  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   244  		Identifier: FileSetFileIdentifier{
   245  			Namespace:  testNs1ID,
   246  			Shard:      shard,
   247  			BlockStart: blockStart,
   248  		},
   249  		BlockSize: testBlockSize,
   250  		Snapshot: DataWriterSnapshotOptions{
   251  			SnapshotID: testSnapshotID,
   252  		},
   253  	}, m3test.IdentTransformer)
   254  	writer.EXPECT().Open(writerOpts).Return(nil)
   255  
   256  	snapshotMetadataWriter.EXPECT().Write(SnapshotMetadataWriteArgs{
   257  		ID: SnapshotMetadataIdentifier{
   258  			Index: 0,
   259  			UUID:  nil,
   260  		},
   261  		CommitlogIdentifier: persist.CommitLogFile{},
   262  	}).Return(nil)
   263  
   264  	var (
   265  		id       = ident.StringID("foo")
   266  		tags     = ident.NewTags(ident.StringTag("bar", "baz"))
   267  		head     = checked.NewBytes([]byte{0x1, 0x2}, nil)
   268  		tail     = checked.NewBytes([]byte{0x3, 0x4}, nil)
   269  		segment  = ts.NewSegment(head, tail, 0, ts.FinalizeNone)
   270  		checksum = segment.CalculateChecksum()
   271  	)
   272  	metadata := persist.NewMetadataFromIDAndTags(id, tags,
   273  		persist.MetadataOptions{})
   274  	writer.EXPECT().WriteAll(metadata, gomock.Any(), checksum).Return(nil)
   275  	writer.EXPECT().Close()
   276  
   277  	flush, err := pm.StartSnapshotPersist(testSnapshotID)
   278  	require.NoError(t, err)
   279  
   280  	defer func() {
   281  		assert.NoError(t, flush.DoneSnapshot(nil, persist.CommitLogFile{}))
   282  	}()
   283  
   284  	now := time.Now()
   285  	pm.start = now
   286  	pm.count = 123
   287  	pm.bytesWritten = 100
   288  
   289  	prepareOpts := persist.DataPrepareOptions{
   290  		NamespaceMetadata: testNs1Metadata(t),
   291  		Shard:             shard,
   292  		BlockStart:        blockStart,
   293  	}
   294  	prepared, err := flush.PrepareData(prepareOpts)
   295  	defer prepared.Close()
   296  
   297  	require.Nil(t, err)
   298  
   299  	require.Nil(t, prepared.Persist(metadata, segment, checksum))
   300  
   301  	require.True(t, pm.start.Equal(now))
   302  	require.Equal(t, 124, pm.count)
   303  	require.Equal(t, int64(104), pm.bytesWritten)
   304  }
   305  
   306  func TestPersistenceManagerCloseData(t *testing.T) {
   307  	ctrl := gomock.NewController(t)
   308  	defer ctrl.Finish()
   309  
   310  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
   311  	defer os.RemoveAll(pm.filePathPrefix)
   312  
   313  	writer.EXPECT().Close()
   314  	pm.closeData()
   315  }
   316  
   317  func TestPersistenceManagerPrepareIndexFileExists(t *testing.T) {
   318  	ctrl := gomock.NewController(xtest.Reporter{T: t})
   319  	defer ctrl.Finish()
   320  
   321  	pm, writer, segWriter, _ := testIndexPersistManager(t, ctrl)
   322  	defer os.RemoveAll(pm.filePathPrefix)
   323  
   324  	blockStart := xtime.FromSeconds(1000)
   325  	indexDir := createIndexDataDir(t, pm.filePathPrefix, testNs1ID)
   326  	checkpointFilePath := FilesetPathFromTimeAndIndex(indexDir, blockStart, 0, CheckpointFileSuffix)
   327  
   328  	digestBuf := digest.NewBuffer()
   329  	digestBuf.WriteDigest(digest.Checksum([]byte("foo")))
   330  
   331  	err := ioutil.WriteFile(checkpointFilePath, digestBuf, defaultNewFileMode)
   332  	require.NoError(t, err)
   333  
   334  	flush, err := pm.StartIndexPersist()
   335  	require.NoError(t, err)
   336  
   337  	defer func() {
   338  		segWriter.EXPECT().Reset(nil)
   339  		assert.NoError(t, flush.DoneIndex())
   340  	}()
   341  	volumeIndex, err := NextIndexFileSetVolumeIndex(
   342  		pm.filePathPrefix,
   343  		testNs1ID,
   344  		blockStart,
   345  	)
   346  	require.NoError(t, err)
   347  
   348  	prepareOpts := persist.IndexPrepareOptions{
   349  		NamespaceMetadata: testNs1Metadata(t),
   350  		BlockStart:        blockStart,
   351  		VolumeIndex:       volumeIndex,
   352  	}
   353  	writer.EXPECT().Open(xtest.CmpMatcher(
   354  		IndexWriterOpenOptions{
   355  			BlockSize: testBlockSize,
   356  			Identifier: FileSetFileIdentifier{
   357  				FileSetContentType: persist.FileSetIndexContentType,
   358  				BlockStart:         blockStart,
   359  				Namespace:          testNs1ID,
   360  				VolumeIndex:        1,
   361  			},
   362  		}, m3test.IdentTransformer),
   363  	).Return(nil)
   364  	prepared, err := flush.PrepareIndex(prepareOpts)
   365  	require.NoError(t, err)
   366  	require.NotNil(t, prepared.Persist)
   367  	require.NotNil(t, prepared.Close)
   368  }
   369  
   370  func TestPersistenceManagerPrepareIndexOpenError(t *testing.T) {
   371  	ctrl := gomock.NewController(t)
   372  	defer ctrl.Finish()
   373  
   374  	pm, writer, segWriter, _ := testIndexPersistManager(t, ctrl)
   375  	defer os.RemoveAll(pm.filePathPrefix)
   376  
   377  	ns1Md := testNs1Metadata(t)
   378  	blockStart := xtime.FromSeconds(1000)
   379  	expectedErr := errors.New("foo")
   380  
   381  	writerOpts := xtest.CmpMatcher(IndexWriterOpenOptions{
   382  		Identifier: FileSetFileIdentifier{
   383  			FileSetContentType: persist.FileSetIndexContentType,
   384  			Namespace:          testNs1ID,
   385  			BlockStart:         blockStart,
   386  		},
   387  		BlockSize: testBlockSize,
   388  	}, m3test.IdentTransformer)
   389  	writer.EXPECT().Open(writerOpts).Return(expectedErr)
   390  
   391  	flush, err := pm.StartIndexPersist()
   392  	require.NoError(t, err)
   393  
   394  	defer func() {
   395  		segWriter.EXPECT().Reset(nil)
   396  		assert.NoError(t, flush.DoneIndex())
   397  	}()
   398  
   399  	prepareOpts := persist.IndexPrepareOptions{
   400  		NamespaceMetadata: ns1Md,
   401  		BlockStart:        blockStart,
   402  	}
   403  	prepared, err := flush.PrepareIndex(prepareOpts)
   404  	require.Equal(t, expectedErr, err)
   405  	require.Nil(t, prepared.Persist)
   406  	require.Nil(t, prepared.Close)
   407  }
   408  
   409  func TestPersistenceManagerPrepareIndexSuccess(t *testing.T) {
   410  	ctrl := gomock.NewController(xtest.Reporter{T: t})
   411  	defer ctrl.Finish()
   412  
   413  	pm, writer, segWriter, _ := testIndexPersistManager(t, ctrl)
   414  	defer os.RemoveAll(pm.filePathPrefix)
   415  
   416  	flush, err := pm.StartIndexPersist()
   417  	require.NoError(t, err)
   418  
   419  	defer func() {
   420  		segWriter.EXPECT().Reset(nil)
   421  		assert.NoError(t, flush.DoneIndex())
   422  	}()
   423  
   424  	// We support preparing multiple index block writers for an index persist.
   425  	numBlocks := 10
   426  	blockStart := xtime.FromSeconds(1000)
   427  	for i := 1; i < numBlocks; i++ {
   428  		blockStart = blockStart.Add(time.Duration(i) * testBlockSize)
   429  		writerOpts := IndexWriterOpenOptions{
   430  			Identifier: FileSetFileIdentifier{
   431  				FileSetContentType: persist.FileSetIndexContentType,
   432  				Namespace:          testNs1ID,
   433  				BlockStart:         blockStart,
   434  			},
   435  			BlockSize: testBlockSize,
   436  		}
   437  		writer.EXPECT().Open(xtest.CmpMatcher(writerOpts, m3test.IdentTransformer)).Return(nil)
   438  
   439  		prepareOpts := persist.IndexPrepareOptions{
   440  			NamespaceMetadata: testNs1Metadata(t),
   441  			BlockStart:        blockStart,
   442  		}
   443  		prepared, err := flush.PrepareIndex(prepareOpts)
   444  		require.NoError(t, err)
   445  
   446  		seg := segment.NewMockMutableSegment(ctrl)
   447  		segWriter.EXPECT().Reset(seg).Return(nil)
   448  		writer.EXPECT().WriteSegmentFileSet(segWriter).Return(nil)
   449  		require.NoError(t, prepared.Persist(seg))
   450  
   451  		reader := NewMockIndexFileSetReader(ctrl)
   452  		pm.indexPM.newReaderFn = func(Options) (IndexFileSetReader, error) {
   453  			return reader, nil
   454  		}
   455  
   456  		reader.EXPECT().Open(xtest.CmpMatcher(IndexReaderOpenOptions{
   457  			Identifier: writerOpts.Identifier,
   458  		}, m3test.IdentTransformer)).Return(IndexReaderOpenResult{}, nil)
   459  
   460  		file := NewMockIndexSegmentFile(ctrl)
   461  		gomock.InOrder(
   462  			reader.EXPECT().SegmentFileSets().Return(1),
   463  			reader.EXPECT().ReadSegmentFileSet().Return(file, nil),
   464  			reader.EXPECT().ReadSegmentFileSet().Return(nil, io.EOF),
   465  		)
   466  		fsSeg := m3ninxfs.NewMockSegment(ctrl)
   467  		pm.indexPM.newPersistentSegmentFn = func(
   468  			fset m3ninxpersist.IndexSegmentFileSet, opts m3ninxfs.Options,
   469  		) (m3ninxfs.Segment, error) {
   470  			require.Equal(t, file, fset)
   471  			return fsSeg, nil
   472  		}
   473  
   474  		writer.EXPECT().Close().Return(nil)
   475  		segs, err := prepared.Close()
   476  		require.NoError(t, err)
   477  		require.Len(t, segs, 1)
   478  		require.Equal(t, fsSeg, segs[0])
   479  	}
   480  }
   481  
   482  func TestPersistenceManagerNoRateLimit(t *testing.T) {
   483  	ctrl := gomock.NewController(t)
   484  	defer ctrl.Finish()
   485  
   486  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
   487  	defer os.RemoveAll(pm.filePathPrefix)
   488  
   489  	shard := uint32(0)
   490  	blockStart := xtime.FromSeconds(1000)
   491  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   492  		Identifier: FileSetFileIdentifier{
   493  			Namespace:  testNs1ID,
   494  			Shard:      shard,
   495  			BlockStart: blockStart,
   496  		},
   497  		BlockSize: testBlockSize,
   498  	}, m3test.IdentTransformer)
   499  	writer.EXPECT().Open(writerOpts).Return(nil)
   500  
   501  	var (
   502  		now      time.Time
   503  		slept    time.Duration
   504  		id       = ident.StringID("foo")
   505  		tags     = ident.NewTags(ident.StringTag("bar", "baz"))
   506  		head     = checked.NewBytes([]byte{0x1, 0x2}, nil)
   507  		tail     = checked.NewBytes([]byte{0x3}, nil)
   508  		segment  = ts.NewSegment(head, tail, 0, ts.FinalizeNone)
   509  		checksum = segment.CalculateChecksum()
   510  	)
   511  
   512  	pm.nowFn = func() time.Time { return now }
   513  	pm.sleepFn = func(d time.Duration) { slept += d }
   514  
   515  	metadata := persist.NewMetadataFromIDAndTags(id, tags,
   516  		persist.MetadataOptions{})
   517  	writer.EXPECT().
   518  		WriteAll(metadata, pm.dataPM.segmentHolder, checksum).
   519  		Return(nil).
   520  		Times(2)
   521  
   522  	flush, err := pm.StartFlushPersist()
   523  	require.NoError(t, err)
   524  
   525  	defer func() {
   526  		assert.NoError(t, flush.DoneFlush())
   527  	}()
   528  
   529  	// prepare the flush
   530  	prepareOpts := persist.DataPrepareOptions{
   531  		NamespaceMetadata: testNs1Metadata(t),
   532  		Shard:             shard,
   533  		BlockStart:        blockStart,
   534  	}
   535  	prepared, err := flush.PrepareData(prepareOpts)
   536  	require.NoError(t, err)
   537  
   538  	// Start persistence
   539  	now = time.Now()
   540  	require.NoError(t, prepared.Persist(metadata, segment, checksum))
   541  
   542  	// Advance time and write again
   543  	now = now.Add(time.Millisecond)
   544  	require.NoError(t, prepared.Persist(metadata, segment, checksum))
   545  
   546  	// Check there is no rate limiting
   547  	require.Equal(t, time.Duration(0), slept)
   548  	require.Equal(t, int64(6), pm.bytesWritten)
   549  }
   550  
   551  func TestPersistenceManagerWithRateLimit(t *testing.T) {
   552  	ctrl := gomock.NewController(t)
   553  	defer ctrl.Finish()
   554  
   555  	pm, writer, _, opts := testDataPersistManager(t, ctrl)
   556  	defer os.RemoveAll(pm.filePathPrefix)
   557  
   558  	shard := uint32(0)
   559  	blockStart := xtime.FromSeconds(1000)
   560  
   561  	var (
   562  		now      time.Time
   563  		slept    time.Duration
   564  		iter     = 2
   565  		id       = ident.StringID("foo")
   566  		head     = checked.NewBytes([]byte{0x1, 0x2}, nil)
   567  		tail     = checked.NewBytes([]byte{0x3}, nil)
   568  		segment  = ts.NewSegment(head, tail, 0, ts.FinalizeNone)
   569  		checksum = segment.CalculateChecksum()
   570  	)
   571  
   572  	pm.nowFn = func() time.Time { return now }
   573  	pm.sleepFn = func(d time.Duration) { slept += d }
   574  
   575  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   576  		Identifier: FileSetFileIdentifier{
   577  			Namespace:  testNs1ID,
   578  			Shard:      shard,
   579  			BlockStart: blockStart,
   580  		},
   581  		BlockSize: testBlockSize,
   582  	}, m3test.IdentTransformer)
   583  	metadata := persist.NewMetadataFromIDAndTags(id, ident.Tags{},
   584  		persist.MetadataOptions{})
   585  	writer.EXPECT().Open(writerOpts).Return(nil).Times(iter)
   586  	writer.EXPECT().
   587  		WriteAll(metadata, pm.dataPM.segmentHolder, checksum).
   588  		Return(nil).
   589  		AnyTimes()
   590  	writer.EXPECT().Close().Times(iter)
   591  
   592  	// Enable rate limiting
   593  	runtimeOpts := opts.RuntimeOptionsManager().Get()
   594  	opts.RuntimeOptionsManager().Update(
   595  		runtimeOpts.SetPersistRateLimitOptions(
   596  			runtimeOpts.PersistRateLimitOptions().
   597  				SetLimitEnabled(true).
   598  				SetLimitCheckEvery(2).
   599  				SetLimitMbps(16.0)))
   600  
   601  	// Wait until enabled
   602  	for func() bool {
   603  		pm.Lock()
   604  		defer pm.Unlock()
   605  		return !pm.currRateLimitOpts.LimitEnabled()
   606  	}() {
   607  		time.Sleep(10 * time.Millisecond)
   608  	}
   609  
   610  	for i := 0; i < iter; i++ {
   611  		// Reset
   612  		slept = time.Duration(0)
   613  
   614  		flush, err := pm.StartFlushPersist()
   615  		require.NoError(t, err)
   616  
   617  		// prepare the flush
   618  		prepareOpts := persist.DataPrepareOptions{
   619  			NamespaceMetadata: testNs1Metadata(t),
   620  			Shard:             shard,
   621  			BlockStart:        blockStart,
   622  		}
   623  		prepared, err := flush.PrepareData(prepareOpts)
   624  		require.NoError(t, err)
   625  
   626  		// Start persistence
   627  		now = time.Now()
   628  		require.NoError(t, prepared.Persist(metadata, segment, checksum))
   629  
   630  		// Assert we don't rate limit if the count is not enough yet
   631  		require.NoError(t, prepared.Persist(metadata, segment, checksum))
   632  		require.Equal(t, time.Duration(0), slept)
   633  
   634  		// Advance time and check we rate limit if the disk throughput exceeds the limit
   635  		now = now.Add(time.Microsecond)
   636  		require.NoError(t, prepared.Persist(metadata, segment, checksum))
   637  		require.Equal(t, time.Duration(1861), slept)
   638  
   639  		// Advance time and check we don't rate limit if the disk throughput is below the limit
   640  		require.NoError(t, prepared.Persist(metadata, segment, checksum))
   641  		now = now.Add(time.Second - time.Microsecond)
   642  		require.NoError(t, prepared.Persist(metadata, segment, checksum))
   643  		require.Equal(t, time.Duration(1861), slept)
   644  
   645  		require.Equal(t, int64(15), pm.bytesWritten)
   646  
   647  		require.NoError(t, prepared.Close())
   648  
   649  		assert.NoError(t, flush.DoneFlush())
   650  	}
   651  }
   652  
   653  func TestPersistenceManagerNamespaceSwitch(t *testing.T) {
   654  	ctrl := gomock.NewController(t)
   655  	defer ctrl.Finish()
   656  
   657  	pm, writer, _, _ := testDataPersistManager(t, ctrl)
   658  	defer os.RemoveAll(pm.filePathPrefix)
   659  
   660  	shard := uint32(0)
   661  	blockStart := xtime.FromSeconds(1000)
   662  
   663  	flush, err := pm.StartFlushPersist()
   664  	require.NoError(t, err)
   665  
   666  	defer func() {
   667  		assert.NoError(t, flush.DoneFlush())
   668  	}()
   669  
   670  	writerOpts := xtest.CmpMatcher(DataWriterOpenOptions{
   671  		Identifier: FileSetFileIdentifier{
   672  			Namespace:  testNs1ID,
   673  			Shard:      shard,
   674  			BlockStart: blockStart,
   675  		},
   676  		BlockSize: testBlockSize,
   677  	}, m3test.IdentTransformer)
   678  	writer.EXPECT().Open(writerOpts).Return(nil)
   679  	prepareOpts := persist.DataPrepareOptions{
   680  		NamespaceMetadata: testNs1Metadata(t),
   681  		Shard:             shard,
   682  		BlockStart:        blockStart,
   683  	}
   684  	prepared, err := flush.PrepareData(prepareOpts)
   685  	require.NoError(t, err)
   686  	require.NotNil(t, prepared.Persist)
   687  	require.NotNil(t, prepared.Close)
   688  
   689  	writerOpts = xtest.CmpMatcher(DataWriterOpenOptions{
   690  		Identifier: FileSetFileIdentifier{
   691  			Namespace:  testNs2ID,
   692  			Shard:      shard,
   693  			BlockStart: blockStart,
   694  		},
   695  		BlockSize: testBlockSize,
   696  	}, m3test.IdentTransformer)
   697  	writer.EXPECT().Open(writerOpts).Return(nil)
   698  	prepareOpts = persist.DataPrepareOptions{
   699  		NamespaceMetadata: testNs2Metadata(t),
   700  		Shard:             shard,
   701  		BlockStart:        blockStart,
   702  	}
   703  	prepared, err = flush.PrepareData(prepareOpts)
   704  	require.NoError(t, err)
   705  	require.NotNil(t, prepared.Persist)
   706  	require.NotNil(t, prepared.Close)
   707  }
   708  
   709  func createDataShardDir(t *testing.T, prefix string, namespace ident.ID, shard uint32) string {
   710  	shardDirPath := ShardDataDirPath(prefix, namespace, shard)
   711  	err := os.MkdirAll(shardDirPath, os.ModeDir|os.FileMode(0755))
   712  	require.Nil(t, err)
   713  	return shardDirPath
   714  }
   715  
   716  func createIndexDataDir(t *testing.T, prefix string, namespace ident.ID) string {
   717  	path := NamespaceIndexDataDirPath(prefix, namespace)
   718  	err := os.MkdirAll(path, os.ModeDir|os.FileMode(0755))
   719  	require.Nil(t, err)
   720  	return path
   721  }
   722  
   723  func testDataPersistManager(
   724  	t *testing.T,
   725  	ctrl *gomock.Controller,
   726  ) (*persistManager, *MockDataFileSetWriter, *MockSnapshotMetadataFileWriter, Options) {
   727  	dir := createTempDir(t)
   728  
   729  	opts := testDefaultOpts.
   730  		SetFilePathPrefix(dir).
   731  		SetWriterBufferSize(10)
   732  
   733  	var (
   734  		fileSetWriter          = NewMockDataFileSetWriter(ctrl)
   735  		snapshotMetadataWriter = NewMockSnapshotMetadataFileWriter(ctrl)
   736  	)
   737  
   738  	mgr, err := NewPersistManager(opts)
   739  	require.NoError(t, err)
   740  
   741  	manager := mgr.(*persistManager)
   742  	manager.dataPM.writer = fileSetWriter
   743  	manager.dataPM.snapshotMetadataWriter = snapshotMetadataWriter
   744  	manager.dataPM.nextSnapshotMetadataFileIndex = func(Options) (int64, error) {
   745  		return 0, nil
   746  	}
   747  
   748  	return manager, fileSetWriter, snapshotMetadataWriter, opts
   749  }
   750  
   751  func testIndexPersistManager(t *testing.T, ctrl *gomock.Controller,
   752  ) (*persistManager, *MockIndexFileSetWriter, *m3ninxpersist.MockMutableSegmentFileSetWriter, Options) {
   753  	dir := createTempDir(t)
   754  
   755  	opts := testDefaultOpts.
   756  		SetFilePathPrefix(dir).
   757  		SetWriterBufferSize(10)
   758  
   759  	writer := NewMockIndexFileSetWriter(ctrl)
   760  	segmentWriter := m3ninxpersist.NewMockMutableSegmentFileSetWriter(ctrl)
   761  
   762  	mgr, err := NewPersistManager(opts)
   763  	require.NoError(t, err)
   764  
   765  	manager := mgr.(*persistManager)
   766  	manager.indexPM.newIndexWriterFn = func(opts Options) (IndexFileSetWriter, error) {
   767  		return writer, nil
   768  	}
   769  	manager.indexPM.segmentWriter = segmentWriter
   770  	return manager, writer, segmentWriter, opts
   771  }