github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/shard_ref_count_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 "testing" 26 "time" 27 28 "github.com/fortytw2/leaktest" 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 "github.com/uber-go/tally" 33 34 "github.com/m3db/m3/src/dbnode/namespace" 35 "github.com/m3db/m3/src/dbnode/runtime" 36 "github.com/m3db/m3/src/dbnode/storage/index" 37 "github.com/m3db/m3/src/dbnode/storage/index/convert" 38 "github.com/m3db/m3/src/dbnode/storage/series" 39 xmetrics "github.com/m3db/m3/src/dbnode/x/metrics" 40 "github.com/m3db/m3/src/x/clock" 41 "github.com/m3db/m3/src/x/context" 42 "github.com/m3db/m3/src/x/ident" 43 xsync "github.com/m3db/m3/src/x/sync" 44 xtest "github.com/m3db/m3/src/x/test" 45 xtime "github.com/m3db/m3/src/x/time" 46 ) 47 48 func TestShardWriteSyncRefCount(t *testing.T) { 49 opts := DefaultTestOptions() 50 testShardWriteSyncRefCount(t, opts) 51 } 52 53 func TestShardWriteSyncRefCountVerifyNoCopyAnnotation(t *testing.T) { 54 opts := DefaultTestOptions(). 55 // Set bytes pool to nil to ensure we're not using it to copy annotations 56 // on the sync path. 57 SetBytesPool(nil) 58 testShardWriteSyncRefCount(t, opts) 59 } 60 61 func testShardWriteSyncRefCount(t *testing.T, opts Options) { 62 now := xtime.Now() 63 64 shard := testDatabaseShard(t, opts) 65 shard.SetRuntimeOptions(runtime.NewOptions(). 66 SetWriteNewSeriesAsync(false)) 67 defer shard.Close() 68 69 ctx := context.NewBackground() 70 defer ctx.Close() 71 72 seriesWrite, err := shard.Write(ctx, ident.StringID("foo"), now, 1.0, 73 xtime.Second, nil, series.WriteOptions{}) 74 assert.NoError(t, err) 75 assert.True(t, seriesWrite.WasWritten) 76 77 seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), now, 1.0, 78 xtime.Second, nil, series.WriteOptions{}) 79 assert.NoError(t, err) 80 assert.False(t, seriesWrite.WasWritten) 81 82 seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), now, 2.0, 83 xtime.Second, nil, series.WriteOptions{}) 84 assert.NoError(t, err) 85 assert.True(t, seriesWrite.WasWritten) 86 87 seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), now, 3.0, 88 xtime.Second, nil, series.WriteOptions{}) 89 assert.NoError(t, err) 90 assert.True(t, seriesWrite.WasWritten) 91 92 // ensure all entries have no references left 93 for _, id := range []string{"foo", "bar", "baz"} { 94 shard.Lock() 95 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 96 shard.Unlock() 97 assert.NoError(t, err) 98 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 99 } 100 101 // write already inserted series' 102 next := now.Add(time.Minute) 103 104 seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), next, 1.0, xtime.Second, nil, series.WriteOptions{}) 105 assert.NoError(t, err) 106 assert.True(t, seriesWrite.WasWritten) 107 108 seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), next, 2.0, xtime.Second, nil, series.WriteOptions{}) 109 assert.NoError(t, err) 110 assert.True(t, seriesWrite.WasWritten) 111 112 seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), next, 3.0, xtime.Second, nil, series.WriteOptions{}) 113 assert.NoError(t, err) 114 assert.True(t, seriesWrite.WasWritten) 115 116 // ensure all entries have no references left 117 for _, id := range []string{"foo", "bar", "baz"} { 118 shard.Lock() 119 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 120 shard.Unlock() 121 assert.NoError(t, err) 122 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 123 } 124 } 125 126 func TestShardWriteTaggedSyncRefCountMockIndex(t *testing.T) { 127 ctrl := xtest.NewController(t) 128 defer ctrl.Finish() 129 130 blockSize := namespaceIndexOptions.BlockSize() 131 132 idx := NewMockNamespaceIndex(ctrl) 133 idx.EXPECT().BlockStartForWriteTime(gomock.Any()). 134 DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano { 135 return t.Truncate(blockSize) 136 }). 137 AnyTimes() 138 idx.EXPECT().WriteBatch(gomock.Any()). 139 Return(nil). 140 Do(func(batch *index.WriteBatch) { 141 if batch.Len() != 1 { 142 // require.Equal(...) silently kills goroutines 143 panic(fmt.Sprintf("expected batch len 1: len=%d", batch.Len())) 144 } 145 146 entry := batch.PendingEntries()[0] 147 blockStart := entry.Timestamp.Truncate(blockSize) 148 onIdx := entry.OnIndexSeries 149 onIdx.OnIndexSuccess(blockStart) 150 onIdx.OnIndexFinalize(blockStart) 151 }). 152 AnyTimes() 153 154 testShardWriteTaggedSyncRefCount(t, idx) 155 } 156 157 func TestShardWriteTaggedSyncRefCountSyncIndex(t *testing.T) { 158 defer leaktest.CheckTimeout(t, 10*time.Second)() 159 newFn := func( 160 fn nsIndexInsertBatchFn, 161 md namespace.Metadata, 162 nowFn clock.NowFn, 163 coreFn xsync.CoreFn, 164 s tally.Scope, 165 ) namespaceIndexInsertQueue { 166 q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s) 167 q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond 168 return q 169 } 170 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 171 require.NoError(t, err) 172 173 var ( 174 opts = DefaultTestOptions() 175 indexOpts = opts.IndexOptions(). 176 SetInsertMode(index.InsertSync) 177 ) 178 opts = opts.SetIndexOptions(indexOpts) 179 180 idx, err := newNamespaceIndexWithInsertQueueFn(md, 181 namespace.NewRuntimeOptionsManager(md.ID().String()), 182 testShardSet, newFn, opts) 183 assert.NoError(t, err) 184 185 defer func() { 186 assert.NoError(t, idx.Close()) 187 }() 188 189 testShardWriteTaggedSyncRefCount(t, idx) 190 } 191 192 func testShardWriteTaggedSyncRefCount(t *testing.T, idx NamespaceIndex) { 193 var ( 194 now = xtime.Now() 195 opts = DefaultTestOptions() 196 shard = testDatabaseShardWithIndexFn(t, opts, idx, false) 197 ) 198 199 shard.SetRuntimeOptions(runtime.NewOptions(). 200 SetWriteNewSeriesAsync(false)) 201 defer shard.Close() 202 203 ctx := context.NewBackground() 204 defer ctx.Close() 205 206 seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"), 207 convert.EmptyTagMetadataResolver, now, 1.0, xtime.Second, nil, series.WriteOptions{}) 208 assert.NoError(t, err) 209 assert.True(t, seriesWrite.WasWritten) 210 211 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"), 212 convert.EmptyTagMetadataResolver, now, 2.0, xtime.Second, nil, series.WriteOptions{}) 213 assert.NoError(t, err) 214 assert.True(t, seriesWrite.WasWritten) 215 216 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"), 217 convert.EmptyTagMetadataResolver, now, 3.0, xtime.Second, nil, series.WriteOptions{}) 218 assert.NoError(t, err) 219 assert.True(t, seriesWrite.WasWritten) 220 221 // ensure all entries have no references left 222 for _, id := range []string{"foo", "bar", "baz"} { 223 shard.Lock() 224 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 225 shard.Unlock() 226 assert.NoError(t, err) 227 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 228 } 229 230 // write already inserted series' 231 next := now.Add(time.Minute) 232 233 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"), 234 convert.EmptyTagMetadataResolver, next, 1.0, xtime.Second, nil, series.WriteOptions{}) 235 assert.NoError(t, err) 236 assert.True(t, seriesWrite.WasWritten) 237 238 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"), 239 convert.EmptyTagMetadataResolver, next, 2.0, xtime.Second, nil, series.WriteOptions{}) 240 assert.NoError(t, err) 241 assert.True(t, seriesWrite.WasWritten) 242 243 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"), 244 convert.EmptyTagMetadataResolver, next, 3.0, xtime.Second, nil, series.WriteOptions{}) 245 assert.NoError(t, err) 246 assert.True(t, seriesWrite.WasWritten) 247 248 // ensure all entries have no references left 249 for _, id := range []string{"foo", "bar", "baz"} { 250 shard.Lock() 251 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 252 shard.Unlock() 253 assert.NoError(t, err) 254 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 255 } 256 } 257 258 func TestShardWriteAsyncRefCount(t *testing.T) { 259 testReporter := xmetrics.NewTestStatsReporter(xmetrics.NewTestStatsReporterOptions()) 260 scope, closer := tally.NewRootScope(tally.ScopeOptions{ 261 Reporter: testReporter, 262 }, 100*time.Millisecond) 263 defer closer.Close() 264 265 now := xtime.Now() 266 opts := DefaultTestOptions() 267 opts = opts.SetInstrumentOptions( 268 opts.InstrumentOptions(). 269 SetMetricsScope(scope). 270 SetReportInterval(100 * time.Millisecond)) 271 272 shard := testDatabaseShard(t, opts) 273 shard.SetRuntimeOptions(runtime.NewOptions(). 274 SetWriteNewSeriesAsync(true)) 275 defer shard.Close() 276 277 ctx := context.NewBackground() 278 defer ctx.Close() 279 280 seriesWrite, err := shard.Write(ctx, ident.StringID("foo"), now, 1.0, 281 xtime.Second, nil, series.WriteOptions{}) 282 assert.NoError(t, err) 283 assert.True(t, seriesWrite.WasWritten) 284 285 seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), now, 2.0, 286 xtime.Second, nil, series.WriteOptions{}) 287 assert.NoError(t, err) 288 assert.True(t, seriesWrite.WasWritten) 289 290 seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), now, 3.0, 291 xtime.Second, nil, series.WriteOptions{}) 292 assert.NoError(t, err) 293 assert.True(t, seriesWrite.WasWritten) 294 295 inserted := clock.WaitUntil(func() bool { 296 counter, ok := testReporter.Counters()["dbshard.insert-queue.inserts"] 297 return ok && counter == 3 298 }, 2*time.Second) 299 assert.True(t, inserted) 300 301 // ensure all entries have no references left 302 for _, id := range []string{"foo", "bar", "baz"} { 303 shard.Lock() 304 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 305 shard.Unlock() 306 assert.NoError(t, err) 307 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 308 } 309 310 // write already inserted series' 311 next := now.Add(time.Minute) 312 313 seriesWrite, err = shard.Write(ctx, ident.StringID("foo"), next, 1.0, xtime.Second, nil, series.WriteOptions{}) 314 assert.NoError(t, err) 315 assert.True(t, seriesWrite.WasWritten) 316 317 seriesWrite, err = shard.Write(ctx, ident.StringID("bar"), next, 2.0, xtime.Second, nil, series.WriteOptions{}) 318 assert.NoError(t, err) 319 assert.True(t, seriesWrite.WasWritten) 320 321 seriesWrite, err = shard.Write(ctx, ident.StringID("baz"), next, 3.0, xtime.Second, nil, series.WriteOptions{}) 322 assert.NoError(t, err) 323 assert.True(t, seriesWrite.WasWritten) 324 325 // ensure all entries have no references left 326 for _, id := range []string{"foo", "bar", "baz"} { 327 shard.Lock() 328 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 329 shard.Unlock() 330 assert.NoError(t, err) 331 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 332 } 333 } 334 335 func newNowFnForWriteTaggedAsyncRefCount() func() time.Time { 336 // Explicitly truncate the time to the beginning of the index block size to prevent 337 // the ref-counts from being thrown off by needing to re-index entries because the first 338 // write happened near a block boundary so that when the second write comes in they need 339 // to be re-indexed for the next block time. 340 start := time.Now().Truncate(namespaceIndexOptions.BlockSize()) 341 342 return func() time.Time { 343 return start 344 } 345 } 346 347 func TestShardWriteTaggedAsyncRefCountMockIndex(t *testing.T) { 348 ctrl := xtest.NewController(t) 349 defer ctrl.Finish() 350 351 blockSize := namespaceIndexOptions.BlockSize() 352 353 idx := NewMockNamespaceIndex(ctrl) 354 idx.EXPECT().BlockStartForWriteTime(gomock.Any()). 355 DoAndReturn(func(t xtime.UnixNano) xtime.UnixNano { 356 return t.Truncate(blockSize) 357 }). 358 AnyTimes() 359 idx.EXPECT().WriteBatch(gomock.Any()). 360 Return(nil). 361 Do(func(batch *index.WriteBatch) { 362 for _, entry := range batch.PendingEntries() { 363 blockStart := entry.Timestamp.Truncate(blockSize) 364 onIdx := entry.OnIndexSeries 365 onIdx.OnIndexSuccess(blockStart) 366 onIdx.OnIndexFinalize(blockStart) 367 } 368 }). 369 AnyTimes() 370 371 nowFn := newNowFnForWriteTaggedAsyncRefCount() 372 testShardWriteTaggedAsyncRefCount(t, idx, nowFn) 373 } 374 375 func TestShardWriteTaggedAsyncRefCountSyncIndex(t *testing.T) { 376 defer leaktest.CheckTimeout(t, 10*time.Second)() 377 newFn := func(fn nsIndexInsertBatchFn, md namespace.Metadata, 378 nowFn clock.NowFn, coreFn xsync.CoreFn, s tally.Scope) namespaceIndexInsertQueue { 379 q := newNamespaceIndexInsertQueue(fn, md, nowFn, coreFn, s) 380 q.(*nsIndexInsertQueue).indexBatchBackoff = 10 * time.Millisecond 381 return q 382 } 383 md, err := namespace.NewMetadata(defaultTestNs1ID, defaultTestNs1Opts) 384 require.NoError(t, err) 385 386 nowFn := newNowFnForWriteTaggedAsyncRefCount() 387 var ( 388 opts = DefaultTestOptions() 389 indexOpts = opts.IndexOptions(). 390 SetInsertMode(index.InsertSync) 391 ) 392 opts = opts. 393 SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 394 indexOpts = indexOpts. 395 SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 396 opts = opts.SetIndexOptions(indexOpts) 397 398 idx, err := newNamespaceIndexWithInsertQueueFn(md, 399 namespace.NewRuntimeOptionsManager(md.ID().String()), 400 testShardSet, newFn, opts) 401 assert.NoError(t, err) 402 403 defer func() { 404 assert.NoError(t, idx.Close()) 405 }() 406 407 testShardWriteTaggedAsyncRefCount(t, idx, nowFn) 408 } 409 410 func testShardWriteTaggedAsyncRefCount(t *testing.T, idx NamespaceIndex, nowFn func() time.Time) { 411 testReporter := xmetrics.NewTestStatsReporter(xmetrics.NewTestStatsReporterOptions()) 412 scope, closer := tally.NewRootScope(tally.ScopeOptions{ 413 Reporter: testReporter, 414 }, 100*time.Millisecond) 415 defer closer.Close() 416 417 var ( 418 start = xtime.ToUnixNano(nowFn()) 419 now = start 420 opts = DefaultTestOptions() 421 ) 422 opts = opts. 423 SetInstrumentOptions( 424 opts.InstrumentOptions(). 425 SetMetricsScope(scope). 426 SetReportInterval(100 * time.Millisecond)) 427 opts = opts. 428 SetClockOptions(opts.ClockOptions().SetNowFn(nowFn)) 429 430 shard := testDatabaseShardWithIndexFn(t, opts, idx, false) 431 shard.SetRuntimeOptions(runtime.NewOptions(). 432 SetWriteNewSeriesAsync(true)) 433 defer shard.Close() 434 435 ctx := context.NewBackground() 436 defer ctx.Close() 437 438 seriesWrite, err := shard.WriteTagged(ctx, ident.StringID("foo"), 439 convert.EmptyTagMetadataResolver, now, 1.0, xtime.Second, nil, series.WriteOptions{}) 440 assert.NoError(t, err) 441 assert.True(t, seriesWrite.WasWritten) 442 assert.True(t, seriesWrite.NeedsIndex) 443 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now)) 444 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now)) 445 446 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"), 447 convert.EmptyTagMetadataResolver, now, 2.0, xtime.Second, nil, series.WriteOptions{}) 448 assert.NoError(t, err) 449 assert.True(t, seriesWrite.WasWritten) 450 assert.True(t, seriesWrite.NeedsIndex) 451 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now)) 452 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now)) 453 454 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"), 455 convert.EmptyTagMetadataResolver, now, 3.0, xtime.Second, nil, series.WriteOptions{}) 456 assert.NoError(t, err) 457 assert.True(t, seriesWrite.WasWritten) 458 assert.True(t, seriesWrite.NeedsIndex) 459 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexSuccess(idx.BlockStartForWriteTime(now)) 460 seriesWrite.PendingIndexInsert.Entry.OnIndexSeries.OnIndexFinalize(idx.BlockStartForWriteTime(now)) 461 462 inserted := clock.WaitUntil(func() bool { 463 counter, ok := testReporter.Counters()["dbshard.insert-queue.inserts"] 464 return ok && counter == 3 465 }, 5*time.Second) 466 assert.True(t, inserted) 467 468 // ensure all entries have no references left 469 for _, id := range []string{"foo", "bar", "baz"} { 470 shard.Lock() 471 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 472 shard.Unlock() 473 assert.NoError(t, err) 474 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 475 } 476 477 // write already inserted series' 478 next := now.Add(time.Minute) 479 480 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("foo"), 481 convert.EmptyTagMetadataResolver, next, 1.0, xtime.Second, nil, series.WriteOptions{}) 482 assert.NoError(t, err) 483 assert.True(t, seriesWrite.WasWritten) 484 485 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("bar"), 486 convert.EmptyTagMetadataResolver, next, 2.0, xtime.Second, nil, series.WriteOptions{}) 487 assert.NoError(t, err) 488 assert.True(t, seriesWrite.WasWritten) 489 490 seriesWrite, err = shard.WriteTagged(ctx, ident.StringID("baz"), 491 convert.EmptyTagMetadataResolver, next, 3.0, xtime.Second, nil, series.WriteOptions{}) 492 assert.NoError(t, err) 493 assert.True(t, seriesWrite.WasWritten) 494 495 // ensure all entries have no references left 496 for _, id := range []string{"foo", "bar", "baz"} { 497 shard.Lock() 498 entry, err := shard.lookupEntryWithLock(ident.StringID(id)) 499 shard.Unlock() 500 assert.NoError(t, err) 501 assert.Equal(t, int32(0), entry.ReaderWriterCount(), id) 502 } 503 }