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 }