github.com/m3db/m3@v1.5.0/src/dbnode/storage/index_queue_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  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/namespace"
    30  	m3dberrors "github.com/m3db/m3/src/dbnode/storage/errors"
    31  	"github.com/m3db/m3/src/dbnode/storage/index"
    32  	idxconvert "github.com/m3db/m3/src/dbnode/storage/index/convert"
    33  	"github.com/m3db/m3/src/m3ninx/doc"
    34  	m3ninxidx "github.com/m3db/m3/src/m3ninx/idx"
    35  	"github.com/m3db/m3/src/m3ninx/index/segment/fst/encoding/docs"
    36  	"github.com/m3db/m3/src/x/clock"
    37  	"github.com/m3db/m3/src/x/context"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	"github.com/m3db/m3/src/x/resource"
    40  	xsync "github.com/m3db/m3/src/x/sync"
    41  	xtest "github.com/m3db/m3/src/x/test"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  
    44  	"github.com/fortytw2/leaktest"
    45  	"github.com/golang/mock/gomock"
    46  	"github.com/stretchr/testify/assert"
    47  	"github.com/stretchr/testify/require"
    48  	"github.com/uber-go/tally"
    49  )
    50  
    51  func testNamespaceIndexOptions() index.Options {
    52  	return DefaultTestOptions().IndexOptions()
    53  }
    54  
    55  func newTestNamespaceIndex(t *testing.T, ctrl *gomock.Controller) (NamespaceIndex, *MocknamespaceIndexInsertQueue) {
    56  	q := NewMocknamespaceIndexInsertQueue(ctrl)
    57  	newFn := func(
    58  		fn nsIndexInsertBatchFn,
    59  		md namespace.Metadata,
    60  		nowFn clock.NowFn,
    61  		coreFn xsync.CoreFn,
    62  		s tally.Scope,
    63  	) namespaceIndexInsertQueue {
    64  		return q
    65  	}
    66  	q.EXPECT().Start().Return(nil)
    67  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
    68  	require.NoError(t, err)
    69  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
    70  		namespace.NewRuntimeOptionsManager(md.ID().String()),
    71  		testShardSet, newFn, DefaultTestOptions())
    72  	assert.NoError(t, err)
    73  	return idx, q
    74  }
    75  
    76  func TestNamespaceIndexHappyPath(t *testing.T) {
    77  	ctrl := xtest.NewController(t)
    78  	defer ctrl.Finish()
    79  
    80  	q := NewMocknamespaceIndexInsertQueue(ctrl)
    81  	newFn := func(
    82  		fn nsIndexInsertBatchFn,
    83  		md namespace.Metadata,
    84  		nowFn clock.NowFn,
    85  		coreFn xsync.CoreFn,
    86  		s tally.Scope,
    87  	) namespaceIndexInsertQueue {
    88  		return q
    89  	}
    90  	q.EXPECT().Start().Return(nil)
    91  
    92  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
    93  	require.NoError(t, err)
    94  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
    95  		namespace.NewRuntimeOptionsManager(md.ID().String()),
    96  		testShardSet, newFn, DefaultTestOptions())
    97  	assert.NoError(t, err)
    98  	assert.NotNil(t, idx)
    99  
   100  	q.EXPECT().Stop().Return(nil)
   101  	assert.NoError(t, idx.Close())
   102  }
   103  
   104  func TestNamespaceIndexStartErr(t *testing.T) {
   105  	ctrl := xtest.NewController(t)
   106  	defer ctrl.Finish()
   107  
   108  	q := NewMocknamespaceIndexInsertQueue(ctrl)
   109  	newFn := func(
   110  		fn nsIndexInsertBatchFn,
   111  		md namespace.Metadata,
   112  		nowFn clock.NowFn,
   113  		coreFn xsync.CoreFn,
   114  		s tally.Scope,
   115  	) namespaceIndexInsertQueue {
   116  		return q
   117  	}
   118  	q.EXPECT().Start().Return(fmt.Errorf("random err"))
   119  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   120  	require.NoError(t, err)
   121  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
   122  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   123  		testShardSet, newFn, DefaultTestOptions())
   124  	assert.Error(t, err)
   125  	assert.Nil(t, idx)
   126  }
   127  
   128  func TestNamespaceIndexStopErr(t *testing.T) {
   129  	ctrl := xtest.NewController(t)
   130  	defer ctrl.Finish()
   131  
   132  	q := NewMocknamespaceIndexInsertQueue(ctrl)
   133  	newFn := func(
   134  		fn nsIndexInsertBatchFn,
   135  		md namespace.Metadata,
   136  		nowFn clock.NowFn,
   137  		coreFn xsync.CoreFn,
   138  		s tally.Scope,
   139  	) namespaceIndexInsertQueue {
   140  		return q
   141  	}
   142  	q.EXPECT().Start().Return(nil)
   143  
   144  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   145  	require.NoError(t, err)
   146  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
   147  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   148  		testShardSet, newFn, DefaultTestOptions())
   149  	assert.NoError(t, err)
   150  	assert.NotNil(t, idx)
   151  
   152  	q.EXPECT().Stop().Return(fmt.Errorf("random err"))
   153  	assert.Error(t, idx.Close())
   154  }
   155  
   156  func TestNamespaceIndexWriteAfterClose(t *testing.T) {
   157  	ctrl := xtest.NewController(t)
   158  	defer ctrl.Finish()
   159  
   160  	dbIdx, q := newTestNamespaceIndex(t, ctrl)
   161  	idx, ok := dbIdx.(*nsIndex)
   162  	assert.True(t, ok)
   163  
   164  	id := ident.StringID("foo")
   165  	tags := ident.NewTags(
   166  		ident.StringTag("name", "value"),
   167  	)
   168  
   169  	q.EXPECT().Stop().Return(nil)
   170  	assert.NoError(t, idx.Close())
   171  
   172  	now := xtime.Now()
   173  
   174  	lifecycle := doc.NewMockOnIndexSeries(ctrl)
   175  	lifecycle.EXPECT().OnIndexFinalize(now.Truncate(idx.blockSize))
   176  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).
   177  		Return(false).
   178  		AnyTimes()
   179  	entry, document := testWriteBatchEntry(id, tags, now, lifecycle)
   180  	assert.Error(t, idx.WriteBatch(testWriteBatch(entry, document,
   181  		testWriteBatchBlockSizeOption(idx.blockSize))))
   182  }
   183  
   184  func TestNamespaceIndexWriteQueueError(t *testing.T) {
   185  	ctrl := xtest.NewController(t)
   186  	defer ctrl.Finish()
   187  
   188  	dbIdx, q := newTestNamespaceIndex(t, ctrl)
   189  	idx, ok := dbIdx.(*nsIndex)
   190  	assert.True(t, ok)
   191  
   192  	id := ident.StringID("foo")
   193  	tags := ident.NewTags(
   194  		ident.StringTag("name", "value"),
   195  	)
   196  
   197  	n := xtime.Now()
   198  	lifecycle := doc.NewMockOnIndexSeries(ctrl)
   199  	lifecycle.EXPECT().OnIndexFinalize(n.Truncate(idx.blockSize))
   200  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false)
   201  	q.EXPECT().
   202  		InsertBatch(gomock.Any()).
   203  		Return(nil, fmt.Errorf("random err"))
   204  	entry, document := testWriteBatchEntry(id, tags, n, lifecycle)
   205  	assert.Error(t, idx.WriteBatch(testWriteBatch(entry, document,
   206  		testWriteBatchBlockSizeOption(idx.blockSize))))
   207  }
   208  
   209  func TestNamespaceIndexInsertOlderThanRetentionPeriod(t *testing.T) {
   210  	ctrl := xtest.NewController(t)
   211  	defer ctrl.Finish()
   212  
   213  	var (
   214  		nowLock sync.Mutex
   215  		now     = xtime.Now()
   216  		nowFn   = func() time.Time {
   217  			nowLock.Lock()
   218  			n := now
   219  			nowLock.Unlock()
   220  			return n.ToTime()
   221  		}
   222  	)
   223  
   224  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   225  	require.NoError(t, err)
   226  
   227  	opts := testNamespaceIndexOptions().SetInsertMode(index.InsertSync)
   228  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   229  	dbIdx, err := newNamespaceIndex(md,
   230  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   231  		testShardSet, DefaultTestOptions().SetIndexOptions(opts))
   232  	assert.NoError(t, err)
   233  
   234  	idx, ok := dbIdx.(*nsIndex)
   235  	assert.True(t, ok)
   236  
   237  	var (
   238  		id   = ident.StringID("foo")
   239  		tags = ident.NewTags(
   240  			ident.StringTag("name", "value"),
   241  		)
   242  		lifecycle = doc.NewMockOnIndexSeries(ctrl)
   243  	)
   244  
   245  	tooOld := now.Add(-1 * idx.bufferPast).Add(-1 * time.Second)
   246  	lifecycle.EXPECT().OnIndexFinalize(tooOld.Truncate(idx.blockSize))
   247  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).
   248  		Return(false).
   249  		AnyTimes()
   250  	entry, document := testWriteBatchEntry(id, tags, tooOld, lifecycle)
   251  	batch := testWriteBatch(entry, document, testWriteBatchBlockSizeOption(idx.blockSize))
   252  
   253  	assert.Error(t, idx.WriteBatch(batch))
   254  
   255  	verified := 0
   256  	batch.ForEach(func(
   257  		idx int,
   258  		entry index.WriteBatchEntry,
   259  		doc doc.Metadata,
   260  		result index.WriteBatchEntryResult,
   261  	) {
   262  		verified++
   263  		require.Error(t, result.Err)
   264  		require.Equal(t, m3dberrors.ErrTooPast, result.Err)
   265  	})
   266  	require.Equal(t, 1, verified)
   267  
   268  	tooNew := now.Add(1 * idx.bufferFuture).Add(1 * time.Second)
   269  	lifecycle.EXPECT().OnIndexFinalize(tooNew.Truncate(idx.blockSize))
   270  	entry, document = testWriteBatchEntry(id, tags, tooNew, lifecycle)
   271  	batch = testWriteBatch(entry, document, testWriteBatchBlockSizeOption(idx.blockSize))
   272  	assert.Error(t, idx.WriteBatch(batch))
   273  
   274  	verified = 0
   275  	batch.ForEach(func(
   276  		idx int,
   277  		entry index.WriteBatchEntry,
   278  		doc doc.Metadata,
   279  		result index.WriteBatchEntryResult,
   280  	) {
   281  		verified++
   282  		require.Error(t, result.Err)
   283  		require.Equal(t, m3dberrors.ErrTooFuture, result.Err)
   284  	})
   285  	require.Equal(t, 1, verified)
   286  
   287  	assert.NoError(t, dbIdx.Close())
   288  }
   289  
   290  func TestNamespaceIndexInsertQueueInteraction(t *testing.T) {
   291  	ctrl := xtest.NewController(t)
   292  	defer ctrl.Finish()
   293  
   294  	dbIdx, q := newTestNamespaceIndex(t, ctrl)
   295  	idx, ok := dbIdx.(*nsIndex)
   296  	assert.True(t, ok)
   297  
   298  	var (
   299  		id   = ident.StringID("foo")
   300  		tags = ident.NewTags(
   301  			ident.StringTag("name", "value"),
   302  		)
   303  	)
   304  
   305  	now := xtime.Now()
   306  
   307  	var wg sync.WaitGroup
   308  	lifecycle := doc.NewMockOnIndexSeries(ctrl)
   309  	q.EXPECT().InsertBatch(gomock.Any()).Return(&wg, nil)
   310  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).
   311  		Return(false).
   312  		AnyTimes()
   313  	assert.NoError(t, idx.WriteBatch(testWriteBatch(testWriteBatchEntry(id,
   314  		tags, now, lifecycle))))
   315  }
   316  
   317  func setupIndex(t *testing.T,
   318  	ctrl *gomock.Controller,
   319  	now xtime.UnixNano,
   320  	expectAggregateQuery bool,
   321  ) NamespaceIndex {
   322  	newFn := func(
   323  		fn nsIndexInsertBatchFn,
   324  		md namespace.Metadata,
   325  		nowFn clock.NowFn,
   326  		coreFn xsync.CoreFn,
   327  		s tally.Scope,
   328  	) namespaceIndexInsertQueue {
   329  		q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s)
   330  		q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond
   331  		return q
   332  	}
   333  	md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts)
   334  	require.NoError(t, err)
   335  	idx, err := newNamespaceIndexWithInsertQueueFn(md,
   336  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   337  		testShardSet, newFn, DefaultTestOptions().
   338  			SetIndexOptions(testNamespaceIndexOptions().
   339  				SetInsertMode(index.InsertSync)))
   340  	assert.NoError(t, err)
   341  
   342  	var (
   343  		blockSize = idx.(*nsIndex).blockSize
   344  		ts        = idx.(*nsIndex).state.latestBlock.StartTime()
   345  		id        = ident.StringID("foo")
   346  		tags      = ident.NewTags(
   347  			ident.StringTag("name", "value"),
   348  		)
   349  		lifecycleFns = doc.NewMockOnIndexSeries(ctrl)
   350  	)
   351  
   352  	closer := &resource.NoopCloser{}
   353  	lifecycleFns.EXPECT().ReconciledOnIndexSeries().Return(lifecycleFns, closer, false).AnyTimes()
   354  	lifecycleFns.EXPECT().OnIndexFinalize(ts)
   355  	lifecycleFns.EXPECT().OnIndexSuccess(ts)
   356  	lifecycleFns.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false)
   357  
   358  	if !expectAggregateQuery {
   359  		lifecycleFns.EXPECT().IndexedRange().Return(ts, ts)
   360  		lifecycleFns.EXPECT().IndexedForBlockStart(ts).Return(true)
   361  	}
   362  
   363  	entry, doc := testWriteBatchEntry(id, tags, now, lifecycleFns)
   364  	batch := testWriteBatch(entry, doc, testWriteBatchBlockSizeOption(blockSize))
   365  	assert.NoError(t, idx.WriteBatch(batch))
   366  
   367  	return idx
   368  }
   369  
   370  func TestNamespaceIndexInsertQuery(t *testing.T) {
   371  	ctrl := xtest.NewController(t)
   372  	defer ctrl.Finish()
   373  	defer leaktest.CheckTimeout(t, 2*time.Second)()
   374  
   375  	ctx := context.NewBackground()
   376  	defer ctx.Close()
   377  
   378  	now := xtime.Now()
   379  	idx := setupIndex(t, ctrl, now, false)
   380  	defer idx.Close()
   381  
   382  	reQuery, err := m3ninxidx.NewRegexpQuery([]byte("name"), []byte("val.*"))
   383  	assert.NoError(t, err)
   384  	res, err := idx.Query(ctx, index.Query{Query: reQuery}, index.QueryOptions{
   385  		StartInclusive: now.Add(-1 * time.Minute),
   386  		EndExclusive:   now.Add(1 * time.Minute),
   387  	})
   388  	require.NoError(t, err)
   389  
   390  	assert.True(t, res.Exhaustive)
   391  	results := res.Results
   392  	assert.Equal(t, "testns1", results.Namespace().String())
   393  
   394  	reader := docs.NewEncodedDocumentReader()
   395  	d, ok := results.Map().Get(ident.BytesID("foo"))
   396  	md, err := docs.MetadataFromDocument(d, reader)
   397  	require.NoError(t, err)
   398  	tags := idxconvert.ToSeriesTags(md, idxconvert.Opts{NoClone: true})
   399  
   400  	assert.True(t, ok)
   401  	assert.True(t, ident.NewTagIterMatcher(
   402  		ident.MustNewTagStringsIterator("name", "value")).Matches(
   403  		tags))
   404  }
   405  
   406  func TestNamespaceIndexInsertAggregateQuery(t *testing.T) {
   407  	ctrl := xtest.NewController(t)
   408  	defer ctrl.Finish()
   409  	defer leaktest.CheckTimeout(t, 2*time.Second)()
   410  
   411  	ctx := context.NewBackground()
   412  	defer ctx.Close()
   413  
   414  	now := xtime.Now()
   415  	idx := setupIndex(t, ctrl, now, true)
   416  	defer idx.Close()
   417  
   418  	reQuery, err := m3ninxidx.NewRegexpQuery([]byte("name"), []byte("val.*"))
   419  	assert.NoError(t, err)
   420  	res, err := idx.AggregateQuery(ctx, index.Query{Query: reQuery},
   421  		index.AggregationOptions{
   422  			QueryOptions: index.QueryOptions{
   423  				StartInclusive: now.Add(-1 * time.Minute),
   424  				EndExclusive:   now.Add(1 * time.Minute),
   425  			},
   426  		},
   427  	)
   428  	require.NoError(t, err)
   429  
   430  	assert.True(t, res.Exhaustive)
   431  	results := res.Results
   432  	assert.Equal(t, "testns1", results.Namespace().String())
   433  
   434  	rMap := results.Map()
   435  	require.Equal(t, 1, rMap.Len())
   436  	seenIters, found := rMap.Get(ident.StringID("name"))
   437  	require.True(t, found)
   438  
   439  	vMap := seenIters.Map()
   440  	require.Equal(t, 1, vMap.Len())
   441  	assert.True(t, vMap.Contains(ident.StringID("value")))
   442  }