github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index/mutable_segments_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 index 22 23 import ( 24 "fmt" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/dbnode/namespace" 29 "github.com/m3db/m3/src/m3ninx/doc" 30 "github.com/m3db/m3/src/m3ninx/search" 31 "github.com/m3db/m3/src/m3ninx/search/query" 32 "github.com/m3db/m3/src/x/instrument" 33 xsync "github.com/m3db/m3/src/x/sync" 34 xtest "github.com/m3db/m3/src/x/test" 35 xtime "github.com/m3db/m3/src/x/time" 36 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 "go.uber.org/zap" 40 ) 41 42 type testMutableSegmentsResult struct { 43 logger *zap.Logger 44 searchCache *PostingsListCache 45 } 46 47 func newTestMutableSegments( 48 t *testing.T, 49 md namespace.Metadata, 50 blockStart xtime.UnixNano, 51 ) (*mutableSegments, testMutableSegmentsResult) { 52 cachedSearchesWorkers := xsync.NewWorkerPool(2) 53 cachedSearchesWorkers.Init() 54 55 iOpts := instrument.NewTestOptions(t) 56 57 cache, err := NewPostingsListCache(10, PostingsListCacheOptions{ 58 InstrumentOptions: iOpts, 59 }) 60 require.NoError(t, err) 61 62 searchCache, err := NewPostingsListCache(10, PostingsListCacheOptions{ 63 InstrumentOptions: iOpts, 64 }) 65 require.NoError(t, err) 66 67 opts := testOpts. 68 SetPostingsListCache(cache). 69 SetSearchPostingsListCache(searchCache). 70 SetReadThroughSegmentOptions(ReadThroughSegmentOptions{ 71 CacheRegexp: true, 72 CacheTerms: true, 73 CacheSearches: true, 74 }) 75 76 segs := newMutableSegments(md, blockStart, opts, BlockOptions{}, 77 cachedSearchesWorkers, namespace.NewRuntimeOptionsManager("foo"), iOpts) 78 require.NoError(t, err) 79 80 return segs, testMutableSegmentsResult{ 81 logger: iOpts.Logger(), 82 searchCache: searchCache, 83 } 84 } 85 86 func TestMutableSegmentsBackgroundCompactGCReconstructCachedSearches(t *testing.T) { 87 ctrl := xtest.NewController(t) 88 defer ctrl.Finish() 89 90 blockSize := time.Hour 91 testMD := newTestNSMetadata(t) 92 blockStart := xtime.Now().Truncate(blockSize) 93 94 nowNotBlockStartAligned := blockStart.Add(time.Minute) 95 96 segs, result := newTestMutableSegments(t, testMD, blockStart) 97 segs.backgroundCompactDisable = true // Disable to explicitly test. 98 99 inserted := 0 100 segs.Lock() 101 segsBackground := len(segs.backgroundSegments) 102 segs.Unlock() 103 104 for runs := 0; runs < 10; runs++ { 105 runs := runs 106 t.Run(fmt.Sprintf("run-%d", runs), func(t *testing.T) { 107 logger := result.logger.With(zap.Int("run", runs)) 108 109 // Insert until we have a new background segment. 110 for { 111 segs.Lock() 112 curr := len(segs.backgroundSegments) 113 segs.Unlock() 114 if curr > segsBackground { 115 segsBackground = curr 116 break 117 } 118 119 batch := NewWriteBatch(WriteBatchOptions{ 120 IndexBlockSize: blockSize, 121 }) 122 for i := 0; i < 128; i++ { 123 onIndexSeries := doc.NewMockOnIndexSeries(ctrl) 124 onIndexSeries.EXPECT(). 125 TryMarkIndexGarbageCollected(). 126 // Every other is "empty". 127 Return(inserted%2 == 0). 128 AnyTimes() 129 onIndexSeries.EXPECT(). 130 NeedsIndexGarbageCollected(). 131 // Every other is "empty". 132 Return(inserted%2 == 0). 133 AnyTimes() 134 135 batch.Append(WriteBatchEntry{ 136 Timestamp: nowNotBlockStartAligned, 137 OnIndexSeries: onIndexSeries, 138 }, testDocN(inserted)) 139 inserted++ 140 } 141 142 _, err := segs.WriteBatch(batch) 143 require.NoError(t, err) 144 } 145 146 // Perform some searches. 147 testDocSearches(t, segs) 148 149 // Make sure search postings cache was populated. 150 require.True(t, result.searchCache.lru.Len() > 0) 151 logger.Info("search cache populated", zap.Int("n", result.searchCache.lru.Len())) 152 153 // Start some async searches so we have searches going on while 154 // executing background compact GC. 155 doneCh := make(chan struct{}, 2) 156 defer close(doneCh) 157 for i := 0; i < 2; i++ { 158 go func() { 159 for { 160 select { 161 case <-doneCh: 162 return 163 default: 164 } 165 // Search continuously. 166 testDocSearches(t, segs) 167 } 168 }() 169 } 170 171 // Explicitly background compact and make sure that background segment 172 // is GC'd of series no longer present. 173 segs.Lock() 174 segs.backgroundCompactWithLock(false) 175 compactingBackgroundStandard := segs.compact.compactingBackgroundStandard 176 compactingBackgroundGarbageCollect := segs.compact.compactingBackgroundGarbageCollect 177 segs.Unlock() 178 179 // Should have kicked off a background compact GC. 180 require.True(t, compactingBackgroundStandard || compactingBackgroundGarbageCollect) 181 182 // Wait for background compact GC to run. 183 for { 184 segs.Lock() 185 compactingBackgroundStandard := segs.compact.compactingBackgroundStandard 186 compactingBackgroundGarbageCollect := segs.compact.compactingBackgroundGarbageCollect 187 segs.Unlock() 188 if !compactingBackgroundStandard && !compactingBackgroundGarbageCollect { 189 break 190 } 191 time.Sleep(100 * time.Millisecond) 192 } 193 194 logger.Info("compaction done, search cache", zap.Int("n", result.searchCache.lru.Len())) 195 }) 196 } 197 } 198 199 func testDocSearches( 200 t *testing.T, 201 segs *mutableSegments, 202 ) { 203 for i := 0; i < len(testDocBucket0Values); i++ { 204 for j := 0; j < len(testDocBucket1Values); j++ { 205 readers, err := segs.AddReaders(nil) 206 assert.NoError(t, err) 207 208 regexp0 := fmt.Sprintf("(%s|%s)", moduloByteStr(testDocBucket0Values, i), 209 moduloByteStr(testDocBucket0Values, i+1)) 210 b0, err := query.NewRegexpQuery([]byte(testDocBucket0Name), []byte(regexp0)) 211 assert.NoError(t, err) 212 213 regexp1 := fmt.Sprintf("(%s|%s|%s)", moduloByteStr(testDocBucket1Values, j), 214 moduloByteStr(testDocBucket1Values, j+1), 215 moduloByteStr(testDocBucket1Values, j+2)) 216 b1, err := query.NewRegexpQuery([]byte(testDocBucket1Name), []byte(regexp1)) 217 assert.NoError(t, err) 218 219 q := query.NewConjunctionQuery([]search.Query{b0, b1}) 220 searcher, err := q.Searcher() 221 assert.NoError(t, err) 222 223 for _, reader := range readers { 224 readThrough, ok := reader.(search.ReadThroughSegmentSearcher) 225 assert.True(t, ok) 226 227 pl, err := readThrough.Search(q, searcher) 228 assert.NoError(t, err) 229 230 assert.True(t, pl.Len() > 0) 231 } 232 } 233 } 234 } 235 236 var ( 237 testDocBucket0Name = "bucket_0" 238 testDocBucket0Values = []string{ 239 "one", 240 "two", 241 "three", 242 } 243 testDocBucket1Name = "bucket_1" 244 testDocBucket1Values = []string{ 245 "one", 246 "two", 247 "three", 248 "four", 249 "five", 250 } 251 ) 252 253 func testDocN(n int) doc.Metadata { 254 return doc.Metadata{ 255 ID: []byte(fmt.Sprintf("doc-%d", n)), 256 Fields: []doc.Field{ 257 { 258 Name: []byte("foo"), 259 Value: []byte("bar"), 260 }, 261 { 262 Name: []byte(testDocBucket0Name), 263 Value: moduloByteStr(testDocBucket0Values, n), 264 }, 265 { 266 Name: []byte(testDocBucket1Name), 267 Value: moduloByteStr(testDocBucket1Values, n), 268 }, 269 }, 270 } 271 } 272 273 func moduloByteStr(strs []string, n int) []byte { 274 return []byte(strs[n%len(strs)]) 275 }