github.com/m3db/m3@v1.5.0/src/dbnode/storage/index/read_through_segment_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 index 22 23 import ( 24 "regexp/syntax" 25 "testing" 26 27 "github.com/m3db/m3/src/m3ninx/index" 28 "github.com/m3db/m3/src/m3ninx/index/segment" 29 "github.com/m3db/m3/src/m3ninx/index/segment/fst" 30 "github.com/m3db/m3/src/m3ninx/postings/roaring" 31 xtest "github.com/m3db/m3/src/x/test" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/require" 35 ) 36 37 var ( 38 defaultReadThroughSegmentOptions = ReadThroughSegmentOptions{ 39 CacheRegexp: true, 40 CacheTerms: true, 41 } 42 ) 43 44 func testReadThroughSegmentCaches( 45 segmentPostingsListCache *PostingsListCache, 46 ) ReadThroughSegmentCaches { 47 return ReadThroughSegmentCaches{ 48 SegmentPostingsListCache: segmentPostingsListCache, 49 } 50 } 51 52 func TestReadThroughSegmentMatchRegexp(t *testing.T) { 53 ctrl := xtest.NewController(t) 54 defer ctrl.Finish() 55 56 seg := fst.NewMockSegment(ctrl) 57 reader := segment.NewMockReader(ctrl) 58 seg.EXPECT().Reader().Return(reader, nil) 59 60 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 61 require.NoError(t, err) 62 63 field := []byte("some-field") 64 parsedRegex, err := syntax.Parse(".*this-will-be-slow.*", syntax.Simple) 65 require.NoError(t, err) 66 compiledRegex := index.CompiledRegex{ 67 FSTSyntax: parsedRegex, 68 } 69 70 readThrough, err := NewReadThroughSegment(seg, 71 testReadThroughSegmentCaches(cache), 72 defaultReadThroughSegmentOptions).Reader() 73 require.NoError(t, err) 74 75 originalPL := roaring.NewPostingsList() 76 require.NoError(t, originalPL.Insert(1)) 77 reader.EXPECT().MatchRegexp(field, gomock.Any()).Return(originalPL, nil) 78 79 // Make sure it goes to the segment when the cache misses. 80 pl, err := readThrough.MatchRegexp(field, compiledRegex) 81 require.NoError(t, err) 82 require.True(t, pl.Equal(originalPL)) 83 84 // Make sure it relies on the cache if its present (mock only expects 85 // one call.) 86 pl, err = readThrough.MatchRegexp(field, compiledRegex) 87 require.NoError(t, err) 88 require.True(t, pl.Equal(originalPL)) 89 } 90 91 func TestReadThroughSegmentMatchRegexpCacheDisabled(t *testing.T) { 92 ctrl := xtest.NewController(t) 93 defer ctrl.Finish() 94 95 seg := fst.NewMockSegment(ctrl) 96 reader := segment.NewMockReader(ctrl) 97 seg.EXPECT().Reader().Return(reader, nil) 98 99 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 100 require.NoError(t, err) 101 102 field := []byte("some-field") 103 parsedRegex, err := syntax.Parse(".*this-will-be-slow.*", syntax.Simple) 104 require.NoError(t, err) 105 compiledRegex := index.CompiledRegex{ 106 FSTSyntax: parsedRegex, 107 } 108 109 readThrough, err := NewReadThroughSegment(seg, 110 testReadThroughSegmentCaches(cache), 111 ReadThroughSegmentOptions{ 112 CacheRegexp: false, 113 }). 114 Reader() 115 require.NoError(t, err) 116 117 originalPL := roaring.NewPostingsList() 118 require.NoError(t, originalPL.Insert(1)) 119 reader.EXPECT(). 120 MatchRegexp(field, gomock.Any()). 121 Return(originalPL, nil). 122 Times(2) 123 124 // Make sure it goes to the segment. 125 pl, err := readThrough.MatchRegexp(field, compiledRegex) 126 require.NoError(t, err) 127 require.True(t, pl.Equal(originalPL)) 128 129 // Make sure it goes to the segment the second time - meaning the cache was 130 // disabled. 131 pl, err = readThrough.MatchRegexp(field, compiledRegex) 132 require.NoError(t, err) 133 require.True(t, pl.Equal(originalPL)) 134 } 135 136 func TestReadThroughSegmentMatchRegexpNoCache(t *testing.T) { 137 ctrl := xtest.NewController(t) 138 defer ctrl.Finish() 139 140 var ( 141 seg = fst.NewMockSegment(ctrl) 142 reader = segment.NewMockReader(ctrl) 143 field = []byte("some-field") 144 parsedRegex, err = syntax.Parse(".*this-will-be-slow.*", syntax.Simple) 145 ) 146 require.NoError(t, err) 147 148 seg.EXPECT().Reader().Return(reader, nil) 149 compiledRegex := index.CompiledRegex{ 150 FSTSyntax: parsedRegex, 151 } 152 153 readThrough, err := NewReadThroughSegment(seg, 154 testReadThroughSegmentCaches(nil), 155 defaultReadThroughSegmentOptions). 156 Reader() 157 require.NoError(t, err) 158 159 originalPL := roaring.NewPostingsList() 160 require.NoError(t, originalPL.Insert(1)) 161 reader.EXPECT().MatchRegexp(field, gomock.Any()).Return(originalPL, nil) 162 163 // Make sure it it works with no cache. 164 pl, err := readThrough.MatchRegexp(field, compiledRegex) 165 require.NoError(t, err) 166 require.True(t, pl.Equal(originalPL)) 167 } 168 169 func TestReadThroughSegmentMatchTerm(t *testing.T) { 170 ctrl := xtest.NewController(t) 171 defer ctrl.Finish() 172 173 seg := fst.NewMockSegment(ctrl) 174 reader := segment.NewMockReader(ctrl) 175 seg.EXPECT().Reader().Return(reader, nil) 176 177 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 178 require.NoError(t, err) 179 180 var ( 181 field = []byte("some-field") 182 term = []byte("some-term") 183 184 originalPL = roaring.NewPostingsList() 185 ) 186 require.NoError(t, originalPL.Insert(1)) 187 188 readThrough, err := NewReadThroughSegment(seg, 189 testReadThroughSegmentCaches(cache), 190 defaultReadThroughSegmentOptions). 191 Reader() 192 require.NoError(t, err) 193 194 reader.EXPECT().MatchTerm(field, term).Return(originalPL, nil) 195 196 // Make sure it goes to the segment when the cache misses. 197 pl, err := readThrough.MatchTerm(field, term) 198 require.NoError(t, err) 199 require.True(t, pl.Equal(originalPL)) 200 201 // Make sure it relies on the cache if its present (mock only expects 202 // one call.) 203 pl, err = readThrough.MatchTerm(field, term) 204 require.NoError(t, err) 205 require.True(t, pl.Equal(originalPL)) 206 } 207 208 func TestReadThroughSegmentMatchTermCacheDisabled(t *testing.T) { 209 ctrl := xtest.NewController(t) 210 defer ctrl.Finish() 211 212 seg := fst.NewMockSegment(ctrl) 213 reader := segment.NewMockReader(ctrl) 214 seg.EXPECT().Reader().Return(reader, nil) 215 216 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 217 require.NoError(t, err) 218 219 var ( 220 field = []byte("some-field") 221 term = []byte("some-term") 222 223 originalPL = roaring.NewPostingsList() 224 ) 225 require.NoError(t, originalPL.Insert(1)) 226 227 readThrough, err := NewReadThroughSegment(seg, 228 testReadThroughSegmentCaches(cache), 229 ReadThroughSegmentOptions{ 230 CacheTerms: false, 231 }). 232 Reader() 233 require.NoError(t, err) 234 235 reader.EXPECT(). 236 MatchTerm(field, term). 237 Return(originalPL, nil). 238 Times(2) 239 240 // Make sure it goes to the segment when the cache misses. 241 pl, err := readThrough.MatchTerm(field, term) 242 require.NoError(t, err) 243 require.True(t, pl.Equal(originalPL)) 244 245 // Make sure it goes to the segment the second time - meaning the cache was 246 // disabled. 247 pl, err = readThrough.MatchTerm(field, term) 248 require.NoError(t, err) 249 require.True(t, pl.Equal(originalPL)) 250 } 251 252 func TestReadThroughSegmentMatchTermNoCache(t *testing.T) { 253 ctrl := xtest.NewController(t) 254 defer ctrl.Finish() 255 256 var ( 257 seg = fst.NewMockSegment(ctrl) 258 reader = segment.NewMockReader(ctrl) 259 260 field = []byte("some-field") 261 term = []byte("some-term") 262 263 originalPL = roaring.NewPostingsList() 264 ) 265 require.NoError(t, originalPL.Insert(1)) 266 267 seg.EXPECT().Reader().Return(reader, nil) 268 269 readThrough, err := NewReadThroughSegment(seg, 270 testReadThroughSegmentCaches(nil), 271 defaultReadThroughSegmentOptions). 272 Reader() 273 require.NoError(t, err) 274 275 reader.EXPECT().MatchTerm(field, term).Return(originalPL, nil) 276 277 // Make sure it it works with no cache. 278 pl, err := readThrough.MatchTerm(field, term) 279 require.NoError(t, err) 280 require.True(t, pl.Equal(originalPL)) 281 } 282 283 func TestClose(t *testing.T) { 284 ctrl := xtest.NewController(t) 285 defer ctrl.Finish() 286 287 segment := fst.NewMockSegment(ctrl) 288 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 289 require.NoError(t, err) 290 291 readThroughSeg := NewReadThroughSegment(segment, 292 testReadThroughSegmentCaches(nil), 293 defaultReadThroughSegmentOptions) 294 295 segmentUUID := readThroughSeg.uuid 296 297 // Store an entry for the segment in the cache so we can check if it 298 // gets purged after. 299 cache.PutRegexp(segmentUUID, "some-field", "some-pattern", roaring.NewPostingsList()) 300 301 segment.EXPECT().Close().Return(nil) 302 err = readThroughSeg.Close() 303 require.NoError(t, err) 304 require.True(t, readThroughSeg.closed) 305 306 // Make sure it does not allow double closes. 307 err = readThroughSeg.Close() 308 require.Equal(t, errCantCloseClosedSegment, err) 309 310 // Make sure it does not allow readers to be created after closing. 311 _, err = readThroughSeg.Reader() 312 require.Equal(t, errCantGetReaderFromClosedSegment, err) 313 } 314 315 func TestReadThroughSegmentMatchField(t *testing.T) { 316 ctrl := xtest.NewController(t) 317 defer ctrl.Finish() 318 319 seg := fst.NewMockSegment(ctrl) 320 reader := segment.NewMockReader(ctrl) 321 seg.EXPECT().Reader().Return(reader, nil) 322 323 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 324 require.NoError(t, err) 325 326 var ( 327 field = []byte("some-field") 328 329 originalPL = roaring.NewPostingsList() 330 ) 331 require.NoError(t, originalPL.Insert(1)) 332 333 readThrough, err := NewReadThroughSegment(seg, 334 testReadThroughSegmentCaches(cache), 335 defaultReadThroughSegmentOptions). 336 Reader() 337 require.NoError(t, err) 338 339 reader.EXPECT().MatchField(field).Return(originalPL, nil) 340 341 // Make sure it goes to the segment when the cache misses. 342 pl, err := readThrough.MatchField(field) 343 require.NoError(t, err) 344 require.True(t, pl.Equal(originalPL)) 345 346 // Make sure it relies on the cache if its present (mock only expects 347 // one call.) 348 pl, err = readThrough.MatchField(field) 349 require.NoError(t, err) 350 require.True(t, pl.Equal(originalPL)) 351 } 352 353 func TestReadThroughSegmentMatchFieldCacheDisabled(t *testing.T) { 354 ctrl := xtest.NewController(t) 355 defer ctrl.Finish() 356 357 seg := fst.NewMockSegment(ctrl) 358 reader := segment.NewMockReader(ctrl) 359 seg.EXPECT().Reader().Return(reader, nil) 360 361 cache, err := NewPostingsListCache(1, testPostingListCacheOptions) 362 require.NoError(t, err) 363 364 var ( 365 field = []byte("some-field") 366 367 originalPL = roaring.NewPostingsList() 368 ) 369 require.NoError(t, originalPL.Insert(1)) 370 371 readThrough, err := NewReadThroughSegment(seg, 372 testReadThroughSegmentCaches(cache), 373 ReadThroughSegmentOptions{ 374 CacheTerms: false, 375 }). 376 Reader() 377 require.NoError(t, err) 378 379 reader.EXPECT(). 380 MatchField(field). 381 Return(originalPL, nil). 382 Times(2) 383 384 // Make sure it goes to the segment when the cache misses. 385 pl, err := readThrough.MatchField(field) 386 require.NoError(t, err) 387 require.True(t, pl.Equal(originalPL)) 388 389 // Make sure it goes to the segment the second time - meaning the cache was 390 // disabled. 391 pl, err = readThrough.MatchField(field) 392 require.NoError(t, err) 393 require.True(t, pl.Equal(originalPL)) 394 } 395 396 func TestReadThroughSegmentMatchFieldNoCache(t *testing.T) { 397 ctrl := xtest.NewController(t) 398 defer ctrl.Finish() 399 400 var ( 401 seg = fst.NewMockSegment(ctrl) 402 reader = segment.NewMockReader(ctrl) 403 404 field = []byte("some-field") 405 406 originalPL = roaring.NewPostingsList() 407 ) 408 require.NoError(t, originalPL.Insert(1)) 409 410 seg.EXPECT().Reader().Return(reader, nil) 411 412 readThrough, err := NewReadThroughSegment(seg, 413 testReadThroughSegmentCaches(nil), 414 defaultReadThroughSegmentOptions). 415 Reader() 416 require.NoError(t, err) 417 418 reader.EXPECT().MatchField(field).Return(originalPL, nil) 419 420 // Make sure it it works with no cache. 421 pl, err := readThrough.MatchField(field) 422 require.NoError(t, err) 423 require.True(t, pl.Equal(originalPL)) 424 } 425 426 func TestCloseNoCache(t *testing.T) { 427 ctrl := xtest.NewController(t) 428 defer ctrl.Finish() 429 430 seg := fst.NewMockSegment(ctrl) 431 432 readThrough := NewReadThroughSegment(seg, 433 testReadThroughSegmentCaches(nil), 434 defaultReadThroughSegmentOptions) 435 436 seg.EXPECT().Close().Return(nil) 437 err := readThrough.Close() 438 require.NoError(t, err) 439 require.True(t, readThrough.closed) 440 }