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

     1  // Copyright (c) 2017 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  	"crypto/rand"
    25  	"fmt"
    26  	"sort"
    27  	"strconv"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/dbnode/digest"
    32  	"github.com/m3db/m3/src/dbnode/generated/proto/pagetoken"
    33  	"github.com/m3db/m3/src/dbnode/persist"
    34  	"github.com/m3db/m3/src/dbnode/persist/fs"
    35  	"github.com/m3db/m3/src/dbnode/storage/block"
    36  	"github.com/m3db/m3/src/dbnode/storage/series"
    37  	"github.com/m3db/m3/src/x/checked"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	xtest "github.com/m3db/m3/src/x/test"
    40  	xtime "github.com/m3db/m3/src/x/time"
    41  
    42  	"github.com/gogo/protobuf/proto"
    43  	"github.com/golang/mock/gomock"
    44  	"github.com/stretchr/testify/assert"
    45  	"github.com/stretchr/testify/require"
    46  )
    47  
    48  func TestShardFetchBlocksMetadataV2WithSeriesCachePolicyCacheAll(t *testing.T) {
    49  	ctrl := xtest.NewController(t)
    50  	defer ctrl.Finish()
    51  
    52  	opts := DefaultTestOptions().SetSeriesCachePolicy(series.CacheAll)
    53  	ctx := opts.ContextPool().Get()
    54  	defer ctx.Close()
    55  
    56  	shard := testDatabaseShard(t, opts)
    57  	defer shard.Close()
    58  	start := xtime.Now()
    59  	end := start.Add(defaultTestRetentionOpts.BlockSize())
    60  
    61  	fetchLimit := int64(5)
    62  	startCursor := int64(2)
    63  
    64  	var ids []ident.ID
    65  	fetchOpts := block.FetchBlocksMetadataOptions{
    66  		IncludeSizes:     true,
    67  		IncludeChecksums: true,
    68  		IncludeLastRead:  true,
    69  	}
    70  	seriesFetchOpts := series.FetchBlocksMetadataOptions{
    71  		FetchBlocksMetadataOptions: fetchOpts,
    72  	}
    73  	lastRead := xtime.Now().Add(-time.Minute)
    74  	for i := int64(0); i < 10; i++ {
    75  		id := ident.StringID(fmt.Sprintf("foo.%d", i))
    76  		tags := ident.NewTags(
    77  			ident.StringTag("aaa", "bbb"),
    78  			ident.StringTag("ccc", "ddd"),
    79  		)
    80  		tagsIter := ident.NewTagsIterator(tags)
    81  		series := addMockSeries(ctrl, shard, id, tags, uint64(i))
    82  		if i == startCursor {
    83  			series.EXPECT().
    84  				FetchBlocksMetadata(gomock.Not(nil), start, end, seriesFetchOpts).
    85  				Return(block.NewFetchBlocksMetadataResult(id, tagsIter,
    86  					block.NewFetchBlockMetadataResults()), nil)
    87  		} else if i > startCursor && i <= startCursor+fetchLimit {
    88  			ids = append(ids, id)
    89  			blocks := block.NewFetchBlockMetadataResults()
    90  			at := start.Add(time.Duration(i))
    91  			blocks.Add(block.NewFetchBlockMetadataResult(at, 0, nil, lastRead, nil))
    92  			series.EXPECT().
    93  				FetchBlocksMetadata(gomock.Not(nil), start, end, seriesFetchOpts).
    94  				Return(block.NewFetchBlocksMetadataResult(id, tagsIter,
    95  					blocks), nil)
    96  		}
    97  	}
    98  
    99  	currPageToken, err := proto.Marshal(&pagetoken.PageToken{
   100  		ActiveSeriesPhase: &pagetoken.PageToken_ActiveSeriesPhase{
   101  			IndexCursor: startCursor,
   102  		},
   103  	})
   104  	require.NoError(t, err)
   105  
   106  	res, nextPageToken, err := shard.FetchBlocksMetadataV2(ctx, start, end,
   107  		fetchLimit, currPageToken, fetchOpts)
   108  	require.NoError(t, err)
   109  	require.Equal(t, len(ids), len(res.Results()))
   110  
   111  	pageToken := new(pagetoken.PageToken)
   112  	err = proto.Unmarshal(nextPageToken, pageToken)
   113  	require.NoError(t, err)
   114  
   115  	require.NotNil(t, pageToken.GetActiveSeriesPhase())
   116  	require.Equal(t, int64(8), pageToken.GetActiveSeriesPhase().IndexCursor)
   117  
   118  	for i := 0; i < len(res.Results()); i++ {
   119  		require.Equal(t, ids[i], res.Results()[i].ID)
   120  	}
   121  }
   122  
   123  type fetchBlockMetadataResultByStart []block.FetchBlockMetadataResult
   124  
   125  func (b fetchBlockMetadataResultByStart) Len() int      { return len(b) }
   126  func (b fetchBlockMetadataResultByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   127  func (b fetchBlockMetadataResultByStart) Less(i, j int) bool {
   128  	return b[i].Start.Before(b[j].Start)
   129  }
   130  
   131  func TestShardFetchBlocksMetadataV2WithSeriesCachePolicyNotCacheAll(t *testing.T) {
   132  	ctrl := xtest.NewController(t)
   133  	defer ctrl.Finish()
   134  
   135  	opts := DefaultTestOptions().SetSeriesCachePolicy(series.CacheRecentlyRead)
   136  	ctx := opts.ContextPool().Get()
   137  	defer ctx.Close()
   138  
   139  	fsOpts := opts.CommitLogOptions().FilesystemOptions()
   140  
   141  	shard := testDatabaseShard(t, opts)
   142  	defer shard.Close()
   143  
   144  	ropts := defaultTestRetentionOpts
   145  	blockSize := ropts.BlockSize()
   146  	retentionPeriod := ropts.RetentionPeriod()
   147  	now := xtime.Now()
   148  	mostRecentBlockStart := now.Truncate(blockSize)
   149  	start := mostRecentBlockStart.Add(-retentionPeriod)
   150  	end := mostRecentBlockStart.Add(blockSize)
   151  
   152  	// Choose num of series to return from different phases
   153  	numActiveSeries := 10
   154  	numFlushedSeries := 25
   155  
   156  	// Choose a fetch limit that spans multiple pages and a partial page
   157  	fetchLimit := int64(4)
   158  
   159  	fetchOpts := block.FetchBlocksMetadataOptions{
   160  		IncludeSizes:     true,
   161  		IncludeChecksums: true,
   162  		IncludeLastRead:  true,
   163  	}
   164  
   165  	// Populate the mocks and filesets and collect what the expected
   166  	// results will be
   167  	expected := map[string]fetchBlockMetadataResultByStart{}
   168  
   169  	// Write flushed series
   170  	for at := start; at.Before(mostRecentBlockStart); at = at.Add(blockSize) {
   171  		writer, err := fs.NewWriter(fsOpts)
   172  		require.NoError(t, err)
   173  
   174  		writerOpts := fs.DataWriterOpenOptions{
   175  			Identifier: fs.FileSetFileIdentifier{
   176  				Namespace:  shard.namespace.ID(),
   177  				Shard:      shard.shard,
   178  				BlockStart: at,
   179  			},
   180  			BlockSize: blockSize,
   181  		}
   182  		err = writer.Open(writerOpts)
   183  		require.NoError(t, err)
   184  
   185  		for i := 0; i < numFlushedSeries; i++ {
   186  			idxBlock := time.Duration(at-start) / blockSize
   187  			if (idxBlock%2 == 0 && i%2 == 0) || (idxBlock%2 != 0 && i%2 != 0) {
   188  				continue // Every other block skip the evens and odds
   189  			}
   190  
   191  			id := ident.StringID(fmt.Sprintf("series+instance=%d", i))
   192  			data := make([]byte, 8)
   193  			_, err = rand.Read(data)
   194  			require.NoError(t, err)
   195  
   196  			checksum := digest.Checksum(data)
   197  
   198  			bytes := checked.NewBytes(data, nil)
   199  			bytes.IncRef()
   200  			meta := persist.NewMetadataFromIDAndTags(id, ident.Tags{},
   201  				persist.MetadataOptions{})
   202  			err = writer.Write(meta, bytes, checksum)
   203  			require.NoError(t, err)
   204  
   205  			blockMetadataResult := block.NewFetchBlockMetadataResult(at,
   206  				int64(len(data)), &checksum, 0, nil)
   207  			expected[id.String()] = append(expected[id.String()], blockMetadataResult)
   208  		}
   209  
   210  		err = writer.Close()
   211  		require.NoError(t, err)
   212  	}
   213  
   214  	// Add mock active series
   215  	seriesFetchOpts := series.FetchBlocksMetadataOptions{
   216  		FetchBlocksMetadataOptions: fetchOpts,
   217  	}
   218  	lastRead := xtime.Now().Add(-time.Minute)
   219  	for i := 0; i < numActiveSeries; i++ {
   220  		id := ident.StringID(fmt.Sprintf("series+instance=%d", i))
   221  		tags := ident.NewTags(
   222  			ident.StringTag("instance", strconv.Itoa(i)),
   223  		)
   224  		tagsIter := ident.NewTagsIterator(tags)
   225  		series := addMockSeries(ctrl, shard, id, tags, uint64(i))
   226  		blocks := block.NewFetchBlockMetadataResults()
   227  		at := mostRecentBlockStart
   228  		blockMetadataResult := block.NewFetchBlockMetadataResult(at, 0, nil, lastRead, nil)
   229  		blocks.Add(blockMetadataResult)
   230  		series.EXPECT().
   231  			FetchBlocksMetadata(gomock.Not(nil), start, end, seriesFetchOpts).
   232  			Return(block.NewFetchBlocksMetadataResult(id, tagsIter, blocks), nil)
   233  
   234  		// Add to the expected blocks result
   235  		expected[id.String()] = append(expected[id.String()], blockMetadataResult)
   236  	}
   237  
   238  	// Iterate the actual results
   239  	actual := map[string]fetchBlockMetadataResultByStart{}
   240  
   241  	var (
   242  		currPageToken PageToken
   243  		first         = true
   244  	)
   245  	for {
   246  		if !first && currPageToken == nil {
   247  			break // Reached end of iteration
   248  		}
   249  
   250  		first = false
   251  		res, nextPageToken, err := shard.FetchBlocksMetadataV2(ctx, start, end,
   252  			fetchLimit, currPageToken, fetchOpts)
   253  		require.NoError(t, err)
   254  
   255  		currPageToken = nextPageToken
   256  
   257  		for _, elem := range res.Results() {
   258  			for _, r := range elem.Blocks.Results() {
   259  				actual[elem.ID.String()] = append(actual[elem.ID.String()], r)
   260  			}
   261  		}
   262  	}
   263  
   264  	// Sort the results
   265  	for key := range expected {
   266  		sort.Sort(expected[key])
   267  	}
   268  	for key := range actual {
   269  		sort.Sort(actual[key])
   270  	}
   271  
   272  	// Evaluate results
   273  	require.Equal(t, len(expected), len(actual))
   274  
   275  	for id, expectedResults := range expected {
   276  		actualResults, ok := actual[id]
   277  		if !ok {
   278  			require.FailNow(t, fmt.Sprintf("id %s missing from actual results", id))
   279  		}
   280  
   281  		require.Equal(t, len(expectedResults), len(actualResults))
   282  
   283  		for i, expectedBlock := range expectedResults {
   284  			actualBlock := actualResults[i]
   285  
   286  			assert.True(t, expectedBlock.Start.Equal(actualBlock.Start))
   287  			assert.Equal(t, expectedBlock.Size, actualBlock.Size)
   288  			if expectedBlock.Checksum == nil {
   289  				assert.Nil(t, actualBlock.Checksum)
   290  			} else if actualBlock.Checksum == nil {
   291  				assert.Fail(t, fmt.Sprintf("expected checksum but no actual checksum"))
   292  			} else {
   293  				assert.Equal(t, *expectedBlock.Checksum, *actualBlock.Checksum)
   294  			}
   295  			assert.True(t, expectedBlock.LastRead.Equal(actualBlock.LastRead))
   296  			assert.Equal(t, expectedBlock.Err, actualBlock.Err)
   297  		}
   298  	}
   299  }