github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/fs_merge_with_mem_test.go (about) 1 // Copyright (c) 2019 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 "errors" 25 "testing" 26 27 "github.com/m3db/m3/src/dbnode/namespace" 28 "github.com/m3db/m3/src/dbnode/storage/block" 29 "github.com/m3db/m3/src/dbnode/storage/series" 30 "github.com/m3db/m3/src/dbnode/x/xio" 31 "github.com/m3db/m3/src/m3ninx/doc" 32 "github.com/m3db/m3/src/x/context" 33 "github.com/m3db/m3/src/x/ident" 34 xtest "github.com/m3db/m3/src/x/test" 35 xtime "github.com/m3db/m3/src/x/time" 36 37 "github.com/golang/mock/gomock" 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 ) 41 42 type dirtyData struct { 43 id ident.ID 44 start xtime.UnixNano 45 } 46 47 func TestRead(t *testing.T) { 48 ctrl := xtest.NewController(t) 49 defer ctrl.Finish() 50 51 shard := NewMockdatabaseShard(ctrl) 52 retriever := series.NewMockQueryableBlockRetriever(ctrl) 53 version := 0 54 ctx := context.NewBackground() 55 nsCtx := namespace.Context{} 56 result := block.FetchBlockResult{ 57 Blocks: []xio.BlockReader{{}}, 58 } 59 retriever.EXPECT().RetrievableBlockColdVersion(gomock.Any()).Return(version, nil).AnyTimes() 60 61 dirtySeries := newDirtySeriesMap() 62 dirtySeriesToWrite := make(map[xtime.UnixNano]*idList) 63 64 data := []dirtyData{ 65 {start: 0, id: ident.StringID("id0")}, 66 {start: 0, id: ident.StringID("id1")}, 67 {start: 1, id: ident.StringID("id2")}, 68 {start: 1, id: ident.StringID("id3")}, 69 {start: 1, id: ident.StringID("id4")}, 70 {start: 2, id: ident.StringID("id5")}, 71 {start: 3, id: ident.StringID("id6")}, 72 {start: 3, id: ident.StringID("id7")}, 73 {start: 4, id: ident.StringID("id8")}, 74 } 75 76 // Populate bookkeeping data structures with above test data. 77 for _, d := range data { 78 addDirtySeries(dirtySeries, dirtySeriesToWrite, d.id, d.start) 79 shard.EXPECT(). 80 FetchBlocksForColdFlush(gomock.Any(), d.id, d.start, version+1, nsCtx). 81 Return(result, nil) 82 } 83 84 mergeWith := newFSMergeWithMem(shard, retriever, dirtySeries, dirtySeriesToWrite) 85 86 for _, d := range data { 87 require.True(t, dirtySeries.Contains(idAndBlockStart{ 88 blockStart: d.start, 89 id: d.id.Bytes(), 90 })) 91 beforeLen := dirtySeriesToWrite[d.start].Len() 92 res, exists, err := mergeWith.Read(ctx, d.id, d.start, nsCtx) 93 require.NoError(t, err) 94 assert.True(t, exists) 95 assert.Equal(t, result.Blocks, res) 96 // Assert that the Read call removes the element from the "to write" 97 // list. 98 assert.Equal(t, beforeLen-1, dirtySeriesToWrite[d.start].Len()) 99 } 100 101 // Test Read with non-existent dirty block/series. 102 res, exists, err := mergeWith.Read(ctx, ident.StringID("not-present"), 10, nsCtx) 103 assert.Nil(t, res) 104 assert.False(t, exists) 105 assert.NoError(t, err) 106 107 // Test Read with error on fetch. 108 badFetchID := ident.StringID("bad-fetch") 109 addDirtySeries(dirtySeries, dirtySeriesToWrite, badFetchID, 11) 110 shard.EXPECT(). 111 FetchBlocksForColdFlush(gomock.Any(), badFetchID, gomock.Any(), version+1, nsCtx). 112 Return(block.FetchBlockResult{}, errors.New("fetch error")) 113 res, exists, err = mergeWith.Read(ctx, badFetchID, 11, nsCtx) 114 assert.Nil(t, res) 115 assert.False(t, exists) 116 assert.Error(t, err) 117 118 // Test Read with no data on fetch. 119 emptyDataID := ident.StringID("empty-data") 120 addDirtySeries(dirtySeries, dirtySeriesToWrite, emptyDataID, 12) 121 shard.EXPECT(). 122 FetchBlocksForColdFlush(gomock.Any(), emptyDataID, gomock.Any(), version+1, nsCtx). 123 Return(block.FetchBlockResult{}, nil) 124 res, exists, err = mergeWith.Read(ctx, emptyDataID, 12, nsCtx) 125 assert.Nil(t, res) 126 assert.False(t, exists) 127 assert.NoError(t, err) 128 } 129 130 func TestForEachRemaining(t *testing.T) { 131 ctrl := xtest.NewController(t) 132 defer ctrl.Finish() 133 134 shard := NewMockdatabaseShard(ctrl) 135 retriever := series.NewMockQueryableBlockRetriever(ctrl) 136 version := 0 137 ctx := context.NewBackground() 138 nsCtx := namespace.Context{} 139 result := block.FetchBlockResult{ 140 Blocks: []xio.BlockReader{{}}, 141 } 142 retriever.EXPECT().RetrievableBlockColdVersion(gomock.Any()).Return(version, nil).AnyTimes() 143 144 dirtySeries := newDirtySeriesMap() 145 dirtySeriesToWrite := make(map[xtime.UnixNano]*idList) 146 147 id0 := ident.StringID("id0") 148 id1 := ident.StringID("id1") 149 id2 := ident.StringID("id2") 150 id3 := ident.StringID("id3") 151 id4 := ident.StringID("id4") 152 id5 := ident.StringID("id5") 153 id6 := ident.StringID("id6") 154 id7 := ident.StringID("id7") 155 id8 := ident.StringID("id8") 156 data := []dirtyData{ 157 {start: 0, id: id0}, 158 {start: 0, id: id1}, 159 {start: 1, id: id2}, 160 {start: 1, id: id3}, 161 {start: 1, id: id4}, 162 {start: 2, id: id5}, 163 {start: 3, id: id6}, 164 {start: 3, id: id7}, 165 {start: 4, id: id8}, 166 } 167 168 // Populate bookkeeping data structures with above test data. 169 for _, d := range data { 170 addDirtySeries(dirtySeries, dirtySeriesToWrite, d.id, d.start) 171 } 172 173 mergeWith := newFSMergeWithMem(shard, retriever, dirtySeries, dirtySeriesToWrite) 174 175 var forEachCalls []doc.Metadata 176 shard.EXPECT(). 177 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id0"), 178 xtime.UnixNano(0), version+1, gomock.Any()). 179 Return(result, nil) 180 shard.EXPECT(). 181 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id1"), 182 xtime.UnixNano(0), version+1, gomock.Any()). 183 Return(result, nil) 184 err := mergeWith.ForEachRemaining(ctx, 0, 185 func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error { 186 forEachCalls = append(forEachCalls, seriesMetadata) 187 return nil 188 }, nsCtx) 189 require.NoError(t, err) 190 require.Len(t, forEachCalls, 2) 191 assert.Equal(t, id0.Bytes(), forEachCalls[0].ID) 192 assert.Equal(t, id1.Bytes(), forEachCalls[1].ID) 193 194 // Reset expected calls. 195 forEachCalls = forEachCalls[:0] 196 // Read id3 at block start 1, so id2 and id4 should be remaining for block 197 // start 1. 198 shard.EXPECT(). 199 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id3"), 200 xtime.UnixNano(1), version+1, nsCtx). 201 Return(result, nil) 202 res, exists, err := mergeWith.Read(ctx, id3, 1, nsCtx) 203 require.NoError(t, err) 204 assert.True(t, exists) 205 assert.Equal(t, result.Blocks, res) 206 shard.EXPECT(). 207 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id2"), 208 xtime.UnixNano(1), version+1, gomock.Any()). 209 Return(result, nil) 210 shard.EXPECT(). 211 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id4"), 212 xtime.UnixNano(1), version+1, gomock.Any()). 213 Return(result, nil) 214 err = mergeWith.ForEachRemaining(ctx, 1, func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error { 215 forEachCalls = append(forEachCalls, seriesMetadata) 216 return nil 217 }, nsCtx) 218 require.NoError(t, err) 219 require.Len(t, forEachCalls, 2) 220 assert.Equal(t, id2.Bytes(), forEachCalls[0].ID) 221 assert.Equal(t, id4.Bytes(), forEachCalls[1].ID) 222 223 shard.EXPECT(). 224 FetchBlocksForColdFlush(gomock.Any(), ident.NewIDMatcher("id8"), 225 xtime.UnixNano(4), version+1, gomock.Any()). 226 Return(result, nil) 227 228 // Test call with bad function execution. 229 err = mergeWith.ForEachRemaining(ctx, 4, func(seriesMetadata doc.Metadata, result block.FetchBlockResult) error { 230 return errors.New("bad") 231 }, nsCtx) 232 assert.Error(t, err) 233 } 234 235 func addDirtySeries( 236 dirtySeries *dirtySeriesMap, 237 dirtySeriesToWrite map[xtime.UnixNano]*idList, 238 id ident.ID, 239 start xtime.UnixNano, 240 ) { 241 seriesList := dirtySeriesToWrite[start] 242 if seriesList == nil { 243 seriesList = newIDList(nil) 244 dirtySeriesToWrite[start] = seriesList 245 } 246 element := seriesList.PushBack(doc.Metadata{ID: id.Bytes()}) 247 248 dirtySeries.Set(idAndBlockStart{blockStart: start, id: id.Bytes()}, element) 249 }