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

     1  // Copyright (c) 2020 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  	stdlibctx "context"
    25  	"fmt"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/cluster/shard"
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	"github.com/m3db/m3/src/dbnode/sharding"
    33  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    34  	"github.com/m3db/m3/src/dbnode/storage/index"
    35  	"github.com/m3db/m3/src/dbnode/storage/limits"
    36  	"github.com/m3db/m3/src/m3ninx/doc"
    37  	"github.com/m3db/m3/src/m3ninx/idx"
    38  	"github.com/m3db/m3/src/m3ninx/index/segment"
    39  	idxpersist "github.com/m3db/m3/src/m3ninx/persist"
    40  	"github.com/m3db/m3/src/x/context"
    41  	xerrors "github.com/m3db/m3/src/x/errors"
    42  	"github.com/m3db/m3/src/x/ident"
    43  	"github.com/m3db/m3/src/x/instrument"
    44  	xtest "github.com/m3db/m3/src/x/test"
    45  	xtime "github.com/m3db/m3/src/x/time"
    46  
    47  	"github.com/golang/mock/gomock"
    48  	opentracing "github.com/opentracing/opentracing-go"
    49  	opentracinglog "github.com/opentracing/opentracing-go/log"
    50  	"github.com/opentracing/opentracing-go/mocktracer"
    51  	"github.com/stretchr/testify/require"
    52  )
    53  
    54  var (
    55  	namespaceIndexOptions = namespace.NewIndexOptions()
    56  
    57  	defaultQuery = index.Query{
    58  		Query: idx.NewTermQuery([]byte("foo"), []byte("bar")),
    59  	}
    60  
    61  	testShardSet sharding.ShardSet
    62  )
    63  
    64  func init() {
    65  	shards := sharding.NewShards([]uint32{0, 1, 2, 3}, shard.Available)
    66  	hashFn := sharding.DefaultHashFn(len(shards))
    67  	shardSet, err := sharding.NewShardSet(shards, hashFn)
    68  	if err != nil {
    69  		panic(err)
    70  	}
    71  	testShardSet = shardSet
    72  }
    73  
    74  type testWriteBatchOption func(index.WriteBatchOptions) index.WriteBatchOptions
    75  
    76  func testWriteBatchBlockSizeOption(blockSize time.Duration) testWriteBatchOption {
    77  	return func(o index.WriteBatchOptions) index.WriteBatchOptions {
    78  		o.IndexBlockSize = blockSize
    79  		return o
    80  	}
    81  }
    82  
    83  func testWriteBatch(
    84  	e index.WriteBatchEntry,
    85  	d doc.Metadata,
    86  	opts ...testWriteBatchOption,
    87  ) *index.WriteBatch {
    88  	options := index.WriteBatchOptions{}
    89  	for _, opt := range opts {
    90  		options = opt(options)
    91  	}
    92  	b := index.NewWriteBatch(options)
    93  	b.Append(e, d)
    94  	return b
    95  }
    96  
    97  func testWriteBatchEntry(
    98  	id ident.ID,
    99  	tags ident.Tags,
   100  	timestamp xtime.UnixNano,
   101  	fns doc.OnIndexSeries,
   102  ) (index.WriteBatchEntry, doc.Metadata) {
   103  	d := doc.Metadata{ID: copyBytes(id.Bytes())}
   104  	for _, tag := range tags.Values() {
   105  		d.Fields = append(d.Fields, doc.Field{
   106  			Name:  copyBytes(tag.Name.Bytes()),
   107  			Value: copyBytes(tag.Value.Bytes()),
   108  		})
   109  	}
   110  	return index.WriteBatchEntry{
   111  		Timestamp:     timestamp,
   112  		OnIndexSeries: fns,
   113  	}, d
   114  }
   115  
   116  func copyBytes(b []byte) []byte {
   117  	return append([]byte(nil), b...)
   118  }
   119  
   120  func testNamespaceMetadata(blockSize, period time.Duration) namespace.Metadata {
   121  	nopts := namespaceOptions.
   122  		SetRetentionOptions(namespaceOptions.RetentionOptions().
   123  			SetRetentionPeriod(period)).
   124  		SetIndexOptions(
   125  			namespaceIndexOptions.
   126  				SetBlockSize(blockSize))
   127  	md, err := namespace.NewMetadata(ident.StringID("testns"), nopts)
   128  	if err != nil {
   129  		panic(err)
   130  	}
   131  	return md
   132  }
   133  
   134  func TestNamespaceIndexNewBlockFn(t *testing.T) {
   135  	ctrl := xtest.NewController(t)
   136  	defer ctrl.Finish()
   137  
   138  	blockSize := time.Hour
   139  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   140  	nowFn := func() time.Time { return now.ToTime() }
   141  	opts := DefaultTestOptions()
   142  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   143  
   144  	mockBlock := index.NewMockBlock(ctrl)
   145  	mockBlock.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   146  	mockBlock.EXPECT().StartTime().Return(now.Truncate(blockSize)).AnyTimes()
   147  	mockBlock.EXPECT().Close().Return(nil).AnyTimes()
   148  	newBlockFn := func(
   149  		ts xtime.UnixNano,
   150  		md namespace.Metadata,
   151  		opts index.BlockOptions,
   152  		_ namespace.RuntimeOptionsManager,
   153  		io index.Options,
   154  	) (index.Block, error) {
   155  		// If active block, the blockStart should be zero.
   156  		// Otherwise, it should match the actual time.
   157  		if opts.ActiveBlock {
   158  			require.Equal(t, xtime.UnixNano(0), ts)
   159  		} else {
   160  			require.Equal(t, now.Truncate(blockSize), ts)
   161  		}
   162  		return mockBlock, nil
   163  	}
   164  	md := testNamespaceMetadata(blockSize, 4*time.Hour)
   165  	index, err := newNamespaceIndexWithNewBlockFn(md,
   166  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   167  		testShardSet, newBlockFn, opts)
   168  	require.NoError(t, err)
   169  
   170  	defer func() {
   171  		require.NoError(t, index.Close())
   172  	}()
   173  
   174  	blocksSlice := index.(*nsIndex).state.blocksDescOrderImmutable
   175  
   176  	require.Equal(t, 1, len(blocksSlice))
   177  	require.Equal(t, now.Truncate(blockSize), blocksSlice[0].blockStart)
   178  
   179  	require.Equal(t, mockBlock, index.(*nsIndex).state.latestBlock)
   180  
   181  	blocksMap := index.(*nsIndex).state.blocksByTime
   182  	require.Equal(t, 1, len(blocksMap))
   183  	blk, ok := blocksMap[now.Truncate(blockSize)]
   184  	require.True(t, ok)
   185  	require.Equal(t, mockBlock, blk)
   186  }
   187  
   188  func TestNamespaceIndexNewBlockFnRandomErr(t *testing.T) {
   189  	ctrl := xtest.NewController(t)
   190  	defer ctrl.Finish()
   191  
   192  	blockSize := time.Hour
   193  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   194  	nowFn := func() time.Time { return now.ToTime() }
   195  	opts := DefaultTestOptions()
   196  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   197  
   198  	newBlockFn := func(
   199  		ts xtime.UnixNano,
   200  		md namespace.Metadata,
   201  		_ index.BlockOptions,
   202  		_ namespace.RuntimeOptionsManager,
   203  		io index.Options,
   204  	) (index.Block, error) {
   205  		return nil, fmt.Errorf("randomerr")
   206  	}
   207  	defer instrument.SetShouldPanicEnvironmentVariable(true)()
   208  	md := testNamespaceMetadata(blockSize, 4*time.Hour)
   209  	require.Panics(t, func() {
   210  		_, _ = newNamespaceIndexWithNewBlockFn(md,
   211  			namespace.NewRuntimeOptionsManager(md.ID().String()),
   212  			testShardSet, newBlockFn, opts)
   213  	})
   214  }
   215  
   216  func TestNamespaceIndexWrite(t *testing.T) {
   217  	ctrl := xtest.NewController(t)
   218  	defer ctrl.Finish()
   219  
   220  	blockSize := time.Hour
   221  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   222  	nowFn := func() time.Time { return now.ToTime() }
   223  	opts := DefaultTestOptions()
   224  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   225  
   226  	mockBlock := index.NewMockBlock(ctrl)
   227  	mockBlock.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   228  	mockBlock.EXPECT().Close().Return(nil).Times(2) // active and normal
   229  	mockBlock.EXPECT().StartTime().Return(now.Truncate(blockSize)).AnyTimes()
   230  	newBlockFn := func(
   231  		ts xtime.UnixNano,
   232  		md namespace.Metadata,
   233  		opts index.BlockOptions,
   234  		_ namespace.RuntimeOptionsManager,
   235  		io index.Options,
   236  	) (index.Block, error) {
   237  		// If active block, the blockStart should be zero.
   238  		// Otherwise, it should match the actual time.
   239  		if opts.ActiveBlock {
   240  			require.Equal(t, xtime.UnixNano(0), ts)
   241  		} else {
   242  			require.Equal(t, now.Truncate(blockSize), ts)
   243  		}
   244  		return mockBlock, nil
   245  	}
   246  	md := testNamespaceMetadata(blockSize, 4*time.Hour)
   247  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   248  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   249  		testShardSet, newBlockFn, opts)
   250  	require.NoError(t, err)
   251  
   252  	defer func() {
   253  		require.NoError(t, idx.Close())
   254  	}()
   255  
   256  	id := ident.StringID("foo")
   257  	tag := ident.StringTag("name", "value")
   258  	tags := ident.NewTags(tag)
   259  	lifecycle := doc.NewMockOnIndexSeries(ctrl)
   260  	mockWriteBatch(t, &now, lifecycle, mockBlock, &tag)
   261  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false)
   262  	batch := index.NewWriteBatch(index.WriteBatchOptions{
   263  		IndexBlockSize: blockSize,
   264  	})
   265  	batch.Append(testWriteBatchEntry(id, tags, now, lifecycle))
   266  	require.NoError(t, idx.WriteBatch(batch))
   267  }
   268  
   269  func TestNamespaceIndexWriteCreatesBlock(t *testing.T) {
   270  	ctrl := xtest.NewController(t)
   271  	defer ctrl.Finish()
   272  
   273  	blockSize := time.Hour
   274  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   275  	t0 := now.Truncate(blockSize)
   276  	t1 := t0.Add(blockSize)
   277  	var nowLock sync.Mutex
   278  	nowFn := func() time.Time {
   279  		nowLock.Lock()
   280  		defer nowLock.Unlock()
   281  		return now.ToTime()
   282  	}
   283  	opts := DefaultTestOptions()
   284  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   285  
   286  	bActive := index.NewMockBlock(ctrl)
   287  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   288  	bActive.EXPECT().Close().Return(nil)
   289  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   290  	b0 := index.NewMockBlock(ctrl)
   291  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   292  	b0.EXPECT().Close().Return(nil)
   293  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   294  	b1 := index.NewMockBlock(ctrl)
   295  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   296  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
   297  	newBlockFn := func(
   298  		ts xtime.UnixNano,
   299  		md namespace.Metadata,
   300  		opts index.BlockOptions,
   301  		_ namespace.RuntimeOptionsManager,
   302  		io index.Options,
   303  	) (index.Block, error) {
   304  		if opts.ActiveBlock {
   305  			return bActive, nil
   306  		}
   307  		if ts.Equal(t0) {
   308  			return b0, nil
   309  		}
   310  		if ts.Equal(t1) {
   311  			return b1, nil
   312  		}
   313  		panic("should never get here")
   314  	}
   315  	md := testNamespaceMetadata(blockSize, 4*time.Hour)
   316  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   317  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   318  		testShardSet, newBlockFn, opts)
   319  	require.NoError(t, err)
   320  
   321  	defer func() {
   322  		require.NoError(t, idx.Close())
   323  	}()
   324  
   325  	id := ident.StringID("foo")
   326  	tag := ident.StringTag("name", "value")
   327  	tags := ident.NewTags(tag)
   328  	lifecycle := doc.NewMockOnIndexSeries(ctrl)
   329  	mockWriteBatch(t, &now, lifecycle, bActive, &tag)
   330  	lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).
   331  		Return(false).
   332  		AnyTimes()
   333  	nowLock.Lock()
   334  	now = now.Add(blockSize)
   335  	nowLock.Unlock()
   336  
   337  	entry, doc := testWriteBatchEntry(id, tags, now, lifecycle)
   338  	batch := testWriteBatch(entry, doc, testWriteBatchBlockSizeOption(blockSize))
   339  	require.NoError(t, idx.WriteBatch(batch))
   340  }
   341  
   342  func TestNamespaceIndexBootstrap(t *testing.T) {
   343  	ctrl := xtest.NewController(t)
   344  	defer ctrl.Finish()
   345  
   346  	blockSize := time.Hour
   347  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   348  	t0 := now.Truncate(blockSize)
   349  	t1 := t0.Add(1 * blockSize)
   350  	t2 := t1.Add(1 * blockSize)
   351  	var nowLock sync.Mutex
   352  	nowFn := func() time.Time {
   353  		nowLock.Lock()
   354  		defer nowLock.Unlock()
   355  		return now.ToTime()
   356  	}
   357  	opts := DefaultTestOptions()
   358  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   359  
   360  	bActive := index.NewMockBlock(ctrl)
   361  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   362  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   363  	b0 := index.NewMockBlock(ctrl)
   364  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   365  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   366  	b1 := index.NewMockBlock(ctrl)
   367  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   368  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
   369  	newBlockFn := func(
   370  		ts xtime.UnixNano,
   371  		md namespace.Metadata,
   372  		opts index.BlockOptions,
   373  		_ namespace.RuntimeOptionsManager,
   374  		io index.Options,
   375  	) (index.Block, error) {
   376  		if opts.ActiveBlock {
   377  			return bActive, nil
   378  		}
   379  		if ts.Equal(t0) {
   380  			return b0, nil
   381  		}
   382  		if ts.Equal(t1) {
   383  			return b1, nil
   384  		}
   385  		panic("should never get here")
   386  	}
   387  	md := testNamespaceMetadata(blockSize, 4*time.Hour)
   388  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   389  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   390  		testShardSet, newBlockFn, opts)
   391  	require.NoError(t, err)
   392  
   393  	seg1 := segment.NewMockSegment(ctrl)
   394  	seg2 := segment.NewMockSegment(ctrl)
   395  	seg3 := segment.NewMockSegment(ctrl)
   396  	t0Results := result.NewIndexBlockByVolumeType(t0)
   397  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
   398  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
   399  	t1Results := result.NewIndexBlockByVolumeType(t1)
   400  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
   401  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
   402  	bootstrapResults := result.IndexResults{
   403  		t0: t0Results,
   404  		t1: t1Results,
   405  	}
   406  
   407  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
   408  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
   409  	require.NoError(t, idx.Bootstrap(bootstrapResults))
   410  }
   411  
   412  func TestNamespaceIndexTickExpire(t *testing.T) {
   413  	ctrl := xtest.NewController(t)
   414  	defer ctrl.Finish()
   415  
   416  	retentionPeriod := 4 * time.Hour
   417  	blockSize := time.Hour
   418  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   419  	t0 := now.Truncate(blockSize)
   420  	var nowLock sync.Mutex
   421  	nowFn := func() time.Time {
   422  		nowLock.Lock()
   423  		defer nowLock.Unlock()
   424  		return now.ToTime()
   425  	}
   426  	opts := DefaultTestOptions()
   427  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   428  
   429  	bActive := index.NewMockBlock(ctrl)
   430  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   431  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   432  	b0 := index.NewMockBlock(ctrl)
   433  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   434  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   435  	newBlockFn := func(
   436  		ts xtime.UnixNano,
   437  		md namespace.Metadata,
   438  		opts index.BlockOptions,
   439  		_ namespace.RuntimeOptionsManager,
   440  		io index.Options,
   441  	) (index.Block, error) {
   442  		if opts.ActiveBlock {
   443  			return bActive, nil
   444  		}
   445  		if ts.Equal(t0) {
   446  			return b0, nil
   447  		}
   448  		panic("should never get here")
   449  	}
   450  	md := testNamespaceMetadata(blockSize, retentionPeriod)
   451  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   452  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   453  		testShardSet, newBlockFn, opts)
   454  	require.NoError(t, err)
   455  
   456  	nowLock.Lock()
   457  	now = now.Add(retentionPeriod).Add(blockSize)
   458  	nowLock.Unlock()
   459  
   460  	c := context.NewCancellable()
   461  
   462  	bActive.EXPECT().Tick(c).Return(index.BlockTickResult{}, nil)
   463  
   464  	b0.EXPECT().Close().Return(nil)
   465  
   466  	result, err := idx.Tick(c, xtime.ToUnixNano(nowFn()))
   467  	require.NoError(t, err)
   468  	require.Equal(t, namespaceIndexTickResult{
   469  		NumBlocks:        0,
   470  		NumBlocksEvicted: 0,
   471  	}, result)
   472  }
   473  
   474  func TestNamespaceIndexTick(t *testing.T) {
   475  	ctrl := xtest.NewController(t)
   476  	defer ctrl.Finish()
   477  
   478  	retentionPeriod := 4 * time.Hour
   479  	blockSize := time.Hour
   480  	now := xtime.Now().Truncate(blockSize).Add(2 * time.Minute)
   481  	t0 := now.Truncate(blockSize)
   482  	var nowLock sync.Mutex
   483  	nowFn := func() time.Time {
   484  		nowLock.Lock()
   485  		defer nowLock.Unlock()
   486  		return now.ToTime()
   487  	}
   488  	opts := DefaultTestOptions()
   489  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   490  
   491  	bActive := index.NewMockBlock(ctrl)
   492  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   493  	bActive.EXPECT().Close().Return(nil)
   494  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   495  	b0 := index.NewMockBlock(ctrl)
   496  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   497  	b0.EXPECT().Close().Return(nil)
   498  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   499  	newBlockFn := func(
   500  		ts xtime.UnixNano,
   501  		md namespace.Metadata,
   502  		opts index.BlockOptions,
   503  		_ namespace.RuntimeOptionsManager,
   504  		io index.Options,
   505  	) (index.Block, error) {
   506  		if opts.ActiveBlock {
   507  			return bActive, nil
   508  		}
   509  		if ts.Equal(t0) {
   510  			return b0, nil
   511  		}
   512  		panic("should never get here")
   513  	}
   514  	md := testNamespaceMetadata(blockSize, retentionPeriod)
   515  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   516  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   517  		testShardSet, newBlockFn, opts)
   518  	require.NoError(t, err)
   519  
   520  	defer func() {
   521  		require.NoError(t, idx.Close())
   522  	}()
   523  
   524  	c := context.NewCancellable()
   525  
   526  	bActive.EXPECT().Tick(c).
   527  		Return(index.BlockTickResult{
   528  			NumDocs:     10,
   529  			NumSegments: 2,
   530  		}, nil).
   531  		AnyTimes()
   532  	bActive.EXPECT().IsSealed().Return(false).AnyTimes()
   533  
   534  	b0.EXPECT().Tick(c).
   535  		Return(index.BlockTickResult{
   536  			NumDocs:     10,
   537  			NumSegments: 2,
   538  		}, nil)
   539  	b0.EXPECT().IsSealed().Return(false)
   540  
   541  	result, err := idx.Tick(c, xtime.ToUnixNano(nowFn()))
   542  	require.NoError(t, err)
   543  	require.Equal(t, namespaceIndexTickResult{
   544  		NumBlocks:    1,
   545  		NumSegments:  4,
   546  		NumTotalDocs: 20,
   547  	}, result)
   548  
   549  	nowLock.Lock()
   550  	now = now.Add(2 * blockSize)
   551  	nowLock.Unlock()
   552  
   553  	b0.EXPECT().Tick(c).Return(index.BlockTickResult{
   554  		NumDocs:     10,
   555  		NumSegments: 2,
   556  	}, nil)
   557  	b0.EXPECT().IsSealed().Return(false).Times(1)
   558  	b0.EXPECT().Seal().Return(nil).AnyTimes()
   559  	result, err = idx.Tick(c, xtime.ToUnixNano(nowFn()))
   560  	require.NoError(t, err)
   561  	require.Equal(t, namespaceIndexTickResult{
   562  		NumBlocks:       1,
   563  		NumBlocksSealed: 0,
   564  		NumSegments:     4,
   565  		NumTotalDocs:    20,
   566  	}, result)
   567  
   568  	b0.EXPECT().Tick(c).Return(index.BlockTickResult{
   569  		NumDocs:     10,
   570  		NumSegments: 2,
   571  	}, nil)
   572  	result, err = idx.Tick(c, xtime.ToUnixNano(nowFn()))
   573  	require.NoError(t, err)
   574  	require.Equal(t, namespaceIndexTickResult{
   575  		NumBlocks:    1,
   576  		NumSegments:  4,
   577  		NumTotalDocs: 20,
   578  	}, result)
   579  }
   580  
   581  func TestNamespaceIndexBlockQuery(t *testing.T) {
   582  	ctrl := xtest.NewController(t)
   583  	defer ctrl.Finish()
   584  
   585  	retention := 2 * time.Hour
   586  	blockSize := time.Hour
   587  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
   588  	t0 := now.Truncate(blockSize)
   589  	t1 := t0.Add(1 * blockSize)
   590  	t2 := t1.Add(1 * blockSize)
   591  	var nowLock sync.Mutex
   592  	nowFn := func() time.Time {
   593  		nowLock.Lock()
   594  		defer nowLock.Unlock()
   595  		return now.ToTime()
   596  	}
   597  	opts := DefaultTestOptions()
   598  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   599  
   600  	bActive := index.NewMockBlock(ctrl)
   601  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   602  	bActive.EXPECT().Close().Return(nil)
   603  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   604  	bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   605  	b0 := index.NewMockBlock(ctrl)
   606  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   607  	b0.EXPECT().Close().Return(nil)
   608  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   609  	b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   610  	b1 := index.NewMockBlock(ctrl)
   611  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   612  	b1.EXPECT().Close().Return(nil)
   613  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
   614  	b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes()
   615  	newBlockFn := func(
   616  		ts xtime.UnixNano,
   617  		md namespace.Metadata,
   618  		opts index.BlockOptions,
   619  		_ namespace.RuntimeOptionsManager,
   620  		io index.Options,
   621  	) (index.Block, error) {
   622  		if opts.ActiveBlock {
   623  			return bActive, nil
   624  		}
   625  		if ts.Equal(t0) {
   626  			return b0, nil
   627  		}
   628  		if ts.Equal(t1) {
   629  			return b1, nil
   630  		}
   631  		panic("should never get here")
   632  	}
   633  	md := testNamespaceMetadata(blockSize, retention)
   634  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   635  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   636  		testShardSet, newBlockFn, opts)
   637  	require.NoError(t, err)
   638  
   639  	defer func() {
   640  		require.NoError(t, idx.Close())
   641  	}()
   642  
   643  	seg1 := segment.NewMockSegment(ctrl)
   644  	seg2 := segment.NewMockSegment(ctrl)
   645  	seg3 := segment.NewMockSegment(ctrl)
   646  	t0Results := result.NewIndexBlockByVolumeType(t0)
   647  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
   648  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
   649  	t1Results := result.NewIndexBlockByVolumeType(t1)
   650  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
   651  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
   652  	bootstrapResults := result.IndexResults{
   653  		t0: t0Results,
   654  		t1: t1Results,
   655  	}
   656  
   657  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
   658  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
   659  	require.NoError(t, idx.Bootstrap(bootstrapResults))
   660  
   661  	for _, test := range []struct {
   662  		name              string
   663  		requireExhaustive bool
   664  	}{
   665  		{"allow non-exhaustive", false},
   666  		{"require exhaustive", true},
   667  	} {
   668  		t.Run(test.name, func(t *testing.T) {
   669  			// only queries as much as is needed (wrt to time)
   670  			ctx := context.NewBackground()
   671  			q := defaultQuery
   672  			qOpts := index.QueryOptions{
   673  				StartInclusive: t0,
   674  				EndExclusive:   now.Add(time.Minute),
   675  			}
   676  
   677  			// Lock to prevent race given these blocks are processed concurrently.
   678  			var resultLock sync.Mutex
   679  
   680  			// create initial span from a mock tracer and get ctx
   681  			mtr := mocktracer.New()
   682  			sp := mtr.StartSpan("root")
   683  			ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp))
   684  
   685  			mockIterActive := index.NewMockQueryIterator(ctrl)
   686  			mockIter0 := index.NewMockQueryIterator(ctrl)
   687  			bActive.EXPECT().QueryIter(gomock.Any(), q).Return(mockIterActive, nil)
   688  			mockIterActive.EXPECT().Done().Return(true)
   689  			mockIterActive.EXPECT().Close().Return(nil)
   690  			b0.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter0, nil)
   691  			mockIter0.EXPECT().Done().Return(true)
   692  			mockIter0.EXPECT().Close().Return(nil)
   693  
   694  			result, err := idx.Query(ctx, q, qOpts)
   695  			require.NoError(t, err)
   696  			require.True(t, result.Exhaustive)
   697  
   698  			// queries multiple blocks if needed
   699  			qOpts = index.QueryOptions{
   700  				StartInclusive:    t0,
   701  				EndExclusive:      t2.Add(time.Minute),
   702  				RequireExhaustive: test.requireExhaustive,
   703  			}
   704  			bActive.EXPECT().QueryIter(gomock.Any(), q).Return(mockIterActive, nil)
   705  			mockIterActive.EXPECT().Done().Return(true)
   706  			mockIterActive.EXPECT().Close().Return(nil)
   707  			b0.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter0, nil)
   708  			mockIter0.EXPECT().Done().Return(true)
   709  			mockIter0.EXPECT().Close().Return(nil)
   710  
   711  			mockIter1 := index.NewMockQueryIterator(ctrl)
   712  			b1.EXPECT().QueryIter(gomock.Any(), q).Return(mockIter1, nil)
   713  			mockIter1.EXPECT().Done().Return(true)
   714  			mockIter1.EXPECT().Close().Return(nil)
   715  
   716  			result, err = idx.Query(ctx, q, qOpts)
   717  			require.NoError(t, err)
   718  			require.True(t, result.Exhaustive)
   719  
   720  			// stops querying once a block returns non-exhaustive
   721  			qOpts = index.QueryOptions{
   722  				StartInclusive:    t0,
   723  				EndExclusive:      t0.Add(time.Minute),
   724  				RequireExhaustive: test.requireExhaustive,
   725  				SeriesLimit:       1,
   726  			}
   727  
   728  			docs := []doc.Document{
   729  				doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}),
   730  				doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("B")}),
   731  			}
   732  			mockQueryWithIter(t, mockIterActive, bActive, q, qOpts, &resultLock, docs)
   733  			mockQueryWithIter(t, mockIter0, b0, q, qOpts, &resultLock, docs)
   734  
   735  			result, err = idx.Query(ctx, q, qOpts)
   736  			if test.requireExhaustive {
   737  				require.Error(t, err)
   738  				require.False(t, xerrors.IsRetryableError(err))
   739  			} else {
   740  				require.NoError(t, err)
   741  				require.False(t, result.Exhaustive)
   742  			}
   743  
   744  			sp.Finish()
   745  			spans := mtr.FinishedSpans()
   746  			require.Len(t, spans, 9)
   747  		})
   748  	}
   749  }
   750  
   751  func TestLimits(t *testing.T) {
   752  	ctrl := xtest.NewController(t)
   753  	defer ctrl.Finish()
   754  
   755  	retention := 2 * time.Hour
   756  	blockSize := time.Hour
   757  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
   758  	t0 := now.Truncate(blockSize)
   759  	t1 := t0.Add(1 * blockSize)
   760  	var nowLock sync.Mutex
   761  	nowFn := func() time.Time {
   762  		nowLock.Lock()
   763  		defer nowLock.Unlock()
   764  		return now.ToTime()
   765  	}
   766  	opts := DefaultTestOptions()
   767  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   768  
   769  	bActive := index.NewMockBlock(ctrl)
   770  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   771  	bActive.EXPECT().Close().Return(nil).AnyTimes()
   772  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   773  	bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   774  	b0 := index.NewMockBlock(ctrl)
   775  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   776  	b0.EXPECT().Close().Return(nil).AnyTimes()
   777  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   778  	b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   779  	newBlockFn := func(
   780  		ts xtime.UnixNano,
   781  		md namespace.Metadata,
   782  		opts index.BlockOptions,
   783  		_ namespace.RuntimeOptionsManager,
   784  		io index.Options,
   785  	) (index.Block, error) {
   786  		if opts.ActiveBlock {
   787  			return bActive, nil
   788  		}
   789  		if ts.Equal(t0) {
   790  			return b0, nil
   791  		}
   792  		panic("should never get here")
   793  	}
   794  	md := testNamespaceMetadata(blockSize, retention)
   795  	idx, err := newNamespaceIndexWithNewBlockFn(md,
   796  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   797  		testShardSet, newBlockFn, opts)
   798  	require.NoError(t, err)
   799  
   800  	defer func() {
   801  		require.NoError(t, idx.Close())
   802  	}()
   803  
   804  	seg1 := segment.NewMockSegment(ctrl)
   805  	t0Results := result.NewIndexBlockByVolumeType(t0)
   806  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
   807  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
   808  	bootstrapResults := result.IndexResults{
   809  		t0: t0Results,
   810  	}
   811  
   812  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
   813  	require.NoError(t, idx.Bootstrap(bootstrapResults))
   814  
   815  	for _, test := range []struct {
   816  		name                            string
   817  		seriesLimit                     int
   818  		docsLimit                       int
   819  		requireExhaustive               bool
   820  		expectedErr                     string
   821  		expectedQueryLimitExceededError bool
   822  	}{
   823  		{
   824  			name:              "no limits",
   825  			seriesLimit:       0,
   826  			docsLimit:         0,
   827  			requireExhaustive: false,
   828  			expectedErr:       "",
   829  		},
   830  		{
   831  			name:              "series limit only",
   832  			seriesLimit:       1,
   833  			docsLimit:         0,
   834  			requireExhaustive: false,
   835  			expectedErr:       "",
   836  		},
   837  		{
   838  			name:              "docs limit only",
   839  			seriesLimit:       0,
   840  			docsLimit:         1,
   841  			requireExhaustive: false,
   842  			expectedErr:       "",
   843  		},
   844  		{
   845  			name:              "both series and docs limit",
   846  			seriesLimit:       1,
   847  			docsLimit:         1,
   848  			requireExhaustive: false,
   849  			expectedErr:       "",
   850  		},
   851  		{
   852  			name:              "series limit only",
   853  			seriesLimit:       1,
   854  			docsLimit:         0,
   855  			requireExhaustive: true,
   856  			expectedErr: "query exceeded limit: require_exhaustive=true, " +
   857  				"series_limit=1, series_matched=1, docs_limit=0, docs_matched=4",
   858  			expectedQueryLimitExceededError: true,
   859  		},
   860  		{
   861  			name:              "docs limit only",
   862  			seriesLimit:       0,
   863  			docsLimit:         1,
   864  			requireExhaustive: true,
   865  			expectedErr: "query exceeded limit: require_exhaustive=true, " +
   866  				"series_limit=0, series_matched=1, docs_limit=1, docs_matched=4",
   867  			expectedQueryLimitExceededError: true,
   868  		},
   869  		{
   870  			name:              "both series and docs limit",
   871  			seriesLimit:       1,
   872  			docsLimit:         1,
   873  			requireExhaustive: true,
   874  			expectedErr: "query exceeded limit: require_exhaustive=true, " +
   875  				"series_limit=1, series_matched=1, docs_limit=1, docs_matched=4",
   876  			expectedQueryLimitExceededError: true,
   877  		},
   878  	} {
   879  		t.Run(test.name, func(t *testing.T) {
   880  			// only queries as much as is needed (wrt to time)
   881  			ctx := context.NewBackground()
   882  			q := defaultQuery
   883  			qOpts := index.QueryOptions{
   884  				StartInclusive:    t0,
   885  				EndExclusive:      t1.Add(time.Minute),
   886  				SeriesLimit:       test.seriesLimit,
   887  				DocsLimit:         test.docsLimit,
   888  				RequireExhaustive: test.requireExhaustive,
   889  			}
   890  
   891  			// Lock to prevent race given these blocks are processed concurrently.
   892  			var resultLock sync.Mutex
   893  
   894  			// create initial span from a mock tracer and get ctx
   895  			mtr := mocktracer.New()
   896  			sp := mtr.StartSpan("root")
   897  			ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp))
   898  
   899  			mockIterActive := index.NewMockQueryIterator(ctrl)
   900  			mockIter := index.NewMockQueryIterator(ctrl)
   901  
   902  			docs := []doc.Document{
   903  				// Results in size=1 and docs=2.
   904  				// Byte array represents ID encoded as bytes.
   905  				// 1 represents the ID length in bytes, 49 is the ID itself which is
   906  				// the ASCII value for A
   907  				doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}),
   908  				doc.NewDocumentFromMetadata(doc.Metadata{ID: []byte("A")}),
   909  			}
   910  			mockQueryWithIter(t, mockIterActive, bActive, q, qOpts, &resultLock, docs)
   911  			mockQueryWithIter(t, mockIter, b0, q, qOpts, &resultLock, docs)
   912  
   913  			result, err := idx.Query(ctx, q, qOpts)
   914  			if test.seriesLimit == 0 && test.docsLimit == 0 {
   915  				require.True(t, result.Exhaustive)
   916  			} else {
   917  				require.False(t, result.Exhaustive)
   918  			}
   919  
   920  			if test.requireExhaustive {
   921  				require.Error(t, err)
   922  				require.Equal(t, test.expectedErr, err.Error())
   923  				require.Equal(t, test.expectedQueryLimitExceededError, limits.IsQueryLimitExceededError(err))
   924  				require.Equal(t, test.expectedQueryLimitExceededError, xerrors.IsInvalidParams(err))
   925  			} else {
   926  				require.NoError(t, err)
   927  			}
   928  		})
   929  	}
   930  }
   931  
   932  func TestNamespaceIndexBlockQueryReleasingContext(t *testing.T) {
   933  	ctrl := xtest.NewController(t)
   934  	defer ctrl.Finish()
   935  
   936  	retention := 2 * time.Hour
   937  	blockSize := time.Hour
   938  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
   939  	t0 := now.Truncate(blockSize)
   940  	t1 := t0.Add(1 * blockSize)
   941  	t2 := t1.Add(1 * blockSize)
   942  	var nowLock sync.Mutex
   943  	nowFn := func() time.Time {
   944  		nowLock.Lock()
   945  		defer nowLock.Unlock()
   946  		return now.ToTime()
   947  	}
   948  	opts := DefaultTestOptions()
   949  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
   950  
   951  	bActive := index.NewMockBlock(ctrl)
   952  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   953  	bActive.EXPECT().Close().Return(nil)
   954  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
   955  	bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   956  	b0 := index.NewMockBlock(ctrl)
   957  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   958  	b0.EXPECT().Close().Return(nil)
   959  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
   960  	b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
   961  	b1 := index.NewMockBlock(ctrl)
   962  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
   963  	b1.EXPECT().Close().Return(nil)
   964  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
   965  	b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes()
   966  	newBlockFn := func(
   967  		ts xtime.UnixNano,
   968  		md namespace.Metadata,
   969  		opts index.BlockOptions,
   970  		_ namespace.RuntimeOptionsManager,
   971  		io index.Options,
   972  	) (index.Block, error) {
   973  		if opts.ActiveBlock {
   974  			return bActive, nil
   975  		}
   976  		if ts.Equal(t0) {
   977  			return b0, nil
   978  		}
   979  		if ts.Equal(t1) {
   980  			return b1, nil
   981  		}
   982  		panic("should never get here")
   983  	}
   984  
   985  	iopts := opts.IndexOptions()
   986  	mockPool := index.NewMockQueryResultsPool(ctrl)
   987  	iopts = iopts.SetQueryResultsPool(mockPool)
   988  	stubResult := index.NewQueryResults(ident.StringID("ns"), index.QueryResultsOptions{}, iopts)
   989  
   990  	md := testNamespaceMetadata(blockSize, retention)
   991  	idxIface, err := newNamespaceIndexWithNewBlockFn(md,
   992  		namespace.NewRuntimeOptionsManager(md.ID().String()),
   993  		testShardSet, newBlockFn, opts)
   994  	require.NoError(t, err)
   995  
   996  	idx, ok := idxIface.(*nsIndex)
   997  	require.True(t, ok)
   998  	idx.resultsPool = mockPool
   999  
  1000  	defer func() {
  1001  		require.NoError(t, idx.Close())
  1002  	}()
  1003  
  1004  	seg1 := segment.NewMockSegment(ctrl)
  1005  	seg2 := segment.NewMockSegment(ctrl)
  1006  	seg3 := segment.NewMockSegment(ctrl)
  1007  	t0Results := result.NewIndexBlockByVolumeType(t0)
  1008  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
  1009  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
  1010  	t1Results := result.NewIndexBlockByVolumeType(t1)
  1011  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
  1012  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
  1013  	bootstrapResults := result.IndexResults{
  1014  		t0: t0Results,
  1015  		t1: t1Results,
  1016  	}
  1017  
  1018  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
  1019  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
  1020  	require.NoError(t, idx.Bootstrap(bootstrapResults))
  1021  
  1022  	ctx := context.NewBackground()
  1023  	q := defaultQuery
  1024  	qOpts := index.QueryOptions{
  1025  		StartInclusive: t0,
  1026  		EndExclusive:   now.Add(time.Minute),
  1027  	}
  1028  	mockIterActive := index.NewMockQueryIterator(ctrl)
  1029  	mockIter := index.NewMockQueryIterator(ctrl)
  1030  	gomock.InOrder(
  1031  		mockPool.EXPECT().Get().Return(stubResult),
  1032  		bActive.EXPECT().QueryIter(ctx, q).Return(mockIterActive, nil),
  1033  		b0.EXPECT().QueryIter(ctx, q).Return(mockIter, nil),
  1034  		mockPool.EXPECT().Put(stubResult),
  1035  	)
  1036  
  1037  	mockIter.EXPECT().Done().Return(true)
  1038  	mockIterActive.EXPECT().Done().Return(true)
  1039  	mockIter.EXPECT().Close().Return(nil)
  1040  	mockIterActive.EXPECT().Close().Return(nil)
  1041  
  1042  	_, err = idx.Query(ctx, q, qOpts)
  1043  	require.NoError(t, err)
  1044  	ctx.BlockingClose()
  1045  }
  1046  
  1047  func TestNamespaceIndexBlockAggregateQuery(t *testing.T) {
  1048  	ctrl := xtest.NewController(t)
  1049  	defer ctrl.Finish()
  1050  
  1051  	query := idx.NewTermQuery([]byte("a"), []byte("b"))
  1052  	retention := 2 * time.Hour
  1053  	blockSize := time.Hour
  1054  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
  1055  	t0 := now.Truncate(blockSize)
  1056  	t1 := t0.Add(1 * blockSize)
  1057  	t2 := t1.Add(1 * blockSize)
  1058  	var nowLock sync.Mutex
  1059  	nowFn := func() time.Time {
  1060  		nowLock.Lock()
  1061  		defer nowLock.Unlock()
  1062  		return now.ToTime()
  1063  	}
  1064  	opts := DefaultTestOptions()
  1065  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
  1066  
  1067  	bActive := index.NewMockBlock(ctrl)
  1068  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1069  	bActive.EXPECT().Close().Return(nil)
  1070  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
  1071  	bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
  1072  	b0 := index.NewMockBlock(ctrl)
  1073  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1074  	b0.EXPECT().Close().Return(nil)
  1075  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
  1076  	b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
  1077  	b1 := index.NewMockBlock(ctrl)
  1078  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1079  	b1.EXPECT().Close().Return(nil)
  1080  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
  1081  	b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes()
  1082  	newBlockFn := func(
  1083  		ts xtime.UnixNano,
  1084  		md namespace.Metadata,
  1085  		opts index.BlockOptions,
  1086  		_ namespace.RuntimeOptionsManager,
  1087  		io index.Options,
  1088  	) (index.Block, error) {
  1089  		if opts.ActiveBlock {
  1090  			return bActive, nil
  1091  		}
  1092  		if ts.Equal(t0) {
  1093  			return b0, nil
  1094  		}
  1095  		if ts.Equal(t1) {
  1096  			return b1, nil
  1097  		}
  1098  		panic("should never get here")
  1099  	}
  1100  	md := testNamespaceMetadata(blockSize, retention)
  1101  	idx, err := newNamespaceIndexWithNewBlockFn(md,
  1102  		namespace.NewRuntimeOptionsManager(md.ID().String()),
  1103  		testShardSet, newBlockFn, opts)
  1104  	require.NoError(t, err)
  1105  
  1106  	defer func() {
  1107  		require.NoError(t, idx.Close())
  1108  	}()
  1109  
  1110  	seg1 := segment.NewMockSegment(ctrl)
  1111  	seg2 := segment.NewMockSegment(ctrl)
  1112  	seg3 := segment.NewMockSegment(ctrl)
  1113  	t0Results := result.NewIndexBlockByVolumeType(t0)
  1114  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
  1115  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
  1116  	t1Results := result.NewIndexBlockByVolumeType(t1)
  1117  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
  1118  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
  1119  	bootstrapResults := result.IndexResults{
  1120  		t0: t0Results,
  1121  		t1: t1Results,
  1122  	}
  1123  
  1124  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
  1125  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
  1126  	require.NoError(t, idx.Bootstrap(bootstrapResults))
  1127  
  1128  	for _, test := range []struct {
  1129  		name              string
  1130  		requireExhaustive bool
  1131  	}{
  1132  		{"allow non-exhaustive", false},
  1133  		{"require exhaustive", true},
  1134  	} {
  1135  		t.Run(test.name, func(t *testing.T) {
  1136  			// only queries as much as is needed (wrt to time)
  1137  			ctx := context.NewBackground()
  1138  
  1139  			// create initial span from a mock tracer and get ctx
  1140  			mtr := mocktracer.New()
  1141  			sp := mtr.StartSpan("root")
  1142  			ctx.SetGoContext(opentracing.ContextWithSpan(stdlibctx.Background(), sp))
  1143  
  1144  			q := index.Query{
  1145  				Query: query,
  1146  			}
  1147  			qOpts := index.QueryOptions{
  1148  				StartInclusive:    t0,
  1149  				EndExclusive:      now.Add(time.Minute),
  1150  				RequireExhaustive: test.requireExhaustive,
  1151  			}
  1152  			aggOpts := index.AggregationOptions{QueryOptions: qOpts}
  1153  
  1154  			mockIterActive := index.NewMockAggregateIterator(ctrl)
  1155  			bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil)
  1156  			mockIterActive.EXPECT().Done().Return(true)
  1157  			mockIterActive.EXPECT().Close().Return(nil)
  1158  			mockIter0 := index.NewMockAggregateIterator(ctrl)
  1159  			b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil)
  1160  			mockIter0.EXPECT().Done().Return(true)
  1161  			mockIter0.EXPECT().Close().Return(nil)
  1162  			result, err := idx.AggregateQuery(ctx, q, aggOpts)
  1163  			require.NoError(t, err)
  1164  			require.True(t, result.Exhaustive)
  1165  
  1166  			// queries multiple blocks if needed
  1167  			qOpts = index.QueryOptions{
  1168  				StartInclusive:    t0,
  1169  				EndExclusive:      t2.Add(time.Minute),
  1170  				RequireExhaustive: test.requireExhaustive,
  1171  			}
  1172  			aggOpts = index.AggregationOptions{QueryOptions: qOpts}
  1173  			bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil)
  1174  			mockIterActive.EXPECT().Done().Return(true)
  1175  			mockIterActive.EXPECT().Close().Return(nil)
  1176  			b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil)
  1177  			mockIter0.EXPECT().Done().Return(true)
  1178  			mockIter0.EXPECT().Close().Return(nil)
  1179  
  1180  			mockIter1 := index.NewMockAggregateIterator(ctrl)
  1181  			b1.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter1, nil)
  1182  			mockIter1.EXPECT().Done().Return(true)
  1183  			mockIter1.EXPECT().Close().Return(nil)
  1184  			result, err = idx.AggregateQuery(ctx, q, aggOpts)
  1185  			require.NoError(t, err)
  1186  			require.True(t, result.Exhaustive)
  1187  
  1188  			// stops querying once a block returns non-exhaustive
  1189  			qOpts = index.QueryOptions{
  1190  				StartInclusive:    t0,
  1191  				EndExclusive:      t0.Add(time.Minute),
  1192  				RequireExhaustive: test.requireExhaustive,
  1193  				DocsLimit:         1,
  1194  			}
  1195  			bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil)
  1196  			//nolint: dupl
  1197  			bActive.EXPECT().
  1198  				AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1199  				DoAndReturn(func(
  1200  					ctx context.Context,
  1201  					iter index.AggregateIterator,
  1202  					opts index.QueryOptions,
  1203  					results index.AggregateResults,
  1204  					deadline time.Time,
  1205  					logFields []opentracinglog.Field,
  1206  				) error {
  1207  					_, _ = results.AddFields([]index.AggregateResultsEntry{{
  1208  						Field: ident.StringID("A"),
  1209  						Terms: []ident.ID{ident.StringID("foo")},
  1210  					}, {
  1211  						Field: ident.StringID("B"),
  1212  						Terms: []ident.ID{ident.StringID("bar")},
  1213  					}})
  1214  					return nil
  1215  				})
  1216  			gomock.InOrder(
  1217  				mockIterActive.EXPECT().Done().Return(false),
  1218  				mockIterActive.EXPECT().Done().Return(true),
  1219  				mockIterActive.EXPECT().Close().Return(nil),
  1220  			)
  1221  			b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil)
  1222  			//nolint: dupl
  1223  			b0.EXPECT().
  1224  				AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1225  				DoAndReturn(func(
  1226  					ctx context.Context,
  1227  					iter index.AggregateIterator,
  1228  					opts index.QueryOptions,
  1229  					results index.AggregateResults,
  1230  					deadline time.Time,
  1231  					logFields []opentracinglog.Field,
  1232  				) error {
  1233  					_, _ = results.AddFields([]index.AggregateResultsEntry{{
  1234  						Field: ident.StringID("A"),
  1235  						Terms: []ident.ID{ident.StringID("foo")},
  1236  					}, {
  1237  						Field: ident.StringID("B"),
  1238  						Terms: []ident.ID{ident.StringID("bar")},
  1239  					}})
  1240  					return nil
  1241  				})
  1242  			gomock.InOrder(
  1243  				mockIter0.EXPECT().Done().Return(false),
  1244  				mockIter0.EXPECT().Done().Return(true),
  1245  				mockIter0.EXPECT().Close().Return(nil),
  1246  			)
  1247  			aggOpts = index.AggregationOptions{QueryOptions: qOpts}
  1248  			result, err = idx.AggregateQuery(ctx, q, aggOpts)
  1249  			if test.requireExhaustive {
  1250  				require.Error(t, err)
  1251  				require.False(t, xerrors.IsRetryableError(err))
  1252  			} else {
  1253  				require.NoError(t, err)
  1254  				require.False(t, result.Exhaustive)
  1255  			}
  1256  
  1257  			sp.Finish()
  1258  			spans := mtr.FinishedSpans()
  1259  			require.Len(t, spans, 9)
  1260  		})
  1261  	}
  1262  }
  1263  
  1264  func TestNamespaceIndexBlockAggregateQueryReleasingContext(t *testing.T) {
  1265  	ctrl := xtest.NewController(t)
  1266  	defer ctrl.Finish()
  1267  
  1268  	retention := 2 * time.Hour
  1269  	blockSize := time.Hour
  1270  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
  1271  	t0 := now.Truncate(blockSize)
  1272  	t1 := t0.Add(1 * blockSize)
  1273  	t2 := t1.Add(1 * blockSize)
  1274  	var nowLock sync.Mutex
  1275  	nowFn := func() time.Time {
  1276  		nowLock.Lock()
  1277  		defer nowLock.Unlock()
  1278  		return now.ToTime()
  1279  	}
  1280  	opts := DefaultTestOptions()
  1281  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
  1282  
  1283  	query := idx.NewTermQuery([]byte("a"), []byte("b"))
  1284  	bActive := index.NewMockBlock(ctrl)
  1285  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1286  	bActive.EXPECT().Close().Return(nil)
  1287  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
  1288  	bActive.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
  1289  	b0 := index.NewMockBlock(ctrl)
  1290  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1291  	b0.EXPECT().Close().Return(nil)
  1292  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
  1293  	b0.EXPECT().EndTime().Return(t0.Add(blockSize)).AnyTimes()
  1294  	b1 := index.NewMockBlock(ctrl)
  1295  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1296  	b1.EXPECT().Close().Return(nil)
  1297  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
  1298  	b1.EXPECT().EndTime().Return(t1.Add(blockSize)).AnyTimes()
  1299  	newBlockFn := func(
  1300  		ts xtime.UnixNano,
  1301  		md namespace.Metadata,
  1302  		opts index.BlockOptions,
  1303  		_ namespace.RuntimeOptionsManager,
  1304  		io index.Options,
  1305  	) (index.Block, error) {
  1306  		if opts.ActiveBlock {
  1307  			return bActive, nil
  1308  		}
  1309  		if ts.Equal(t0) {
  1310  			return b0, nil
  1311  		}
  1312  		if ts.Equal(t1) {
  1313  			return b1, nil
  1314  		}
  1315  		panic("should never get here")
  1316  	}
  1317  
  1318  	iopts := opts.IndexOptions()
  1319  	mockPool := index.NewMockAggregateResultsPool(ctrl)
  1320  	iopts = iopts.SetAggregateResultsPool(mockPool)
  1321  	stubResult := index.NewAggregateResults(ident.StringID("ns"),
  1322  		index.AggregateResultsOptions{}, iopts)
  1323  
  1324  	md := testNamespaceMetadata(blockSize, retention)
  1325  	idxIface, err := newNamespaceIndexWithNewBlockFn(md,
  1326  		namespace.NewRuntimeOptionsManager(md.ID().String()),
  1327  		testShardSet, newBlockFn, opts)
  1328  	require.NoError(t, err)
  1329  
  1330  	idx, ok := idxIface.(*nsIndex)
  1331  	require.True(t, ok)
  1332  	idx.aggregateResultsPool = mockPool
  1333  
  1334  	defer func() {
  1335  		require.NoError(t, idx.Close())
  1336  	}()
  1337  
  1338  	seg1 := segment.NewMockSegment(ctrl)
  1339  	seg2 := segment.NewMockSegment(ctrl)
  1340  	seg3 := segment.NewMockSegment(ctrl)
  1341  	t0Results := result.NewIndexBlockByVolumeType(t0)
  1342  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
  1343  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
  1344  	t1Results := result.NewIndexBlockByVolumeType(t1)
  1345  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
  1346  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
  1347  	bootstrapResults := result.IndexResults{
  1348  		t0: t0Results,
  1349  		t1: t1Results,
  1350  	}
  1351  
  1352  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
  1353  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
  1354  	require.NoError(t, idx.Bootstrap(bootstrapResults))
  1355  
  1356  	// only queries as much as is needed (wrt to time)
  1357  	ctx := context.NewBackground()
  1358  	q := index.Query{
  1359  		Query: query,
  1360  	}
  1361  	qOpts := index.QueryOptions{
  1362  		StartInclusive: t0,
  1363  		EndExclusive:   now.Add(time.Minute),
  1364  	}
  1365  	aggOpts := index.AggregationOptions{QueryOptions: qOpts}
  1366  
  1367  	mockIterActive := index.NewMockAggregateIterator(ctrl)
  1368  	mockIter := index.NewMockAggregateIterator(ctrl)
  1369  	gomock.InOrder(
  1370  		mockPool.EXPECT().Get().Return(stubResult),
  1371  		bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil),
  1372  		b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter, nil),
  1373  		mockPool.EXPECT().Put(stubResult),
  1374  	)
  1375  	mockIter.EXPECT().Done().Return(true)
  1376  	mockIterActive.EXPECT().Done().Return(true)
  1377  	mockIter.EXPECT().Close().Return(nil)
  1378  	mockIterActive.EXPECT().Close().Return(nil)
  1379  
  1380  	_, err = idx.AggregateQuery(ctx, q, aggOpts)
  1381  	require.NoError(t, err)
  1382  	ctx.BlockingClose()
  1383  }
  1384  
  1385  func TestNamespaceIndexBlockAggregateQueryAggPath(t *testing.T) {
  1386  	ctrl := xtest.NewController(t)
  1387  	defer ctrl.Finish()
  1388  
  1389  	queries := []idx.Query{idx.NewAllQuery(), idx.NewFieldQuery([]byte("field"))}
  1390  	retention := 2 * time.Hour
  1391  	blockSize := time.Hour
  1392  	now := xtime.Now().Truncate(blockSize).Add(10 * time.Minute)
  1393  	t0 := now.Truncate(blockSize)
  1394  	t1 := t0.Add(1 * blockSize)
  1395  	t2 := t1.Add(1 * blockSize)
  1396  	var nowLock sync.Mutex
  1397  	nowFn := func() time.Time {
  1398  		nowLock.Lock()
  1399  		defer nowLock.Unlock()
  1400  		return now.ToTime()
  1401  	}
  1402  	opts := DefaultTestOptions()
  1403  	opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn))
  1404  
  1405  	bActive := index.NewMockBlock(ctrl)
  1406  	bActive.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1407  	bActive.EXPECT().Close().Return(nil)
  1408  	bActive.EXPECT().StartTime().Return(t0).AnyTimes()
  1409  	bActive.EXPECT().EndTime().Return(t1).AnyTimes()
  1410  	b0 := index.NewMockBlock(ctrl)
  1411  	b0.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1412  	b0.EXPECT().Close().Return(nil)
  1413  	b0.EXPECT().StartTime().Return(t0).AnyTimes()
  1414  	b0.EXPECT().EndTime().Return(t1).AnyTimes()
  1415  	b1 := index.NewMockBlock(ctrl)
  1416  	b1.EXPECT().Stats(gomock.Any()).Return(nil).AnyTimes()
  1417  	b1.EXPECT().Close().Return(nil)
  1418  	b1.EXPECT().StartTime().Return(t1).AnyTimes()
  1419  	b1.EXPECT().EndTime().Return(t2).AnyTimes()
  1420  	newBlockFn := func(
  1421  		ts xtime.UnixNano,
  1422  		md namespace.Metadata,
  1423  		opts index.BlockOptions,
  1424  		_ namespace.RuntimeOptionsManager,
  1425  		io index.Options,
  1426  	) (index.Block, error) {
  1427  		if opts.ActiveBlock {
  1428  			return bActive, nil
  1429  		}
  1430  		if ts.Equal(t0) {
  1431  			return b0, nil
  1432  		}
  1433  		if ts.Equal(t1) {
  1434  			return b1, nil
  1435  		}
  1436  		panic("should never get here")
  1437  	}
  1438  	md := testNamespaceMetadata(blockSize, retention)
  1439  	idx, err := newNamespaceIndexWithNewBlockFn(md,
  1440  		namespace.NewRuntimeOptionsManager(md.ID().String()),
  1441  		testShardSet, newBlockFn, opts)
  1442  	require.NoError(t, err)
  1443  
  1444  	defer func() {
  1445  		require.NoError(t, idx.Close())
  1446  	}()
  1447  
  1448  	seg1 := segment.NewMockSegment(ctrl)
  1449  	seg2 := segment.NewMockSegment(ctrl)
  1450  	seg3 := segment.NewMockSegment(ctrl)
  1451  	t0Results := result.NewIndexBlockByVolumeType(t0)
  1452  	t0Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg1, false)},
  1453  		result.NewShardTimeRangesFromRange(t0, t1, 1, 2, 3)))
  1454  	t1Results := result.NewIndexBlockByVolumeType(t1)
  1455  	t1Results.SetBlock(idxpersist.DefaultIndexVolumeType, result.NewIndexBlock([]result.Segment{result.NewSegment(seg2, false), result.NewSegment(seg3, false)},
  1456  		result.NewShardTimeRangesFromRange(t1, t2, 1, 2, 3)))
  1457  	bootstrapResults := result.IndexResults{
  1458  		t0: t0Results,
  1459  		t1: t1Results,
  1460  	}
  1461  
  1462  	b0.EXPECT().AddResults(bootstrapResults[t0]).Return(nil)
  1463  	b1.EXPECT().AddResults(bootstrapResults[t1]).Return(nil)
  1464  	require.NoError(t, idx.Bootstrap(bootstrapResults))
  1465  
  1466  	// only queries as much as is needed (wrt to time)
  1467  	ctx := context.NewBackground()
  1468  
  1469  	qOpts := index.QueryOptions{
  1470  		StartInclusive: t0,
  1471  		EndExclusive:   now.Add(time.Minute),
  1472  	}
  1473  	aggOpts := index.AggregationOptions{QueryOptions: qOpts}
  1474  
  1475  	for _, test := range []struct {
  1476  		name              string
  1477  		requireExhaustive bool
  1478  	}{
  1479  		{"allow non-exhaustive", false},
  1480  		{"require exhaustive", true},
  1481  	} {
  1482  		t.Run(test.name, func(t *testing.T) {
  1483  			for _, query := range queries {
  1484  				q := index.Query{
  1485  					Query: query,
  1486  				}
  1487  				mockIterActive := index.NewMockAggregateIterator(ctrl)
  1488  				mockIterActive.EXPECT().Done().Return(true)
  1489  				mockIterActive.EXPECT().Close().Return(nil)
  1490  				bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil)
  1491  				mockIter0 := index.NewMockAggregateIterator(ctrl)
  1492  				mockIter0.EXPECT().Done().Return(true)
  1493  				mockIter0.EXPECT().Close().Return(nil)
  1494  				b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter0, nil)
  1495  				result, err := idx.AggregateQuery(ctx, q, aggOpts)
  1496  				require.NoError(t, err)
  1497  				require.True(t, result.Exhaustive)
  1498  
  1499  				// queries multiple blocks if needed
  1500  				qOpts = index.QueryOptions{
  1501  					StartInclusive:    t0,
  1502  					EndExclusive:      t2.Add(time.Minute),
  1503  					RequireExhaustive: test.requireExhaustive,
  1504  				}
  1505  				aggOpts = index.AggregationOptions{QueryOptions: qOpts}
  1506  
  1507  				mockIterActive.EXPECT().Done().Return(true)
  1508  				mockIterActive.EXPECT().Close().Return(nil)
  1509  				bActive.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIterActive, nil)
  1510  
  1511  				mockIter0.EXPECT().Done().Return(true)
  1512  				mockIter0.EXPECT().Close().Return(nil)
  1513  				b0.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter0, nil)
  1514  
  1515  				mockIter1 := index.NewMockAggregateIterator(ctrl)
  1516  				mockIter1.EXPECT().Done().Return(true)
  1517  				mockIter1.EXPECT().Close().Return(nil)
  1518  				b1.EXPECT().AggregateIter(ctx, gomock.Any()).Return(mockIter1, nil)
  1519  				result, err = idx.AggregateQuery(ctx, q, aggOpts)
  1520  				require.NoError(t, err)
  1521  				require.True(t, result.Exhaustive)
  1522  
  1523  				// stops querying once a block returns non-exhaustive
  1524  				qOpts = index.QueryOptions{
  1525  					StartInclusive:    t0,
  1526  					EndExclusive:      t0.Add(time.Minute),
  1527  					RequireExhaustive: test.requireExhaustive,
  1528  					DocsLimit:         1,
  1529  				}
  1530  				bActive.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIterActive, nil)
  1531  				//nolint: dupl
  1532  				bActive.EXPECT().
  1533  					AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1534  					DoAndReturn(func(
  1535  						ctx context.Context,
  1536  						iter index.AggregateIterator,
  1537  						opts index.QueryOptions,
  1538  						results index.AggregateResults,
  1539  						deadline time.Time,
  1540  						logFields []opentracinglog.Field,
  1541  					) error {
  1542  						_, _ = results.AddFields([]index.AggregateResultsEntry{{
  1543  							Field: ident.StringID("A"),
  1544  							Terms: []ident.ID{ident.StringID("foo")},
  1545  						}, {
  1546  							Field: ident.StringID("B"),
  1547  							Terms: []ident.ID{ident.StringID("bar")},
  1548  						}})
  1549  						return nil
  1550  					})
  1551  				gomock.InOrder(
  1552  					mockIterActive.EXPECT().Done().Return(false),
  1553  					mockIterActive.EXPECT().Done().Return(true),
  1554  					mockIterActive.EXPECT().Close().Return(nil),
  1555  				)
  1556  				b0.EXPECT().AggregateIter(gomock.Any(), gomock.Any()).Return(mockIter0, nil)
  1557  				//nolint: dupl
  1558  				b0.EXPECT().
  1559  					AggregateWithIter(gomock.Any(), mockIter0, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
  1560  					DoAndReturn(func(
  1561  						ctx context.Context,
  1562  						iter index.AggregateIterator,
  1563  						opts index.QueryOptions,
  1564  						results index.AggregateResults,
  1565  						deadline time.Time,
  1566  						logFields []opentracinglog.Field,
  1567  					) error {
  1568  						_, _ = results.AddFields([]index.AggregateResultsEntry{{
  1569  							Field: ident.StringID("A"),
  1570  							Terms: []ident.ID{ident.StringID("foo")},
  1571  						}, {
  1572  							Field: ident.StringID("B"),
  1573  							Terms: []ident.ID{ident.StringID("bar")},
  1574  						}})
  1575  						return nil
  1576  					})
  1577  				gomock.InOrder(
  1578  					mockIter0.EXPECT().Done().Return(false),
  1579  					mockIter0.EXPECT().Done().Return(true),
  1580  					mockIter0.EXPECT().Close().Return(nil),
  1581  				)
  1582  				aggOpts = index.AggregationOptions{QueryOptions: qOpts}
  1583  				result, err = idx.AggregateQuery(ctx, q, aggOpts)
  1584  				if test.requireExhaustive {
  1585  					require.Error(t, err)
  1586  					require.False(t, xerrors.IsRetryableError(err))
  1587  				} else {
  1588  					require.NoError(t, err)
  1589  					require.False(t, result.Exhaustive)
  1590  				}
  1591  			}
  1592  		})
  1593  	}
  1594  }
  1595  
  1596  func mockWriteBatch(t *testing.T,
  1597  	now *xtime.UnixNano,
  1598  	lifecycle *doc.MockOnIndexSeries,
  1599  	block *index.MockBlock,
  1600  	tag *ident.Tag,
  1601  ) {
  1602  	block.EXPECT().
  1603  		WriteBatch(gomock.Any()).
  1604  		Return(index.WriteBatchResult{}, nil).
  1605  		Do(func(batch *index.WriteBatch) {
  1606  			docs := batch.PendingDocs()
  1607  			require.Equal(t, 1, len(docs))
  1608  			require.Equal(t, doc.Metadata{
  1609  				ID:     id.Bytes(),
  1610  				Fields: doc.Fields{{Name: tag.Name.Bytes(), Value: tag.Value.Bytes()}},
  1611  			}, docs[0])
  1612  			entries := batch.PendingEntries()
  1613  			require.Equal(t, 1, len(entries))
  1614  			require.True(t, entries[0].Timestamp.Equal(*now))
  1615  			require.True(t, entries[0].OnIndexSeries == lifecycle) // Just ptr equality
  1616  		})
  1617  }
  1618  
  1619  func mockQueryWithIter(t *testing.T,
  1620  	iter *index.MockQueryIterator,
  1621  	block *index.MockBlock,
  1622  	q index.Query,
  1623  	qOpts index.QueryOptions,
  1624  	resultLock *sync.Mutex,
  1625  	docsToAdd []doc.Document,
  1626  ) {
  1627  	block.EXPECT().QueryIter(gomock.Any(), q).Return(iter, nil)
  1628  	block.EXPECT().QueryWithIter(gomock.Any(), qOpts, iter, gomock.Any(), gomock.Any(), gomock.Any()).
  1629  		DoAndReturn(func(
  1630  			ctx context.Context,
  1631  			opts index.QueryOptions,
  1632  			iter index.QueryIterator,
  1633  			r index.QueryResults,
  1634  			deadline time.Time,
  1635  			logFields []opentracinglog.Field,
  1636  		) error {
  1637  			resultLock.Lock()
  1638  			defer resultLock.Unlock()
  1639  			_, _, err := r.AddDocuments(docsToAdd)
  1640  			require.NoError(t, err)
  1641  			return nil
  1642  		})
  1643  	gomock.InOrder(
  1644  		iter.EXPECT().Done().Return(false),
  1645  		iter.EXPECT().Done().Return(true),
  1646  		iter.EXPECT().Close().Return(nil),
  1647  	)
  1648  }