github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/compactor/compactor_test.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package compactor 12 13 import ( 14 "context" 15 "fmt" 16 "reflect" 17 "sort" 18 "sync/atomic" 19 "testing" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/keys" 23 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb" 24 "github.com/cockroachdb/cockroach/pkg/roachpb" 25 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 26 "github.com/cockroachdb/cockroach/pkg/storage" 27 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 28 "github.com/cockroachdb/cockroach/pkg/testutils" 29 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 30 "github.com/cockroachdb/cockroach/pkg/util/stop" 31 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 32 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 33 "github.com/cockroachdb/errors" 34 ) 35 36 const testCompactionLatency = 1 * time.Millisecond 37 38 type wrappedEngine struct { 39 storage.Engine 40 mu struct { 41 syncutil.Mutex 42 compactions []roachpb.Span 43 } 44 } 45 46 func newWrappedEngine() *wrappedEngine { 47 return &wrappedEngine{ 48 Engine: storage.NewDefaultInMem(), 49 } 50 } 51 52 func (we *wrappedEngine) GetSSTables() storage.SSTableInfos { 53 key := func(s string) storage.MVCCKey { 54 return storage.MakeMVCCMetadataKey([]byte(s)) 55 } 56 ssti := storage.SSTableInfos{ 57 // Level 0. 58 {Level: 0, Size: 20, Start: key("a"), End: key("z")}, 59 {Level: 0, Size: 15, Start: key("a"), End: key("k")}, 60 // Level 2. 61 {Level: 2, Size: 200, Start: key("a"), End: key("j")}, 62 {Level: 2, Size: 100, Start: key("k"), End: key("o")}, 63 {Level: 2, Size: 100, Start: key("r"), End: key("t")}, 64 // Level 6. 65 {Level: 6, Size: 200, Start: key("0"), End: key("9")}, 66 {Level: 6, Size: 201, Start: key("a"), End: key("c")}, 67 {Level: 6, Size: 200, Start: key("d"), End: key("f")}, 68 {Level: 6, Size: 300, Start: key("h"), End: key("r")}, 69 {Level: 6, Size: 405, Start: key("s"), End: key("z")}, 70 } 71 sort.Sort(ssti) 72 return ssti 73 } 74 75 func (we *wrappedEngine) CompactRange(start, end roachpb.Key, forceBottommost bool) error { 76 we.mu.Lock() 77 defer we.mu.Unlock() 78 time.Sleep(testCompactionLatency) 79 we.mu.compactions = append(we.mu.compactions, roachpb.Span{Key: start, EndKey: end}) 80 return nil 81 } 82 83 func (we *wrappedEngine) GetCompactions() []roachpb.Span { 84 we.mu.Lock() 85 defer we.mu.Unlock() 86 return append([]roachpb.Span(nil), we.mu.compactions...) 87 } 88 89 func testSetup(capFn storeCapacityFunc) (*Compactor, *wrappedEngine, *int32, func()) { 90 stopper := stop.NewStopper() 91 eng := newWrappedEngine() 92 stopper.AddCloser(eng) 93 compactionCount := new(int32) 94 doneFn := func(_ context.Context) { atomic.AddInt32(compactionCount, 1) } 95 st := cluster.MakeTestingClusterSettings() 96 compactor := NewCompactor(st, eng, capFn, doneFn) 97 compactor.Start(context.Background(), stopper) 98 return compactor, eng, compactionCount, func() { 99 stopper.Stop(context.Background()) 100 } 101 } 102 103 func key(s string) roachpb.Key { 104 return roachpb.Key([]byte(s)) 105 } 106 107 // TestCompactorThresholds verifies the thresholding logic for the compactor. 108 func TestCompactorThresholds(t *testing.T) { 109 defer leaktest.AfterTest(t)() 110 111 // This test relies on concurrently waiting for a value to change in the 112 // underlying engine(s). Since the teeing engine does not respond well to 113 // value mismatches, whether transient or permanent, skip this test if the 114 // teeing engine is being used. See 115 // https://github.com/cockroachdb/cockroach/issues/42656 for more context. 116 if storage.DefaultStorageEngine == enginepb.EngineTypeTeePebbleRocksDB { 117 t.Skip("disabled on teeing engine") 118 } 119 120 fractionUsedThresh := thresholdBytesUsedFraction.Default()*float64(thresholdBytes.Default()) + 1 121 fractionAvailableThresh := thresholdBytesAvailableFraction.Default()*float64(thresholdBytes.Default()) + 1 122 nowNanos := timeutil.Now().UnixNano() 123 testCases := []struct { 124 name string 125 suggestions []kvserverpb.SuggestedCompaction 126 logicalBytes int64 // logical byte count to return with store capacity 127 availableBytes int64 // available byte count to return with store capacity 128 expBytesCompacted int64 129 expCompactions []roachpb.Span 130 expUncompacted []roachpb.Span 131 }{ 132 // Single suggestion under all thresholds. 133 { 134 name: "single suggestion under all thresholds", 135 suggestions: []kvserverpb.SuggestedCompaction{ 136 { 137 StartKey: key("a"), EndKey: key("b"), 138 Compaction: kvserverpb.Compaction{ 139 Bytes: thresholdBytes.Default() - 1, 140 SuggestedAtNanos: nowNanos, 141 }, 142 }, 143 }, 144 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 145 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 146 expBytesCompacted: 0, 147 expCompactions: nil, 148 expUncompacted: []roachpb.Span{ 149 {Key: key("a"), EndKey: key("b")}, 150 }, 151 }, 152 // Single suggestion which is over absolute bytes threshold should compact. 153 { 154 name: "single suggestion over absolute threshold", 155 suggestions: []kvserverpb.SuggestedCompaction{ 156 { 157 StartKey: key("a"), EndKey: key("b"), 158 Compaction: kvserverpb.Compaction{ 159 Bytes: thresholdBytes.Default(), 160 SuggestedAtNanos: nowNanos, 161 }, 162 }, 163 }, 164 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 165 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 166 expBytesCompacted: thresholdBytes.Default(), 167 expCompactions: []roachpb.Span{ 168 {Key: key("a"), EndKey: key("b")}, 169 }, 170 }, 171 // Single suggestion which is over absolute bytes threshold should not 172 // trigger a compaction if the span contains keys. 173 { 174 name: "outdated single suggestion over absolute threshold", 175 suggestions: []kvserverpb.SuggestedCompaction{ 176 { 177 StartKey: key("0"), EndKey: key("9"), 178 Compaction: kvserverpb.Compaction{ 179 Bytes: thresholdBytes.Default(), 180 SuggestedAtNanos: nowNanos, 181 }, 182 }, 183 }, 184 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 185 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 186 expBytesCompacted: 0, 187 expCompactions: nil, 188 expUncompacted: []roachpb.Span{ 189 {Key: key("0"), EndKey: key("9")}, 190 }, 191 }, 192 // Single suggestion over the fractional threshold. 193 { 194 name: "single suggestion over fractional threshold", 195 suggestions: []kvserverpb.SuggestedCompaction{ 196 { 197 StartKey: key("a"), EndKey: key("b"), 198 Compaction: kvserverpb.Compaction{ 199 Bytes: int64(fractionUsedThresh), 200 SuggestedAtNanos: nowNanos, 201 }, 202 }, 203 }, 204 logicalBytes: thresholdBytes.Default(), 205 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 206 expBytesCompacted: int64(fractionUsedThresh), 207 expCompactions: []roachpb.Span{ 208 {Key: key("a"), EndKey: key("b")}, 209 }, 210 }, 211 // Single suggestion over the fractional bytes available threshold. 212 { 213 name: "single suggestion over fractional bytes available threshold", 214 suggestions: []kvserverpb.SuggestedCompaction{ 215 { 216 StartKey: key("a"), EndKey: key("b"), 217 Compaction: kvserverpb.Compaction{ 218 Bytes: int64(fractionAvailableThresh), 219 SuggestedAtNanos: nowNanos, 220 }, 221 }, 222 }, 223 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 224 availableBytes: thresholdBytes.Default(), 225 expBytesCompacted: int64(fractionAvailableThresh), 226 expCompactions: []roachpb.Span{ 227 {Key: key("a"), EndKey: key("b")}, 228 }, 229 }, 230 // Double suggestion which in aggregate exceed absolute bytes threshold. 231 { 232 name: "double suggestion over absolute threshold", 233 suggestions: []kvserverpb.SuggestedCompaction{ 234 { 235 StartKey: key("a"), EndKey: key("b"), 236 Compaction: kvserverpb.Compaction{ 237 Bytes: thresholdBytes.Default() / 2, 238 SuggestedAtNanos: nowNanos, 239 }, 240 }, 241 { 242 StartKey: key("b"), EndKey: key("c"), 243 Compaction: kvserverpb.Compaction{ 244 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 245 SuggestedAtNanos: nowNanos, 246 }, 247 }, 248 }, 249 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 250 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 251 expBytesCompacted: thresholdBytes.Default(), 252 expCompactions: []roachpb.Span{ 253 {Key: key("a"), EndKey: key("c")}, 254 }, 255 }, 256 // Double suggestion to same span. 257 { 258 name: "double suggestion to same span over absolute threshold", 259 suggestions: []kvserverpb.SuggestedCompaction{ 260 { 261 StartKey: key("a"), EndKey: key("b"), 262 Compaction: kvserverpb.Compaction{ 263 Bytes: thresholdBytes.Default() / 2, 264 SuggestedAtNanos: nowNanos, 265 }, 266 }, 267 { 268 StartKey: key("a"), EndKey: key("b"), 269 Compaction: kvserverpb.Compaction{ 270 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 271 SuggestedAtNanos: nowNanos, 272 }, 273 }, 274 }, 275 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 276 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 277 expBytesCompacted: thresholdBytes.Default(), 278 expCompactions: []roachpb.Span{ 279 {Key: key("a"), EndKey: key("b")}, 280 }, 281 }, 282 // Double suggestion overlapping. 283 { 284 name: "double suggestion overlapping over absolute threshold", 285 suggestions: []kvserverpb.SuggestedCompaction{ 286 { 287 StartKey: key("a"), EndKey: key("c"), 288 Compaction: kvserverpb.Compaction{ 289 Bytes: thresholdBytes.Default() / 2, 290 SuggestedAtNanos: nowNanos, 291 }, 292 }, 293 { 294 StartKey: key("b"), EndKey: key("d"), 295 Compaction: kvserverpb.Compaction{ 296 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 297 SuggestedAtNanos: nowNanos, 298 }, 299 }, 300 }, 301 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 302 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 303 expBytesCompacted: thresholdBytes.Default(), 304 expCompactions: []roachpb.Span{ 305 {Key: key("a"), EndKey: key("d")}, 306 }, 307 }, 308 // Double suggestion which in aggregate exceeds fractional bytes threshold. 309 { 310 name: "double suggestion over fractional threshold", 311 suggestions: []kvserverpb.SuggestedCompaction{ 312 { 313 StartKey: key("a"), EndKey: key("b"), 314 Compaction: kvserverpb.Compaction{ 315 Bytes: int64(fractionUsedThresh / 2), 316 SuggestedAtNanos: nowNanos, 317 }, 318 }, 319 { 320 StartKey: key("b"), EndKey: key("c"), 321 Compaction: kvserverpb.Compaction{ 322 Bytes: int64(fractionUsedThresh) - int64(fractionUsedThresh/2), 323 SuggestedAtNanos: nowNanos, 324 }, 325 }, 326 }, 327 logicalBytes: thresholdBytes.Default(), 328 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 329 expBytesCompacted: int64(fractionUsedThresh), 330 expCompactions: []roachpb.Span{ 331 {Key: key("a"), EndKey: key("c")}, 332 }, 333 }, 334 // Double suggestion without excessive gap. 335 { 336 name: "double suggestion without excessive gap", 337 suggestions: []kvserverpb.SuggestedCompaction{ 338 { 339 StartKey: key("a"), EndKey: key("b"), 340 Compaction: kvserverpb.Compaction{ 341 Bytes: thresholdBytes.Default() / 2, 342 SuggestedAtNanos: nowNanos, 343 }, 344 }, 345 // There are only two sstables between ("b", "e") at the max level. 346 { 347 StartKey: key("e"), EndKey: key("f"), 348 Compaction: kvserverpb.Compaction{ 349 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 350 SuggestedAtNanos: nowNanos, 351 }, 352 }, 353 }, 354 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 355 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 356 expBytesCompacted: thresholdBytes.Default(), 357 expCompactions: []roachpb.Span{ 358 {Key: key("a"), EndKey: key("f")}, 359 }, 360 }, 361 // Double suggestion with non-excessive gap, but there are live keys in the 362 // gap. 363 // 364 // NOTE: when a suggestion itself contains live keys, we skip the compaction 365 // because amounts of data may have been added to the span since the 366 // compaction was proposed. When only the gap contains live keys, however, 367 // it's still desirable to compact: the individual suggestions are empty, so 368 // we can assume there's lots of data to reclaim by compacting, and the 369 // aggregator is very careful not to jump gaps that span too many SSTs. 370 { 371 name: "double suggestion over gap with live keys", 372 suggestions: []kvserverpb.SuggestedCompaction{ 373 { 374 StartKey: key("0"), EndKey: key("4"), 375 Compaction: kvserverpb.Compaction{ 376 Bytes: thresholdBytes.Default() / 2, 377 SuggestedAtNanos: nowNanos, 378 }, 379 }, 380 { 381 StartKey: key("6"), EndKey: key("9"), 382 Compaction: kvserverpb.Compaction{ 383 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 384 SuggestedAtNanos: nowNanos, 385 }, 386 }, 387 }, 388 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 389 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 390 expBytesCompacted: thresholdBytes.Default(), 391 expCompactions: []roachpb.Span{ 392 {Key: key("0"), EndKey: key("9")}, 393 }, 394 }, 395 // Double suggestion with excessive gap. 396 { 397 name: "double suggestion with excessive gap", 398 suggestions: []kvserverpb.SuggestedCompaction{ 399 { 400 StartKey: key("a"), EndKey: key("b"), 401 Compaction: kvserverpb.Compaction{ 402 Bytes: thresholdBytes.Default() / 2, 403 SuggestedAtNanos: nowNanos, 404 }, 405 }, 406 // There are three sstables between ("b", "h0") at the max level. 407 { 408 StartKey: key("h0"), EndKey: key("i"), 409 Compaction: kvserverpb.Compaction{ 410 Bytes: thresholdBytes.Default() - (thresholdBytes.Default() / 2), 411 SuggestedAtNanos: nowNanos, 412 }, 413 }, 414 }, 415 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 416 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 417 expBytesCompacted: 0, 418 expCompactions: nil, 419 expUncompacted: []roachpb.Span{ 420 {Key: key("a"), EndKey: key("b")}, 421 {Key: key("h0"), EndKey: key("i")}, 422 }, 423 }, 424 // Double suggestion with excessive gap, but both over absolute threshold. 425 { 426 name: "double suggestion with excessive gap but both over threshold", 427 suggestions: []kvserverpb.SuggestedCompaction{ 428 { 429 StartKey: key("a"), EndKey: key("b"), 430 Compaction: kvserverpb.Compaction{ 431 Bytes: thresholdBytes.Default(), 432 SuggestedAtNanos: nowNanos, 433 }, 434 }, 435 // There are three sstables between ("b", "h0") at the max level. 436 { 437 StartKey: key("h0"), EndKey: key("i"), 438 Compaction: kvserverpb.Compaction{ 439 Bytes: thresholdBytes.Default(), 440 SuggestedAtNanos: nowNanos, 441 }, 442 }, 443 }, 444 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 445 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 446 expBytesCompacted: thresholdBytes.Default() * 2, 447 expCompactions: []roachpb.Span{ 448 {Key: key("a"), EndKey: key("b")}, 449 {Key: key("h0"), EndKey: key("i")}, 450 }, 451 }, 452 // Double suggestion with excessive gap, with just one over absolute threshold. 453 { 454 name: "double suggestion with excessive gap but one over threshold", 455 suggestions: []kvserverpb.SuggestedCompaction{ 456 { 457 StartKey: key("a"), EndKey: key("b"), 458 Compaction: kvserverpb.Compaction{ 459 Bytes: thresholdBytes.Default(), 460 SuggestedAtNanos: nowNanos, 461 }, 462 }, 463 // There are three sstables between ("b", "h0") at the max level. 464 { 465 StartKey: key("h0"), EndKey: key("i"), 466 Compaction: kvserverpb.Compaction{ 467 Bytes: thresholdBytes.Default() - 1, 468 SuggestedAtNanos: nowNanos, 469 }, 470 }, 471 }, 472 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 473 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 474 expBytesCompacted: thresholdBytes.Default(), 475 expCompactions: []roachpb.Span{ 476 {Key: key("a"), EndKey: key("b")}, 477 }, 478 expUncompacted: []roachpb.Span{ 479 {Key: key("h0"), EndKey: key("i")}, 480 }, 481 }, 482 // Quadruple suggestion which can be aggregated into a single compaction. 483 { 484 name: "quadruple suggestion which aggregates", 485 suggestions: []kvserverpb.SuggestedCompaction{ 486 { 487 StartKey: key("a"), EndKey: key("b"), 488 Compaction: kvserverpb.Compaction{ 489 Bytes: thresholdBytes.Default() / 4, 490 SuggestedAtNanos: nowNanos, 491 }, 492 }, 493 { 494 StartKey: key("e"), EndKey: key("f0"), 495 Compaction: kvserverpb.Compaction{ 496 Bytes: thresholdBytes.Default() / 4, 497 SuggestedAtNanos: nowNanos, 498 }, 499 }, 500 { 501 StartKey: key("g"), EndKey: key("q"), 502 Compaction: kvserverpb.Compaction{ 503 Bytes: thresholdBytes.Default() / 4, 504 SuggestedAtNanos: nowNanos, 505 }, 506 }, 507 { 508 StartKey: key("y"), EndKey: key("zzz"), 509 Compaction: kvserverpb.Compaction{ 510 Bytes: thresholdBytes.Default() - 3*(thresholdBytes.Default()/4), 511 SuggestedAtNanos: nowNanos, 512 }, 513 }, 514 }, 515 logicalBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 516 availableBytes: thresholdBytes.Default() * 100, // not going to trigger fractional threshold 517 expBytesCompacted: thresholdBytes.Default(), 518 expCompactions: []roachpb.Span{ 519 {Key: key("a"), EndKey: key("zzz")}, 520 }, 521 }, 522 } 523 524 for _, test := range testCases { 525 t.Run(test.name, func(t *testing.T) { 526 capacityFn := func() (roachpb.StoreCapacity, error) { 527 return roachpb.StoreCapacity{ 528 LogicalBytes: test.logicalBytes, 529 Available: test.availableBytes, 530 }, nil 531 } 532 compactor, we, compactionCount, cleanup := testSetup(capacityFn) 533 defer cleanup() 534 // Shorten wait times for compactor processing. 535 minInterval.Override(&compactor.st.SV, time.Millisecond) 536 537 // Add a key so we can test that suggestions that span live data are 538 // ignored. 539 if err := we.Put(storage.MakeMVCCMetadataKey(key("5")), nil); err != nil { 540 t.Fatal(err) 541 } 542 543 for _, sc := range test.suggestions { 544 compactor.Suggest(context.Background(), sc) 545 } 546 547 // If we expect no compaction, pause to ensure test will fail if 548 // a compaction happens. Note that 10ms is not enough to make 549 // this fail all the time, but it will surely trigger a failure 550 // most of the time. 551 if len(test.expCompactions) == 0 { 552 time.Sleep(10 * time.Millisecond) 553 } 554 testutils.SucceedsSoon(t, func() error { 555 comps := we.GetCompactions() 556 if !reflect.DeepEqual(test.expCompactions, comps) { 557 return fmt.Errorf("expected %+v; got %+v", test.expCompactions, comps) 558 } 559 if a, e := compactor.Metrics.BytesCompacted.Count(), test.expBytesCompacted; a != e { 560 return fmt.Errorf("expected bytes compacted %d; got %d", e, a) 561 } 562 if a, e := compactor.Metrics.CompactionSuccesses.Count(), int64(len(test.expCompactions)); a != e { 563 return fmt.Errorf("expected compactions %d; got %d", e, a) 564 } 565 if a, e := atomic.LoadInt32(compactionCount), int32(len(test.expCompactions)); a != e { 566 return fmt.Errorf("expected compactions %d; got %d", e, a) 567 } 568 if len(test.expCompactions) == 0 { 569 if cn := compactor.Metrics.CompactingNanos.Count(); cn > 0 { 570 return fmt.Errorf("expected compaction time to be 0; got %d", cn) 571 } 572 } else { 573 expNanos := int64(len(test.expCompactions)) * int64(testCompactionLatency) 574 if a, e := compactor.Metrics.CompactingNanos.Count(), expNanos; a < e { 575 return fmt.Errorf("expected compacting nanos > %d; got %d", e, a) 576 } 577 } 578 // Read the remaining suggestions in the queue; verify compacted 579 // spans have been cleared and uncompacted spans remain. 580 var idx int 581 return we.Iterate( 582 keys.LocalStoreSuggestedCompactionsMin, 583 keys.LocalStoreSuggestedCompactionsMax, 584 func(kv storage.MVCCKeyValue) (bool, error) { 585 start, end, err := keys.DecodeStoreSuggestedCompactionKey(kv.Key.Key) 586 if err != nil { 587 t.Fatalf("failed to decode suggested compaction key: %+v", err) 588 } 589 if idx >= len(test.expUncompacted) { 590 return true, fmt.Errorf("found unexpected uncompacted span %s-%s", start, end) 591 } 592 if !start.Equal(test.expUncompacted[idx].Key) || !end.Equal(test.expUncompacted[idx].EndKey) { 593 return true, fmt.Errorf("found unexpected uncompacted span %s-%s; expected %s-%s", 594 start, end, test.expUncompacted[idx].Key, test.expUncompacted[idx].EndKey) 595 } 596 idx++ 597 return false, nil // continue iteration 598 }, 599 ) 600 }) 601 }) 602 } 603 } 604 605 // TestCompactorDeadlockOnStart prevents regression of an issue that 606 // could cause nodes to lock up during the boot sequence. The 607 // compactor may receive suggestions before starting the goroutine, 608 // yet starting the goroutine could block on the suggestions channel, 609 // deadlocking the call to (Compactor).Start and thus the main node 610 // boot goroutine. This was observed in practice. 611 func TestCompactorDeadlockOnStart(t *testing.T) { 612 stopper := stop.NewStopper() 613 defer stopper.Stop(context.Background()) 614 615 eng := newWrappedEngine() 616 stopper.AddCloser(eng) 617 capFn := func() (roachpb.StoreCapacity, error) { 618 return roachpb.StoreCapacity{}, errors.New("never called") 619 } 620 doneFn := func(_ context.Context) {} 621 st := cluster.MakeTestingClusterSettings() 622 compactor := NewCompactor(st, eng, capFn, doneFn) 623 624 compactor.ch <- struct{}{} 625 626 compactor.Start(context.Background(), stopper) 627 } 628 629 // TestCompactorProcessingInitialization verifies that a compactor gets 630 // started with processing if the queue is non-empty. 631 func TestCompactorProcessingInitialization(t *testing.T) { 632 defer leaktest.AfterTest(t)() 633 634 capacityFn := func() (roachpb.StoreCapacity, error) { 635 return roachpb.StoreCapacity{LogicalBytes: 100 * thresholdBytes.Default()}, nil 636 } 637 compactor, we, compactionCount, cleanup := testSetup(capacityFn) 638 defer cleanup() 639 640 // Add a suggested compaction -- this won't get processed by this 641 // compactor for an hour. 642 minInterval.Override(&compactor.st.SV, time.Hour) 643 compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{ 644 StartKey: key("a"), EndKey: key("b"), 645 Compaction: kvserverpb.Compaction{ 646 Bytes: thresholdBytes.Default(), 647 SuggestedAtNanos: timeutil.Now().UnixNano(), 648 }, 649 }) 650 651 // Create a new fast compactor with a short wait time for processing, 652 // using the same engine so that it sees a non-empty queue. 653 stopper := stop.NewStopper() 654 doneFn := func(_ context.Context) { atomic.AddInt32(compactionCount, 1) } 655 st := cluster.MakeTestingClusterSettings() 656 fastCompactor := NewCompactor(st, we, capacityFn, doneFn) 657 minInterval.Override(&fastCompactor.st.SV, time.Millisecond) 658 fastCompactor.Start(context.Background(), stopper) 659 defer stopper.Stop(context.Background()) 660 661 testutils.SucceedsSoon(t, func() error { 662 comps := we.GetCompactions() 663 expComps := []roachpb.Span{{Key: key("a"), EndKey: key("b")}} 664 if !reflect.DeepEqual(expComps, comps) { 665 return fmt.Errorf("expected %+v; got %+v", expComps, comps) 666 } 667 if a, e := atomic.LoadInt32(compactionCount), int32(1); a != e { 668 return fmt.Errorf("expected %d; got %d", e, a) 669 } 670 return nil 671 }) 672 } 673 674 // TestCompactorCleansUpOldRecords verifies that records which exceed 675 // the maximum age are deleted if they cannot be compacted. 676 func TestCompactorCleansUpOldRecords(t *testing.T) { 677 defer leaktest.AfterTest(t)() 678 679 capacityFn := func() (roachpb.StoreCapacity, error) { 680 return roachpb.StoreCapacity{ 681 LogicalBytes: 100 * thresholdBytes.Default(), 682 Available: 100 * thresholdBytes.Default(), 683 }, nil 684 } 685 compactor, we, compactionCount, cleanup := testSetup(capacityFn) 686 minInterval.Override(&compactor.st.SV, time.Millisecond) 687 // NB: The compactor had a bug where it would never revisit skipped compactions 688 // alone when there wasn't also a new suggestion. Making the max age larger 689 // than the min interval exercises that code path (flakily). 690 maxSuggestedCompactionRecordAge.Override(&compactor.st.SV, 5*time.Millisecond) 691 defer cleanup() 692 693 // Add a suggested compaction that won't get processed because it's 694 // not over any of the thresholds. 695 compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{ 696 StartKey: key("a"), EndKey: key("b"), 697 Compaction: kvserverpb.Compaction{ 698 Bytes: thresholdBytes.Default() - 1, 699 SuggestedAtNanos: timeutil.Now().UnixNano(), 700 }, 701 }) 702 703 // Verify that the record is deleted without a compaction and that the 704 // bytes are recorded as having been skipped. 705 testutils.SucceedsSoon(t, func() error { 706 comps := we.GetCompactions() 707 if !reflect.DeepEqual([]roachpb.Span(nil), comps) { 708 return fmt.Errorf("expected nil compactions; got %+v", comps) 709 } 710 if a, e := compactor.Metrics.BytesSkipped.Count(), thresholdBytes.Get(&compactor.st.SV)-1; a != e { 711 return fmt.Errorf("expected skipped bytes %d; got %d", e, a) 712 } 713 if a, e := atomic.LoadInt32(compactionCount), int32(0); a != e { 714 return fmt.Errorf("expected compactions processed %d; got %d", e, a) 715 } 716 // Verify compaction queue is empty. 717 if bytesQueued, err := compactor.examineQueue(context.Background()); err != nil || bytesQueued > 0 { 718 return fmt.Errorf("compaction queue not empty (%d bytes) or err %v", bytesQueued, err) 719 } 720 return nil 721 }) 722 } 723 724 // TestCompactorDisabled that a disabled compactor throws away past and future 725 // suggestions. 726 func TestCompactorDisabled(t *testing.T) { 727 defer leaktest.AfterTest(t)() 728 729 threshold := int64(10) 730 731 capacityFn := func() (roachpb.StoreCapacity, error) { 732 return roachpb.StoreCapacity{ 733 LogicalBytes: 100 * threshold, 734 Available: 100 * threshold, 735 }, nil 736 } 737 compactor, we, compactionCount, cleanup := testSetup(capacityFn) 738 minInterval.Override(&compactor.st.SV, time.Millisecond) 739 maxSuggestedCompactionRecordAge.Override(&compactor.st.SV, 24*time.Hour) // large 740 thresholdBytesAvailableFraction.Override(&compactor.st.SV, 0.0) // disable 741 thresholdBytesUsedFraction.Override(&compactor.st.SV, 0.0) // disable 742 defer cleanup() 743 744 compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{ 745 StartKey: key("a"), EndKey: key("b"), 746 Compaction: kvserverpb.Compaction{ 747 // Suggest so little that this suggestion plus the one below stays below 748 // the threshold. Otherwise this test gets racy and difficult to fix 749 // without remodeling the compactor. 750 Bytes: threshold / 3, 751 SuggestedAtNanos: timeutil.Now().UnixNano(), 752 }, 753 }) 754 755 enabled.Override(&compactor.st.SV, false) 756 757 compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{ 758 // Note that we don't reuse the same interval above or we hit another race, 759 // in which the compactor discards the first suggestion and wipes out the 760 // second one with it, without incrementing the discarded metric. 761 StartKey: key("b"), EndKey: key("c"), 762 Compaction: kvserverpb.Compaction{ 763 Bytes: threshold / 3, 764 SuggestedAtNanos: timeutil.Now().UnixNano(), 765 }, 766 }) 767 768 // Verify that the record is deleted without a compaction and that the 769 // bytes are recorded as having been skipped. 770 testutils.SucceedsSoon(t, func() error { 771 if a, e := atomic.LoadInt32(compactionCount), int32(0); a != e { 772 t.Fatalf("expected compactions processed %d; got %d", e, a) 773 } 774 775 comps := we.GetCompactions() 776 if !reflect.DeepEqual([]roachpb.Span(nil), comps) { 777 return fmt.Errorf("expected nil compactions; got %+v", comps) 778 } 779 if a, e := compactor.Metrics.BytesSkipped.Count(), 2*(threshold/3); a != e { 780 return fmt.Errorf("expected skipped bytes %d; got %d", e, a) 781 } 782 783 // Verify compaction queue is empty. 784 if bytesQueued, err := compactor.examineQueue(context.Background()); err != nil || bytesQueued > 0 { 785 return fmt.Errorf("compaction queue not empty (%d bytes) or err %v", bytesQueued, err) 786 } 787 return nil 788 }) 789 }