github.com/m3db/m3@v1.5.0/src/dbnode/network/server/tchannelthrift/node/fetch_result_iter_test.go (about)

     1  // Copyright (c) 2021 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 node
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/require"
    31  	"github.com/uber-go/tally"
    32  
    33  	"github.com/m3db/m3/src/dbnode/storage"
    34  	"github.com/m3db/m3/src/dbnode/storage/index"
    35  	"github.com/m3db/m3/src/dbnode/storage/limits/permits"
    36  	"github.com/m3db/m3/src/dbnode/storage/series"
    37  	"github.com/m3db/m3/src/dbnode/x/xio"
    38  	"github.com/m3db/m3/src/m3ninx/doc"
    39  	"github.com/m3db/m3/src/x/context"
    40  	"github.com/m3db/m3/src/x/ident"
    41  	"github.com/m3db/m3/src/x/instrument"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  )
    44  
    45  func TestFetchResultIterTest(t *testing.T) {
    46  	mocks := gomock.NewController(t)
    47  	defer mocks.Finish()
    48  
    49  	ctx, nsID, resMap, start, end, db := setup(mocks)
    50  	blockPermits := &fakePermits{available: 5, quotaPerPermit: 5}
    51  	iter := newFetchTaggedResultsIter(fetchTaggedResultsIterOpts{
    52  		queryResult: index.QueryResult{
    53  			Results: resMap,
    54  		},
    55  		queryOpts: index.QueryOptions{
    56  			StartInclusive: start,
    57  			EndExclusive:   end,
    58  		},
    59  		fetchData:       true,
    60  		db:              db,
    61  		nsID:            nsID,
    62  		blockPermits:    blockPermits,
    63  		instrumentClose: func(err error) {},
    64  	})
    65  	total := 0
    66  	for iter.Next(ctx) {
    67  		total++
    68  		require.NotNil(t, iter.Current())
    69  		require.Len(t, iter.Current().(*idResult).blockReaders, 10)
    70  	}
    71  	require.NoError(t, iter.Err())
    72  	iter.Close(nil)
    73  
    74  	require.Equal(t, 10, total)
    75  	// 20 permits are not acquired because the accounting is not 100% accurate. permits are not acquired until
    76  	// after the block is processed, so a block might be eagerly processed and then permit acquisition fails.
    77  	require.Equal(t, 19, blockPermits.acquired)
    78  	require.Equal(t, 19, blockPermits.released)
    79  	require.Equal(t, 1, blockPermits.closed)
    80  }
    81  
    82  func TestFetchResultIterTestNoReleaseWithoutAcquire(t *testing.T) {
    83  	blockPermits := &fakePermits{available: 10, quotaPerPermit: 1000}
    84  	emptyMap := index.NewQueryResults(ident.StringID("testNs"), index.QueryResultsOptions{}, testIndexOptions)
    85  	iter := newFetchTaggedResultsIter(fetchTaggedResultsIterOpts{
    86  		queryResult: index.QueryResult{
    87  			Results: emptyMap,
    88  		},
    89  		blockPermits:    blockPermits,
    90  		instrumentClose: func(err error) {},
    91  	})
    92  	ctx := context.NewBackground()
    93  	for iter.Next(ctx) {
    94  	}
    95  	require.NoError(t, iter.Err())
    96  	iter.Close(nil)
    97  
    98  	require.Equal(t, 0, blockPermits.acquired)
    99  	require.Equal(t, 0, blockPermits.released)
   100  	require.Equal(t, 1, blockPermits.closed)
   101  }
   102  
   103  func requireSeriesBlockMetric(t *testing.T, scope tally.TestScope) {
   104  	values, ok := scope.Snapshot().Histograms()["series-blocks+"]
   105  	require.True(t, ok)
   106  
   107  	sum := 0
   108  	for _, count := range values.Values() {
   109  		sum += int(count)
   110  	}
   111  	require.Equal(t, 1, sum)
   112  }
   113  
   114  func setup(mocks *gomock.Controller) (
   115  	context.Context, ident.ID, index.QueryResults,
   116  	xtime.UnixNano, xtime.UnixNano, *storage.Mockdatabase,
   117  ) {
   118  	ctx := context.NewBackground()
   119  	nsID := ident.StringID("testNs")
   120  
   121  	resMap := index.NewQueryResults(nsID,
   122  		index.QueryResultsOptions{}, testIndexOptions)
   123  	start := xtime.Now()
   124  	end := start.Add(24 * time.Hour)
   125  	db := storage.NewMockdatabase(mocks)
   126  
   127  	// 10 series IDs
   128  	for i := 0; i < 10; i++ {
   129  		id := ident.StringID(fmt.Sprintf("seriesId_%d", i))
   130  		var blockReaders [][]xio.BlockReader
   131  		// 10 block readers per series
   132  		for j := 0; j < 10; j++ {
   133  			blockReaders = append(blockReaders, []xio.BlockReader{})
   134  		}
   135  		db.EXPECT().ReadEncoded(ctx, nsID, id, start, end).Return(&series.FakeBlockReaderIter{
   136  			Readers: blockReaders,
   137  		}, nil)
   138  		resMap.Map().Set(id.Bytes(), doc.Document{})
   139  	}
   140  	return ctx, nsID, resMap, start, end, db
   141  }
   142  
   143  type fakePermits struct {
   144  	acquired       int
   145  	released       int
   146  	available      int
   147  	quotaPerPermit int64
   148  	closed         int
   149  }
   150  
   151  func (p *fakePermits) Acquire(_ context.Context) (permits.AcquireResult, error) {
   152  	if p.available == 0 {
   153  		return permits.AcquireResult{}, errors.New("available should never be 0")
   154  	}
   155  	p.available--
   156  	p.acquired++
   157  	return permits.AcquireResult{
   158  		Permit: permits.NewPermit(p.quotaPerPermit, instrument.NewOptions()),
   159  	}, nil
   160  }
   161  
   162  func (p *fakePermits) TryAcquire(_ context.Context) (permits.Permit, error) {
   163  	if p.available == 0 {
   164  		return nil, nil
   165  	}
   166  	p.available--
   167  	p.acquired++
   168  	return permits.NewPermit(p.quotaPerPermit, instrument.NewOptions()), nil
   169  }
   170  
   171  func (p *fakePermits) Release(_ permits.Permit) {
   172  	p.released++
   173  	p.available++
   174  }
   175  
   176  func (p *fakePermits) Close() {
   177  	p.closed++
   178  }