github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index_queue_test.go (about) 1 // Copyright (c) 2018 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 "fmt" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/namespace" 30 m3dberrors "github.com/m3db/m3/src/dbnode/storage/errors" 31 "github.com/m3db/m3/src/dbnode/storage/index" 32 idxconvert "github.com/m3db/m3/src/dbnode/storage/index/convert" 33 "github.com/m3db/m3/src/m3ninx/doc" 34 m3ninxidx "github.com/m3db/m3/src/m3ninx/idx" 35 "github.com/m3db/m3/src/m3ninx/index/segment/fst/encoding/docs" 36 "github.com/m3db/m3/src/x/clock" 37 "github.com/m3db/m3/src/x/context" 38 "github.com/m3db/m3/src/x/ident" 39 "github.com/m3db/m3/src/x/resource" 40 xsync "github.com/m3db/m3/src/x/sync" 41 xtest "github.com/m3db/m3/src/x/test" 42 xtime "github.com/m3db/m3/src/x/time" 43 44 "github.com/fortytw2/leaktest" 45 "github.com/golang/mock/gomock" 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 "github.com/uber-go/tally" 49 ) 50 51 func testNamespaceIndexOptions() index.Options { 52 return DefaultTestOptions().IndexOptions() 53 } 54 55 func newTestNamespaceIndex(t *testing.T, ctrl *gomock.Controller) (NamespaceIndex, *MocknamespaceIndexInsertQueue) { 56 q := NewMocknamespaceIndexInsertQueue(ctrl) 57 newFn := func( 58 fn nsIndexInsertBatchFn, 59 md namespace.Metadata, 60 nowFn clock.NowFn, 61 coreFn xsync.CoreFn, 62 s tally.Scope, 63 ) namespaceIndexInsertQueue { 64 return q 65 } 66 q.EXPECT().Start().Return(nil) 67 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 68 require.NoError(t, err) 69 idx, err := newNamespaceIndexWithInsertQueueFn(md, 70 namespace.NewRuntimeOptionsManager(md.ID().String()), 71 testShardSet, newFn, DefaultTestOptions()) 72 assert.NoError(t, err) 73 return idx, q 74 } 75 76 func TestNamespaceIndexHappyPath(t *testing.T) { 77 ctrl := xtest.NewController(t) 78 defer ctrl.Finish() 79 80 q := NewMocknamespaceIndexInsertQueue(ctrl) 81 newFn := func( 82 fn nsIndexInsertBatchFn, 83 md namespace.Metadata, 84 nowFn clock.NowFn, 85 coreFn xsync.CoreFn, 86 s tally.Scope, 87 ) namespaceIndexInsertQueue { 88 return q 89 } 90 q.EXPECT().Start().Return(nil) 91 92 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 93 require.NoError(t, err) 94 idx, err := newNamespaceIndexWithInsertQueueFn(md, 95 namespace.NewRuntimeOptionsManager(md.ID().String()), 96 testShardSet, newFn, DefaultTestOptions()) 97 assert.NoError(t, err) 98 assert.NotNil(t, idx) 99 100 q.EXPECT().Stop().Return(nil) 101 assert.NoError(t, idx.Close()) 102 } 103 104 func TestNamespaceIndexStartErr(t *testing.T) { 105 ctrl := xtest.NewController(t) 106 defer ctrl.Finish() 107 108 q := NewMocknamespaceIndexInsertQueue(ctrl) 109 newFn := func( 110 fn nsIndexInsertBatchFn, 111 md namespace.Metadata, 112 nowFn clock.NowFn, 113 coreFn xsync.CoreFn, 114 s tally.Scope, 115 ) namespaceIndexInsertQueue { 116 return q 117 } 118 q.EXPECT().Start().Return(fmt.Errorf("random err")) 119 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 120 require.NoError(t, err) 121 idx, err := newNamespaceIndexWithInsertQueueFn(md, 122 namespace.NewRuntimeOptionsManager(md.ID().String()), 123 testShardSet, newFn, DefaultTestOptions()) 124 assert.Error(t, err) 125 assert.Nil(t, idx) 126 } 127 128 func TestNamespaceIndexStopErr(t *testing.T) { 129 ctrl := xtest.NewController(t) 130 defer ctrl.Finish() 131 132 q := NewMocknamespaceIndexInsertQueue(ctrl) 133 newFn := func( 134 fn nsIndexInsertBatchFn, 135 md namespace.Metadata, 136 nowFn clock.NowFn, 137 coreFn xsync.CoreFn, 138 s tally.Scope, 139 ) namespaceIndexInsertQueue { 140 return q 141 } 142 q.EXPECT().Start().Return(nil) 143 144 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 145 require.NoError(t, err) 146 idx, err := newNamespaceIndexWithInsertQueueFn(md, 147 namespace.NewRuntimeOptionsManager(md.ID().String()), 148 testShardSet, newFn, DefaultTestOptions()) 149 assert.NoError(t, err) 150 assert.NotNil(t, idx) 151 152 q.EXPECT().Stop().Return(fmt.Errorf("random err")) 153 assert.Error(t, idx.Close()) 154 } 155 156 func TestNamespaceIndexWriteAfterClose(t *testing.T) { 157 ctrl := xtest.NewController(t) 158 defer ctrl.Finish() 159 160 dbIdx, q := newTestNamespaceIndex(t, ctrl) 161 idx, ok := dbIdx.(*nsIndex) 162 assert.True(t, ok) 163 164 id := ident.StringID("foo") 165 tags := ident.NewTags( 166 ident.StringTag("name", "value"), 167 ) 168 169 q.EXPECT().Stop().Return(nil) 170 assert.NoError(t, idx.Close()) 171 172 now := xtime.Now() 173 174 lifecycle := doc.NewMockOnIndexSeries(ctrl) 175 lifecycle.EXPECT().OnIndexFinalize(now.Truncate(idx.blockSize)) 176 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()). 177 Return(false). 178 AnyTimes() 179 entry, document := testWriteBatchEntry(id, tags, now, lifecycle) 180 assert.Error(t, idx.WriteBatch(testWriteBatch(entry, document, 181 testWriteBatchBlockSizeOption(idx.blockSize)))) 182 } 183 184 func TestNamespaceIndexWriteQueueError(t *testing.T) { 185 ctrl := xtest.NewController(t) 186 defer ctrl.Finish() 187 188 dbIdx, q := newTestNamespaceIndex(t, ctrl) 189 idx, ok := dbIdx.(*nsIndex) 190 assert.True(t, ok) 191 192 id := ident.StringID("foo") 193 tags := ident.NewTags( 194 ident.StringTag("name", "value"), 195 ) 196 197 n := xtime.Now() 198 lifecycle := doc.NewMockOnIndexSeries(ctrl) 199 lifecycle.EXPECT().OnIndexFinalize(n.Truncate(idx.blockSize)) 200 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false) 201 q.EXPECT(). 202 InsertBatch(gomock.Any()). 203 Return(nil, fmt.Errorf("random err")) 204 entry, document := testWriteBatchEntry(id, tags, n, lifecycle) 205 assert.Error(t, idx.WriteBatch(testWriteBatch(entry, document, 206 testWriteBatchBlockSizeOption(idx.blockSize)))) 207 } 208 209 func TestNamespaceIndexInsertOlderThanRetentionPeriod(t *testing.T) { 210 ctrl := xtest.NewController(t) 211 defer ctrl.Finish() 212 213 var ( 214 nowLock sync.Mutex 215 now = xtime.Now() 216 nowFn = func() time.Time { 217 nowLock.Lock() 218 n := now 219 nowLock.Unlock() 220 return n.ToTime() 221 } 222 ) 223 224 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 225 require.NoError(t, err) 226 227 opts := testNamespaceIndexOptions().SetInsertMode(index.InsertSync) 228 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 229 dbIdx, err := newNamespaceIndex(md, 230 namespace.NewRuntimeOptionsManager(md.ID().String()), 231 testShardSet, DefaultTestOptions().SetIndexOptions(opts)) 232 assert.NoError(t, err) 233 234 idx, ok := dbIdx.(*nsIndex) 235 assert.True(t, ok) 236 237 var ( 238 id = ident.StringID("foo") 239 tags = ident.NewTags( 240 ident.StringTag("name", "value"), 241 ) 242 lifecycle = doc.NewMockOnIndexSeries(ctrl) 243 ) 244 245 tooOld := now.Add(-1 * idx.bufferPast).Add(-1 * time.Second) 246 lifecycle.EXPECT().OnIndexFinalize(tooOld.Truncate(idx.blockSize)) 247 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()). 248 Return(false). 249 AnyTimes() 250 entry, document := testWriteBatchEntry(id, tags, tooOld, lifecycle) 251 batch := testWriteBatch(entry, document, testWriteBatchBlockSizeOption(idx.blockSize)) 252 253 assert.Error(t, idx.WriteBatch(batch)) 254 255 verified := 0 256 batch.ForEach(func( 257 idx int, 258 entry index.WriteBatchEntry, 259 doc doc.Metadata, 260 result index.WriteBatchEntryResult, 261 ) { 262 verified++ 263 require.Error(t, result.Err) 264 require.Equal(t, m3dberrors.ErrTooPast, result.Err) 265 }) 266 require.Equal(t, 1, verified) 267 268 tooNew := now.Add(1 * idx.bufferFuture).Add(1 * time.Second) 269 lifecycle.EXPECT().OnIndexFinalize(tooNew.Truncate(idx.blockSize)) 270 entry, document = testWriteBatchEntry(id, tags, tooNew, lifecycle) 271 batch = testWriteBatch(entry, document, testWriteBatchBlockSizeOption(idx.blockSize)) 272 assert.Error(t, idx.WriteBatch(batch)) 273 274 verified = 0 275 batch.ForEach(func( 276 idx int, 277 entry index.WriteBatchEntry, 278 doc doc.Metadata, 279 result index.WriteBatchEntryResult, 280 ) { 281 verified++ 282 require.Error(t, result.Err) 283 require.Equal(t, m3dberrors.ErrTooFuture, result.Err) 284 }) 285 require.Equal(t, 1, verified) 286 287 assert.NoError(t, dbIdx.Close()) 288 } 289 290 func TestNamespaceIndexInsertQueueInteraction(t *testing.T) { 291 ctrl := xtest.NewController(t) 292 defer ctrl.Finish() 293 294 dbIdx, q := newTestNamespaceIndex(t, ctrl) 295 idx, ok := dbIdx.(*nsIndex) 296 assert.True(t, ok) 297 298 var ( 299 id = ident.StringID("foo") 300 tags = ident.NewTags( 301 ident.StringTag("name", "value"), 302 ) 303 ) 304 305 now := xtime.Now() 306 307 var wg sync.WaitGroup 308 lifecycle := doc.NewMockOnIndexSeries(ctrl) 309 q.EXPECT().InsertBatch(gomock.Any()).Return(&wg, nil) 310 lifecycle.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()). 311 Return(false). 312 AnyTimes() 313 assert.NoError(t, idx.WriteBatch(testWriteBatch(testWriteBatchEntry(id, 314 tags, now, lifecycle)))) 315 } 316 317 func setupIndex(t *testing.T, 318 ctrl *gomock.Controller, 319 now xtime.UnixNano, 320 expectAggregateQuery bool, 321 ) NamespaceIndex { 322 newFn := func( 323 fn nsIndexInsertBatchFn, 324 md namespace.Metadata, 325 nowFn clock.NowFn, 326 coreFn xsync.CoreFn, 327 s tally.Scope, 328 ) namespaceIndexInsertQueue { 329 q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s) 330 q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond 331 return q 332 } 333 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 334 require.NoError(t, err) 335 idx, err := newNamespaceIndexWithInsertQueueFn(md, 336 namespace.NewRuntimeOptionsManager(md.ID().String()), 337 testShardSet, newFn, DefaultTestOptions(). 338 SetIndexOptions(testNamespaceIndexOptions(). 339 SetInsertMode(index.InsertSync))) 340 assert.NoError(t, err) 341 342 var ( 343 blockSize = idx.(*nsIndex).blockSize 344 ts = idx.(*nsIndex).state.latestBlock.StartTime() 345 id = ident.StringID("foo") 346 tags = ident.NewTags( 347 ident.StringTag("name", "value"), 348 ) 349 lifecycleFns = doc.NewMockOnIndexSeries(ctrl) 350 ) 351 352 closer := &resource.NoopCloser{} 353 lifecycleFns.EXPECT().ReconciledOnIndexSeries().Return(lifecycleFns, closer, false).AnyTimes() 354 lifecycleFns.EXPECT().OnIndexFinalize(ts) 355 lifecycleFns.EXPECT().OnIndexSuccess(ts) 356 lifecycleFns.EXPECT().IfAlreadyIndexedMarkIndexSuccessAndFinalize(gomock.Any()).Return(false) 357 358 if !expectAggregateQuery { 359 lifecycleFns.EXPECT().IndexedRange().Return(ts, ts) 360 lifecycleFns.EXPECT().IndexedForBlockStart(ts).Return(true) 361 } 362 363 entry, doc := testWriteBatchEntry(id, tags, now, lifecycleFns) 364 batch := testWriteBatch(entry, doc, testWriteBatchBlockSizeOption(blockSize)) 365 assert.NoError(t, idx.WriteBatch(batch)) 366 367 return idx 368 } 369 370 func TestNamespaceIndexInsertQuery(t *testing.T) { 371 ctrl := xtest.NewController(t) 372 defer ctrl.Finish() 373 defer leaktest.CheckTimeout(t, 2*time.Second)() 374 375 ctx := context.NewBackground() 376 defer ctx.Close() 377 378 now := xtime.Now() 379 idx := setupIndex(t, ctrl, now, false) 380 defer idx.Close() 381 382 reQuery, err := m3ninxidx.NewRegexpQuery([]byte("name"), []byte("val.*")) 383 assert.NoError(t, err) 384 res, err := idx.Query(ctx, index.Query{Query: reQuery}, index.QueryOptions{ 385 StartInclusive: now.Add(-1 * time.Minute), 386 EndExclusive: now.Add(1 * time.Minute), 387 }) 388 require.NoError(t, err) 389 390 assert.True(t, res.Exhaustive) 391 results := res.Results 392 assert.Equal(t, "testns1", results.Namespace().String()) 393 394 reader := docs.NewEncodedDocumentReader() 395 d, ok := results.Map().Get(ident.BytesID("foo")) 396 md, err := docs.MetadataFromDocument(d, reader) 397 require.NoError(t, err) 398 tags := idxconvert.ToSeriesTags(md, idxconvert.Opts{NoClone: true}) 399 400 assert.True(t, ok) 401 assert.True(t, ident.NewTagIterMatcher( 402 ident.MustNewTagStringsIterator("name", "value")).Matches( 403 tags)) 404 } 405 406 func TestNamespaceIndexInsertAggregateQuery(t *testing.T) { 407 ctrl := xtest.NewController(t) 408 defer ctrl.Finish() 409 defer leaktest.CheckTimeout(t, 2*time.Second)() 410 411 ctx := context.NewBackground() 412 defer ctx.Close() 413 414 now := xtime.Now() 415 idx := setupIndex(t, ctrl, now, true) 416 defer idx.Close() 417 418 reQuery, err := m3ninxidx.NewRegexpQuery([]byte("name"), []byte("val.*")) 419 assert.NoError(t, err) 420 res, err := idx.AggregateQuery(ctx, index.Query{Query: reQuery}, 421 index.AggregationOptions{ 422 QueryOptions: index.QueryOptions{ 423 StartInclusive: now.Add(-1 * time.Minute), 424 EndExclusive: now.Add(1 * time.Minute), 425 }, 426 }, 427 ) 428 require.NoError(t, err) 429 430 assert.True(t, res.Exhaustive) 431 results := res.Results 432 assert.Equal(t, "testns1", results.Namespace().String()) 433 434 rMap := results.Map() 435 require.Equal(t, 1, rMap.Len()) 436 seenIters, found := rMap.Get(ident.StringID("name")) 437 require.True(t, found) 438 439 vMap := seenIters.Map() 440 require.Equal(t, 1, vMap.Len()) 441 assert.True(t, vMap.Contains(ident.StringID("value"))) 442 }