github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/flush_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 storage
    22  
    23  import (
    24  	"errors"
    25  	"sort"
    26  	"strings"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	"github.com/m3db/m3/src/dbnode/persist"
    33  	"github.com/m3db/m3/src/dbnode/persist/fs/commitlog"
    34  	"github.com/m3db/m3/src/dbnode/retention"
    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/golang/mock/gomock"
    40  	"github.com/stretchr/testify/require"
    41  	"github.com/uber-go/tally"
    42  )
    43  
    44  var testCommitlogFile = persist.CommitLogFile{
    45  	FilePath: "/var/lib/m3db/commitlogs/commitlog-0-0.db",
    46  	Index:    0,
    47  }
    48  
    49  func newMultipleFlushManagerNeedsFlush(t *testing.T, ctrl *gomock.Controller) (
    50  	*flushManager,
    51  	*MockdatabaseNamespace,
    52  	*MockdatabaseNamespace,
    53  	*commitlog.MockCommitLog,
    54  ) {
    55  	options := namespace.NewOptions()
    56  	namespace := NewMockdatabaseNamespace(ctrl)
    57  	namespace.EXPECT().Options().Return(options).AnyTimes()
    58  	namespace.EXPECT().ID().Return(defaultTestNs1ID).AnyTimes()
    59  	otherNamespace := NewMockdatabaseNamespace(ctrl)
    60  	otherNamespace.EXPECT().Options().Return(options).AnyTimes()
    61  	otherNamespace.EXPECT().ID().Return(ident.StringID("someString")).AnyTimes()
    62  
    63  	db := newMockdatabase(ctrl, namespace, otherNamespace)
    64  
    65  	cl := commitlog.NewMockCommitLog(ctrl)
    66  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
    67  
    68  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
    69  
    70  	return fm, namespace, otherNamespace, cl
    71  }
    72  
    73  func TestFlushManagerFlushAlreadyInProgress(t *testing.T) {
    74  	ctrl := xtest.NewController(t)
    75  	defer ctrl.Finish()
    76  
    77  	var (
    78  		// Channels used to coordinate flushing / snapshotting
    79  		startCh = make(chan struct{}, 1)
    80  		doneCh  = make(chan struct{}, 1)
    81  	)
    82  	defer func() {
    83  		close(startCh)
    84  		close(doneCh)
    85  	}()
    86  
    87  	var (
    88  		mockPersistManager  = persist.NewMockManager(ctrl)
    89  		mockFlushPerist     = persist.NewMockFlushPreparer(ctrl)
    90  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
    91  	)
    92  
    93  	mockFlushPerist.EXPECT().DoneFlush().Return(nil).AnyTimes()
    94  	mockPersistManager.EXPECT().StartFlushPersist().Do(func() {
    95  		startCh <- struct{}{}
    96  		<-doneCh
    97  	}).Return(mockFlushPerist, nil).AnyTimes()
    98  
    99  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   100  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Do(func(_ interface{}) {
   101  		startCh <- struct{}{}
   102  		<-doneCh
   103  	}).Return(mockSnapshotPersist, nil).AnyTimes()
   104  
   105  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   106  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil).AnyTimes()
   107  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil).AnyTimes()
   108  
   109  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   110  	db := newMockdatabase(ctrl)
   111  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   112  	db.EXPECT().OwnedNamespaces().Return(nil, nil).AnyTimes()
   113  
   114  	cl := commitlog.NewMockCommitLog(ctrl)
   115  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   116  
   117  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   118  	fm.pm = mockPersistManager
   119  
   120  	now := xtime.UnixNano(0)
   121  	var wg sync.WaitGroup
   122  	wg.Add(2)
   123  
   124  	// Goroutine 1 should successfully flush.
   125  	go func() {
   126  		defer wg.Done()
   127  		require.NoError(t, fm.Flush(now))
   128  	}()
   129  
   130  	// Goroutine 2 should indicate already flushing.
   131  	go func() {
   132  		defer wg.Done()
   133  
   134  		// Wait until we start the flushing process.
   135  		<-startCh
   136  
   137  		// Ensure it doesn't allow a parallel flush.
   138  		require.Equal(t, errFlushOperationsInProgress, fm.Flush(now))
   139  
   140  		// Allow the flush to finish.
   141  		doneCh <- struct{}{}
   142  
   143  		// Allow the snapshot to begin and finish.
   144  		<-startCh
   145  
   146  		// Ensure it doesn't allow a parallel flush.
   147  		require.Equal(t, errFlushOperationsInProgress, fm.Flush(now))
   148  
   149  		doneCh <- struct{}{}
   150  	}()
   151  
   152  	wg.Wait()
   153  }
   154  
   155  // TestFlushManagerFlushDoneFlushError makes sure that flush errors do not
   156  // impact snapshotting or index operations.
   157  func TestFlushManagerFlushDoneFlushError(t *testing.T) {
   158  	ctrl := xtest.NewController(t)
   159  	defer ctrl.Finish()
   160  
   161  	var (
   162  		fakeErr             = errors.New("fake error while marking flush done")
   163  		mockPersistManager  = persist.NewMockManager(ctrl)
   164  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   165  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   166  	)
   167  
   168  	mockFlushPersist.EXPECT().DoneFlush().Return(fakeErr)
   169  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   170  
   171  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(nil)
   172  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   173  
   174  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   175  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil)
   176  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   177  
   178  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   179  	db := newMockdatabase(ctrl)
   180  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   181  	db.EXPECT().OwnedNamespaces().Return(nil, nil)
   182  
   183  	cl := commitlog.NewMockCommitLog(ctrl)
   184  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   185  
   186  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   187  	fm.pm = mockPersistManager
   188  
   189  	now := xtime.UnixNano(0)
   190  	require.EqualError(t, fakeErr, fm.Flush(now).Error())
   191  }
   192  
   193  // TestFlushManagerNamespaceFlushTimesErr makes sure that namespaceFlushTimes errors do
   194  // not leave the persist manager in an invalid state.
   195  func TestFlushManagerNamespaceFlushTimesErr(t *testing.T) {
   196  	ctrl := xtest.NewController(t)
   197  	defer ctrl.Finish()
   198  
   199  	var (
   200  		fakeErr             = errors.New("some-err")
   201  		mockPersistManager  = persist.NewMockManager(ctrl)
   202  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   203  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   204  	)
   205  
   206  	// Make sure DoneFlush is called despite encountering an error, once for snapshot and once for warm flush.
   207  	mockFlushPersist.EXPECT().DoneFlush().Return(nil)
   208  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   209  
   210  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(nil)
   211  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   212  
   213  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   214  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil)
   215  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   216  
   217  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   218  	db := newMockdatabase(ctrl)
   219  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   220  
   221  	nsOpts := defaultTestNs1Opts.SetIndexOptions(namespace.NewIndexOptions().SetEnabled(false))
   222  	ns := NewMockdatabaseNamespace(ctrl)
   223  	ns.EXPECT().Options().Return(nsOpts).AnyTimes()
   224  	ns.EXPECT().ID().Return(defaultTestNs1ID).AnyTimes()
   225  	ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, fakeErr).AnyTimes()
   226  	ns.EXPECT().Snapshot(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   227  	db.EXPECT().OwnedNamespaces().Return([]databaseNamespace{ns}, nil)
   228  
   229  	cl := commitlog.NewMockCommitLog(ctrl)
   230  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   231  
   232  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   233  	fm.pm = mockPersistManager
   234  
   235  	now := xtime.UnixNano(0)
   236  	require.True(t, strings.Contains(fm.Flush(now).Error(), fakeErr.Error()))
   237  }
   238  
   239  // TestFlushManagerFlushDoneSnapshotError makes sure that snapshot errors do not
   240  // impact flushing or index operations.
   241  func TestFlushManagerFlushDoneSnapshotError(t *testing.T) {
   242  	ctrl := xtest.NewController(t)
   243  	defer ctrl.Finish()
   244  
   245  	var (
   246  		fakeErr             = errors.New("fake error while marking snapshot done")
   247  		mockPersistManager  = persist.NewMockManager(ctrl)
   248  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   249  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   250  	)
   251  
   252  	mockFlushPersist.EXPECT().DoneFlush().Return(nil)
   253  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   254  
   255  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(fakeErr)
   256  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   257  
   258  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   259  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil)
   260  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   261  
   262  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   263  	db := newMockdatabase(ctrl)
   264  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   265  	db.EXPECT().OwnedNamespaces().Return(nil, nil)
   266  
   267  	cl := commitlog.NewMockCommitLog(ctrl)
   268  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   269  
   270  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   271  	fm.pm = mockPersistManager
   272  
   273  	now := xtime.UnixNano(0)
   274  	require.EqualError(t, fakeErr, fm.Flush(now).Error())
   275  }
   276  
   277  func TestFlushManagerFlushDoneIndexError(t *testing.T) {
   278  	ctrl := xtest.NewController(t)
   279  	defer ctrl.Finish()
   280  
   281  	var (
   282  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   283  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   284  		mockPersistManager  = persist.NewMockManager(ctrl)
   285  	)
   286  
   287  	mockFlushPersist.EXPECT().DoneFlush().Return(nil)
   288  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   289  
   290  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(nil)
   291  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   292  
   293  	fakeErr := errors.New("fake error while marking flush done")
   294  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   295  	mockIndexFlusher.EXPECT().DoneIndex().Return(fakeErr)
   296  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   297  
   298  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   299  	db := newMockdatabase(ctrl)
   300  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   301  	db.EXPECT().OwnedNamespaces().Return(nil, nil)
   302  
   303  	cl := commitlog.NewMockCommitLog(ctrl)
   304  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   305  
   306  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   307  	fm.pm = mockPersistManager
   308  
   309  	now := xtime.UnixNano(0)
   310  	require.EqualError(t, fakeErr, fm.Flush(now).Error())
   311  }
   312  
   313  func TestFlushManagerSkipNamespaceIndexingDisabled(t *testing.T) {
   314  	ctrl := xtest.NewController(t)
   315  	defer ctrl.Finish()
   316  
   317  	nsOpts := defaultTestNs1Opts.SetIndexOptions(namespace.NewIndexOptions().SetEnabled(false))
   318  	s1 := NewMockdatabaseShard(ctrl)
   319  	s2 := NewMockdatabaseShard(ctrl)
   320  	ns := NewMockdatabaseNamespace(ctrl)
   321  	ns.EXPECT().Options().Return(nsOpts).AnyTimes()
   322  	ns.EXPECT().ID().Return(defaultTestNs1ID).AnyTimes()
   323  	ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes()
   324  	ns.EXPECT().WarmFlush(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   325  	ns.EXPECT().Snapshot(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   326  	s1.EXPECT().ID().Return(uint32(1)).AnyTimes()
   327  	s2.EXPECT().ID().Return(uint32(2)).AnyTimes()
   328  
   329  	var (
   330  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   331  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   332  		mockPersistManager  = persist.NewMockManager(ctrl)
   333  	)
   334  
   335  	mockFlushPersist.EXPECT().DoneFlush().Return(nil)
   336  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   337  
   338  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(nil)
   339  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   340  
   341  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   342  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil)
   343  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   344  
   345  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   346  	db := newMockdatabase(ctrl)
   347  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   348  	db.EXPECT().OwnedNamespaces().Return([]databaseNamespace{ns}, nil)
   349  
   350  	cl := commitlog.NewMockCommitLog(ctrl)
   351  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   352  
   353  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   354  	fm.pm = mockPersistManager
   355  
   356  	now := xtime.UnixNano(0)
   357  	require.NoError(t, fm.Flush(now))
   358  }
   359  
   360  func TestFlushManagerNamespaceIndexingEnabled(t *testing.T) {
   361  	ctrl := xtest.NewController(t)
   362  	defer ctrl.Finish()
   363  
   364  	nsOpts := defaultTestNs1Opts.SetIndexOptions(namespace.NewIndexOptions().SetEnabled(true))
   365  	s1 := NewMockdatabaseShard(ctrl)
   366  	s2 := NewMockdatabaseShard(ctrl)
   367  	ns := NewMockdatabaseNamespace(ctrl)
   368  	ns.EXPECT().Options().Return(nsOpts).AnyTimes()
   369  	ns.EXPECT().ID().Return(defaultTestNs1ID).AnyTimes()
   370  	ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes()
   371  	s1.EXPECT().ID().Return(uint32(1)).AnyTimes()
   372  	s2.EXPECT().ID().Return(uint32(2)).AnyTimes()
   373  
   374  	// Validate that the flush state is marked as successful only AFTER all prequisite steps have been run.
   375  	// Order is important to avoid any edge case where data is GCed from memory without all flushing operations
   376  	// being completed.
   377  	gomock.InOrder(
   378  		ns.EXPECT().WarmFlush(gomock.Any(), gomock.Any()).Return(nil).AnyTimes(),
   379  		ns.EXPECT().Snapshot(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes(),
   380  		ns.EXPECT().FlushIndex(gomock.Any()).Return(nil),
   381  	)
   382  
   383  	var (
   384  		mockFlushPersist    = persist.NewMockFlushPreparer(ctrl)
   385  		mockSnapshotPersist = persist.NewMockSnapshotPreparer(ctrl)
   386  		mockPersistManager  = persist.NewMockManager(ctrl)
   387  	)
   388  
   389  	mockFlushPersist.EXPECT().DoneFlush().Return(nil)
   390  	mockPersistManager.EXPECT().StartFlushPersist().Return(mockFlushPersist, nil)
   391  
   392  	mockSnapshotPersist.EXPECT().DoneSnapshot(gomock.Any(), testCommitlogFile).Return(nil)
   393  	mockPersistManager.EXPECT().StartSnapshotPersist(gomock.Any()).Return(mockSnapshotPersist, nil)
   394  
   395  	mockIndexFlusher := persist.NewMockIndexFlush(ctrl)
   396  	mockIndexFlusher.EXPECT().DoneIndex().Return(nil)
   397  	mockPersistManager.EXPECT().StartIndexPersist().Return(mockIndexFlusher, nil)
   398  
   399  	testOpts := DefaultTestOptions().SetPersistManager(mockPersistManager)
   400  	db := newMockdatabase(ctrl)
   401  	db.EXPECT().Options().Return(testOpts).AnyTimes()
   402  	db.EXPECT().OwnedNamespaces().Return([]databaseNamespace{ns}, nil)
   403  
   404  	cl := commitlog.NewMockCommitLog(ctrl)
   405  	cl.EXPECT().RotateLogs().Return(testCommitlogFile, nil).AnyTimes()
   406  
   407  	fm := newFlushManager(db, cl, tally.NoopScope).(*flushManager)
   408  	fm.pm = mockPersistManager
   409  
   410  	now := xtime.UnixNano(0)
   411  	require.NoError(t, fm.Flush(now))
   412  }
   413  
   414  func TestFlushManagerFlushTimeStart(t *testing.T) {
   415  	ctrl := xtest.NewController(t)
   416  	defer ctrl.Finish()
   417  
   418  	inputs := []struct {
   419  		ts       xtime.UnixNano
   420  		expected xtime.UnixNano
   421  	}{
   422  		{
   423  			ts:       xtime.FromSeconds(86400 * 2),
   424  			expected: xtime.UnixNano(0),
   425  		},
   426  		{
   427  			ts:       xtime.FromSeconds(86400*2 + 7200),
   428  			expected: xtime.FromSeconds(7200),
   429  		},
   430  		{
   431  			ts:       xtime.FromSeconds(86400*2 + 10800),
   432  			expected: xtime.FromSeconds(7200),
   433  		},
   434  	}
   435  
   436  	fm, _, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   437  	for _, input := range inputs {
   438  		start, _ := fm.flushRange(defaultTestRetentionOpts, input.ts)
   439  		require.Equal(t, input.expected, start)
   440  	}
   441  }
   442  
   443  func TestFlushManagerFlushTimeEnd(t *testing.T) {
   444  	ctrl := xtest.NewController(t)
   445  	defer ctrl.Finish()
   446  
   447  	inputs := []struct {
   448  		ts       xtime.UnixNano
   449  		expected xtime.UnixNano
   450  	}{
   451  		{
   452  			ts:       xtime.FromSeconds(7800),
   453  			expected: xtime.UnixNano(0),
   454  		},
   455  		{
   456  			ts:       xtime.FromSeconds(8000),
   457  			expected: xtime.UnixNano(0),
   458  		},
   459  		{
   460  			ts:       xtime.FromSeconds(15200),
   461  			expected: xtime.FromSeconds(7200),
   462  		},
   463  	}
   464  
   465  	fm, _, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   466  	for _, input := range inputs {
   467  		_, end := fm.flushRange(defaultTestRetentionOpts, input.ts)
   468  		require.Equal(t, input.expected, end)
   469  	}
   470  }
   471  
   472  func TestFlushManagerNamespaceFlushTimesNoNeedFlush(t *testing.T) {
   473  	ctrl := xtest.NewController(t)
   474  	defer ctrl.Finish()
   475  
   476  	fm, ns1, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   477  	now := xtime.Now()
   478  
   479  	ns1.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes()
   480  	flushTimes, err := fm.namespaceFlushTimes(ns1, now)
   481  	require.NoError(t, err)
   482  	require.Empty(t, flushTimes)
   483  }
   484  
   485  func TestFlushManagerNamespaceFlushTimesError(t *testing.T) {
   486  	ctrl := xtest.NewController(t)
   487  	defer ctrl.Finish()
   488  
   489  	fm, ns1, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   490  	now := xtime.Now()
   491  
   492  	ns1.EXPECT().
   493  		NeedsFlush(gomock.Any(), gomock.Any()).
   494  		Return(false, errors.New("an error")).
   495  		AnyTimes()
   496  	_, err := fm.namespaceFlushTimes(ns1, now)
   497  	require.Error(t, err)
   498  }
   499  
   500  func TestFlushManagerNamespaceFlushTimesAllNeedFlush(t *testing.T) {
   501  	ctrl := xtest.NewController(t)
   502  	defer ctrl.Finish()
   503  
   504  	fm, ns1, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   505  	now := xtime.Now()
   506  
   507  	ns1.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes()
   508  	times, err := fm.namespaceFlushTimes(ns1, now)
   509  	require.NoError(t, err)
   510  	sort.Sort(timesInOrder(times))
   511  
   512  	blockSize := ns1.Options().RetentionOptions().BlockSize()
   513  	start := retention.FlushTimeStart(ns1.Options().RetentionOptions(), now)
   514  	end := retention.FlushTimeEnd(ns1.Options().RetentionOptions(), now)
   515  
   516  	require.Equal(t, numIntervals(start, end, blockSize), len(times))
   517  	for i, ti := range times {
   518  		require.Equal(t, start.Add(time.Duration(i)*blockSize), ti)
   519  	}
   520  }
   521  
   522  func TestFlushManagerNamespaceFlushTimesSomeNeedFlush(t *testing.T) {
   523  	ctrl := xtest.NewController(t)
   524  	defer ctrl.Finish()
   525  
   526  	fm, ns1, _, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   527  	now := xtime.Now()
   528  
   529  	blockSize := ns1.Options().RetentionOptions().BlockSize()
   530  	start := retention.FlushTimeStart(ns1.Options().RetentionOptions(), now)
   531  	end := retention.FlushTimeEnd(ns1.Options().RetentionOptions(), now)
   532  	num := numIntervals(start, end, blockSize)
   533  
   534  	var expectedTimes []xtime.UnixNano
   535  	for i := 0; i < num; i++ {
   536  		st := start.Add(time.Duration(i) * blockSize)
   537  
   538  		// skip 1/3 of input
   539  		if i%3 == 0 {
   540  			ns1.EXPECT().NeedsFlush(st, st).Return(false, nil)
   541  			continue
   542  		}
   543  
   544  		ns1.EXPECT().NeedsFlush(st, st).Return(true, nil)
   545  		expectedTimes = append(expectedTimes, st)
   546  	}
   547  
   548  	times, err := fm.namespaceFlushTimes(ns1, now)
   549  	require.NoError(t, err)
   550  	require.NotEmpty(t, times)
   551  	sort.Sort(timesInOrder(times))
   552  	require.Equal(t, expectedTimes, times)
   553  }
   554  
   555  func TestFlushManagerFlushSnapshot(t *testing.T) {
   556  	ctrl := xtest.NewController(t)
   557  	defer ctrl.Finish()
   558  
   559  	fm, ns1, ns2, _ := newMultipleFlushManagerNeedsFlush(t, ctrl)
   560  	now := xtime.Now()
   561  
   562  	for _, ns := range []*MockdatabaseNamespace{ns1, ns2} {
   563  		rOpts := ns.Options().RetentionOptions()
   564  		blockSize := rOpts.BlockSize()
   565  		bufferFuture := rOpts.BufferFuture()
   566  
   567  		start := retention.FlushTimeStart(ns.Options().RetentionOptions(), now)
   568  		flushEnd := retention.FlushTimeEnd(ns.Options().RetentionOptions(), now)
   569  		num := numIntervals(start, flushEnd, blockSize)
   570  
   571  		for i := 0; i < num; i++ {
   572  			st := start.Add(time.Duration(i) * blockSize)
   573  			ns.EXPECT().NeedsFlush(st, st).Return(false, nil)
   574  		}
   575  
   576  		var (
   577  			snapshotEnd    = now.Add(bufferFuture).Truncate(blockSize)
   578  			snapshotBlocks []xtime.UnixNano
   579  		)
   580  		num = numIntervals(start, snapshotEnd, blockSize)
   581  		for i := num - 1; i >= 0; i-- {
   582  			snapshotBlocks = append(snapshotBlocks, start.Add(time.Duration(i)*blockSize))
   583  		}
   584  		ns.EXPECT().Snapshot(snapshotBlocks, now, gomock.Any())
   585  	}
   586  
   587  	require.NoError(t, fm.Flush(now))
   588  
   589  	lastSuccessfulSnapshot, ok := fm.LastSuccessfulSnapshotStartTime()
   590  	require.True(t, ok)
   591  	require.Equal(t, now, lastSuccessfulSnapshot)
   592  }
   593  
   594  type timesInOrder []xtime.UnixNano
   595  
   596  func (a timesInOrder) Len() int           { return len(a) }
   597  func (a timesInOrder) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   598  func (a timesInOrder) Less(i, j int) bool { return a[i].Before(a[j]) }