Skip to content

Commit 148f6c9

Browse files
mszeszko-metameta-codesync[bot]
authored andcommitted
Replace GC cutoff threshold with a constant (facebook#14272)
Summary: Pull Request resolved: facebook#14272 All production deployments have the hardcoded (non-configurable) default for`garbage_collection_cutoff = 0.25`. This change removes the configurable option and replaces it with a fixed constant `kGarbageCollectionCutoff = 0.25`, simplifying the configuration surface. Changes: - Remove `garbage_collection_cutoff` from `BlobDBOptions` - Add `kGarbageCollectionCutoff` constant (0.25) in blob_db_impl.cc - Remove `--blob_db_gc_cutoff` flag from db_bench tool and db_stress - Update tests to work with the fixed cutoff value Reviewed By: xingbowang Differential Revision: D91088998 fbshipit-source-id: 820fc7f1ad4c3fe8a15f22a92cd53fb96c56c6e1
1 parent 6b5ccbb commit 148f6c9

8 files changed

Lines changed: 101 additions & 62 deletions

File tree

db_stress_tool/db_stress_common.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ DECLARE_bool(use_blob_db);
300300
DECLARE_uint64(blob_db_bytes_per_sync);
301301
DECLARE_uint64(blob_db_file_size);
302302
DECLARE_bool(blob_db_enable_gc);
303-
DECLARE_double(blob_db_gc_cutoff);
304303

305304
// Options for integrated BlobDB
306305
DECLARE_bool(allow_setting_blob_options_dynamically);

db_stress_tool/db_stress_gflags.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,11 +439,6 @@ DEFINE_bool(
439439
ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().enable_garbage_collection,
440440
"[Stacked BlobDB] Enable BlobDB garbage collection.");
441441

442-
DEFINE_double(
443-
blob_db_gc_cutoff,
444-
ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().garbage_collection_cutoff,
445-
"[Stacked BlobDB] Cutoff ratio for BlobDB garbage collection.");
446-
447442
// Options for integrated BlobDB
448443
DEFINE_bool(allow_setting_blob_options_dynamically, false,
449444
"[Integrated BlobDB] Allow setting blob options dynamically.");

db_stress_tool/db_stress_test_base.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3847,7 +3847,6 @@ void StressTest::Open(SharedState* shared, bool reopen) {
38473847
blob_db_options.bytes_per_sync = FLAGS_blob_db_bytes_per_sync;
38483848
blob_db_options.blob_file_size = FLAGS_blob_db_file_size;
38493849
blob_db_options.enable_garbage_collection = FLAGS_blob_db_enable_gc;
3850-
blob_db_options.garbage_collection_cutoff = FLAGS_blob_db_gc_cutoff;
38513850

38523851
blob_db::BlobDB* blob_db = nullptr;
38533852
s = blob_db::BlobDB::Open(options_, blob_db_options, FLAGS_db,

tools/db_bench_tool.cc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,11 +1050,6 @@ DEFINE_bool(
10501050
ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().enable_garbage_collection,
10511051
"[Stacked BlobDB] Enable BlobDB garbage collection.");
10521052

1053-
DEFINE_double(
1054-
blob_db_gc_cutoff,
1055-
ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().garbage_collection_cutoff,
1056-
"[Stacked BlobDB] Cutoff ratio for BlobDB garbage collection.");
1057-
10581053
DEFINE_uint64(blob_db_max_db_size,
10591054
ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().max_db_size,
10601055
"[Stacked BlobDB] Max size limit of the directory where blob "
@@ -5181,7 +5176,6 @@ class Benchmark {
51815176
// Stacked BlobDB
51825177
blob_db::BlobDBOptions blob_db_options;
51835178
blob_db_options.enable_garbage_collection = FLAGS_blob_db_enable_gc;
5184-
blob_db_options.garbage_collection_cutoff = FLAGS_blob_db_gc_cutoff;
51855179
blob_db_options.max_db_size = FLAGS_blob_db_max_db_size;
51865180
blob_db_options.ttl_range_secs = FLAGS_blob_db_ttl_range_secs;
51875181
blob_db_options.bytes_per_sync = FLAGS_blob_db_bytes_per_sync;

utilities/blob_db/blob_db.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ void BlobDBOptions::Dump(Logger* log) const {
8989
ROCKS_LOG_HEADER(
9090
log, " BlobDBOptions.enable_garbage_collection: %d",
9191
enable_garbage_collection);
92-
ROCKS_LOG_HEADER(
93-
log, " BlobDBOptions.garbage_collection_cutoff: %f",
94-
garbage_collection_cutoff);
9592
ROCKS_LOG_HEADER(
9693
log, " BlobDBOptions.disable_background_tasks: %d",
9794
disable_background_tasks);

utilities/blob_db/blob_db.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ struct BlobDBOptions {
6161
// by rewriting the remaining live blobs to new files.
6262
bool enable_garbage_collection = false;
6363

64-
// The cutoff in terms of blob file age for garbage collection. Blobs in
65-
// the oldest N non-TTL blob files will be rewritten when encountered during
66-
// compaction, where N = garbage_collection_cutoff * number_of_non_TTL_files.
67-
double garbage_collection_cutoff = 0.25;
68-
6964
// Disable all background job. Used for test only.
7065
bool disable_background_tasks = false;
7166

utilities/blob_db/blob_db_impl.cc

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343

4444
namespace ROCKSDB_NAMESPACE::blob_db {
4545

46+
// The cutoff in terms of blob file age for garbage collection. Blobs in the
47+
// oldest N non-TTL blob files will be rewritten when encountered during
48+
// compaction, where N = kGarbageCollectionCutoff * number_of_non_TTL_files.
49+
constexpr double kGarbageCollectionCutoff = 0.25;
50+
4651
bool BlobFileComparator::operator()(
4752
const std::shared_ptr<BlobFile>& lhs,
4853
const std::shared_ptr<BlobFile>& rhs) const {
@@ -138,12 +143,6 @@ Status BlobDBImpl::Open(std::vector<ColumnFamilyHandle*>* handles) {
138143
return Status::NotSupported("No blob directory in options");
139144
}
140145

141-
if (bdb_options_.garbage_collection_cutoff < 0.0 ||
142-
bdb_options_.garbage_collection_cutoff > 1.0) {
143-
return Status::InvalidArgument(
144-
"Garbage collection cutoff must be in the interval [0.0, 1.0]");
145-
}
146-
147146
// Temporarily disable compactions in the base DB during open; save the user
148147
// defined value beforehand so we can restore it once BlobDB is initialized.
149148
// Note: this is only needed if garbage collection is enabled.
@@ -1185,8 +1184,8 @@ void BlobDBImpl::GetCompactionContext(BlobCompactionContext* context,
11851184

11861185
if (!live_imm_non_ttl_blob_files_.empty()) {
11871186
auto it = live_imm_non_ttl_blob_files_.begin();
1188-
std::advance(it, bdb_options_.garbage_collection_cutoff *
1189-
live_imm_non_ttl_blob_files_.size());
1187+
std::advance(
1188+
it, kGarbageCollectionCutoff * live_imm_non_ttl_blob_files_.size());
11901189
context_gc->cutoff_file_number = it != live_imm_non_ttl_blob_files_.end()
11911190
? it->first
11921191
: std::numeric_limits<uint64_t>::max();

utilities/blob_db/blob_db_test.cc

Lines changed: 94 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,6 @@ TEST_F(BlobDBTest, SstFileManager) {
509509

510510
BlobDBOptions bdb_options;
511511
bdb_options.enable_garbage_collection = true;
512-
bdb_options.garbage_collection_cutoff = 1.0;
513512
Options db_options;
514513

515514
int files_scheduled_to_delete = 0;
@@ -527,20 +526,31 @@ TEST_F(BlobDBTest, SstFileManager) {
527526

528527
Open(bdb_options, db_options);
529528

530-
// Create one obselete file and clean it.
529+
// Create 4 blob files. With GC cutoff of 0.25, the oldest file (file 1)
530+
// will be in the GC zone: floor(0.25 * 4) = 1.
531531
ASSERT_OK(blob_db_->Put(WriteOptions(), "foo", "bar"));
532532
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
533533
ASSERT_EQ(1, blob_files.size());
534534
std::shared_ptr<BlobFile> bfile = blob_files[0];
535535
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
536+
537+
// Create 3 more blob files (files 2-4, outside GC zone).
538+
for (int i = 1; i < 4; i++) {
539+
ASSERT_OK(blob_db_->Put(WriteOptions(), "key" + std::to_string(i), "val"));
540+
blob_files = blob_db_impl()->TEST_GetBlobFiles();
541+
ASSERT_EQ(static_cast<size_t>(i + 1), blob_files.size());
542+
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[i]));
543+
}
544+
536545
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
537546
blob_db_impl()->TEST_DeleteObsoleteFiles();
538547

539548
// Even if SSTFileManager is not set, DB is creating a dummy one.
540549
ASSERT_EQ(1, files_scheduled_to_delete);
541550
Destroy();
542551
// Make sure that DestroyBlobDB() also goes through delete scheduler.
543-
ASSERT_EQ(2, files_scheduled_to_delete);
552+
// Remaining files: 3 original (files 2-4) + 1 GC output file = 4 files.
553+
ASSERT_EQ(5, files_scheduled_to_delete);
544554
SyncPoint::GetInstance()->DisableProcessing();
545555
sfm->WaitForEmptyTrash();
546556
}
@@ -606,20 +616,27 @@ TEST_F(BlobDBTest, SstFileManagerRestart) {
606616
TEST_F(BlobDBTest, SnapshotAndGarbageCollection) {
607617
BlobDBOptions bdb_options;
608618
bdb_options.enable_garbage_collection = true;
609-
bdb_options.garbage_collection_cutoff = 1.0;
610619
bdb_options.disable_background_tasks = true;
611620

612621
Options options;
613622
options.disable_auto_compactions = true;
614623

615-
// i = when to take snapshot
624+
// This test verifies that snapshots protect blob files from deletion during
625+
// garbage collection. With fixed GC cutoff of 0.25 and 8 immutable files,
626+
// floor(0.25 * 8) = 2 files are in the GC zone (files 1 and 2).
627+
//
628+
// We run 4 iterations with different snapshot timing:
629+
// i=0: snapshot after key1 (before key2) - protects file 1
630+
// i=1: snapshot after key2 (before key3) - protects files 1 and 2
631+
// i=2: snapshot after key9 (after all keys) - no protection needed
632+
// i=3: snapshot after Delete(key2) - no protection needed
616633
for (int i = 0; i < 4; i++) {
617634
Destroy();
618635
Open(bdb_options, options);
619636

620637
const Snapshot* snapshot = nullptr;
621638

622-
// First file
639+
// Create first blob file (will be in GC zone).
623640
ASSERT_OK(Put("key1", "value"));
624641
if (i == 0) {
625642
snapshot = blob_db_->GetSnapshot();
@@ -629,47 +646,75 @@ TEST_F(BlobDBTest, SnapshotAndGarbageCollection) {
629646
ASSERT_EQ(1, blob_files.size());
630647
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
631648

632-
// Second file
649+
// Create second blob file (will be in GC zone). We track this file
650+
// to verify it becomes obsolete after GC relocates its blob.
633651
ASSERT_OK(Put("key2", "value"));
634652
if (i == 1) {
635653
snapshot = blob_db_->GetSnapshot();
636654
}
637655

638656
blob_files = blob_db_impl()->TEST_GetBlobFiles();
639657
ASSERT_EQ(2, blob_files.size());
640-
auto bfile = blob_files[1];
641-
ASSERT_FALSE(bfile->Immutable());
642-
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
658+
auto gc_target_file = blob_files[1];
659+
ASSERT_FALSE(gc_target_file->Immutable());
660+
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(gc_target_file));
661+
662+
// Create files 3-8, all closed (these are outside GC zone).
663+
for (int j = 3; j <= 8; j++) {
664+
ASSERT_OK(Put("key" + std::to_string(j), "value"));
665+
blob_files = blob_db_impl()->TEST_GetBlobFiles();
666+
ASSERT_EQ(static_cast<size_t>(j), blob_files.size());
667+
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[j - 1]));
668+
}
643669

644-
// Third file
645-
ASSERT_OK(Put("key3", "value"));
670+
// Create file 9 but leave it open (mutable). Only immutable files are
671+
// counted for GC cutoff calculation.
672+
ASSERT_OK(Put("key9", "value"));
646673
if (i == 2) {
647674
snapshot = blob_db_->GetSnapshot();
648675
}
649676

677+
// Verify we have 9 total files (8 immutable + 1 mutable).
678+
blob_files = blob_db_impl()->TEST_GetBlobFiles();
679+
ASSERT_EQ(9, blob_files.size());
680+
681+
// Trigger GC via compaction. Blobs in files 1 and 2 will be relocated
682+
// to a new GC output file.
650683
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
651-
ASSERT_TRUE(bfile->Obsolete());
684+
685+
// Verify gc_target_file (file 2) is now obsolete.
686+
ASSERT_TRUE(gc_target_file->Obsolete());
687+
// Verify the obsolete sequence matches the latest sequence number.
652688
ASSERT_EQ(blob_db_->GetLatestSequenceNumber(),
653-
bfile->GetObsoleteSequence());
689+
gc_target_file->GetObsoleteSequence());
654690

655691
Delete("key2");
656692
if (i == 3) {
657693
snapshot = blob_db_->GetSnapshot();
658694
}
659695

660-
ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
696+
// Verify we now have 10 files (9 original + 1 GC output file).
697+
// Files 1 and 2 are obsolete but not yet deleted.
698+
ASSERT_EQ(10, blob_db_impl()->TEST_GetBlobFiles().size());
661699
blob_db_impl()->TEST_DeleteObsoleteFiles();
662700

663701
if (i >= 2) {
664-
// The snapshot shouldn't see data in bfile
665-
ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
702+
// Snapshot was taken after all keys were written, so it sees the
703+
// post-compaction state where blob indexes point to the GC output file.
704+
// Obsolete files 1 and 2 can be deleted immediately.
705+
// Verify 8 files remain (10 - 2 obsolete files deleted).
706+
ASSERT_EQ(8, blob_db_impl()->TEST_GetBlobFiles().size());
666707
blob_db_->ReleaseSnapshot(snapshot);
667708
} else {
668-
// The snapshot will see data in bfile, so the file shouldn't be deleted
669-
ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
709+
// Snapshot was taken before compaction completed, so it may still
710+
// reference blobs in the obsolete files. Files cannot be deleted.
711+
// Verify all 10 files still exist.
712+
ASSERT_EQ(10, blob_db_impl()->TEST_GetBlobFiles().size());
670713
blob_db_->ReleaseSnapshot(snapshot);
671714
blob_db_impl()->TEST_DeleteObsoleteFiles();
672-
ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
715+
// After releasing the snapshot, obsolete files can be deleted.
716+
// Verify 8 files remain.
717+
ASSERT_EQ(8, blob_db_impl()->TEST_GetBlobFiles().size());
673718
}
674719
}
675720
}
@@ -1154,7 +1199,6 @@ TEST_F(BlobDBTest, GarbageCollection) {
11541199
BlobDBOptions bdb_options;
11551200
bdb_options.blob_file_size = kBlobFileSize;
11561201
bdb_options.enable_garbage_collection = true;
1157-
bdb_options.garbage_collection_cutoff = 0.25;
11581202
bdb_options.disable_background_tasks = true;
11591203

11601204
Options options;
@@ -1238,8 +1282,9 @@ TEST_F(BlobDBTest, GarbageCollection) {
12381282

12391283
VerifyBaseDB(blob_value_versions);
12401284

1241-
const uint64_t cutoff = static_cast<uint64_t>(
1242-
bdb_options.garbage_collection_cutoff * kNumBlobFiles);
1285+
// GC cutoff is fixed at 0.25
1286+
constexpr double kGCCutoff = 0.25;
1287+
const uint64_t cutoff = static_cast<uint64_t>(kGCCutoff * kNumBlobFiles);
12431288
for (auto& pair : blob_index_versions) {
12441289
BlobIndexVersion& version = pair.second;
12451290

@@ -1290,40 +1335,56 @@ TEST_F(BlobDBTest, GarbageCollection) {
12901335
TEST_F(BlobDBTest, GarbageCollectionFailure) {
12911336
BlobDBOptions bdb_options;
12921337
bdb_options.enable_garbage_collection = true;
1293-
bdb_options.garbage_collection_cutoff = 1.0;
12941338
bdb_options.disable_background_tasks = true;
12951339

12961340
Options db_options;
12971341
db_options.statistics = CreateDBStatistics();
12981342

12991343
Open(bdb_options, db_options);
13001344

1301-
// Write a couple of valid blobs.
1345+
// Create 4 blob files. With fixed GC cutoff of 0.25, the oldest file
1346+
// (floor(0.25 * 4) = 1) will be in the GC zone.
1347+
// The first file contains valid blobs for "foo" and "dead".
13021348
ASSERT_OK(Put("foo", "bar"));
13031349
ASSERT_OK(Put("dead", "beef"));
13041350

1305-
// Write a fake blob reference into the base DB that points to a non-existing
1306-
// blob file.
1351+
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
1352+
ASSERT_EQ(blob_files.size(), 1);
1353+
auto first_file = blob_files[0];
1354+
uint64_t first_file_number = first_file->BlobFileNumber();
1355+
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(first_file));
1356+
1357+
// Create 3 more blob files (files 2-4, outside GC zone).
1358+
for (int i = 1; i < 4; i++) {
1359+
ASSERT_OK(Put("key" + std::to_string(i), "value"));
1360+
blob_files = blob_db_impl()->TEST_GetBlobFiles();
1361+
ASSERT_EQ(static_cast<size_t>(i + 1), blob_files.size());
1362+
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[i]));
1363+
}
1364+
1365+
// Write a fake blob index that points to the first file (in GC zone)
1366+
// but with an invalid offset beyond the file size. This will cause
1367+
// GC to fail when it tries to read this blob.
13071368
std::string blob_index;
1308-
BlobIndex::EncodeBlob(&blob_index, /* file_number */ 1000, /* offset */ 1234,
1369+
BlobIndex::EncodeBlob(&blob_index, first_file_number, /* offset */ 999999,
13091370
/* size */ 5678, kNoCompression);
13101371

13111372
WriteBatch batch;
13121373
ASSERT_OK(WriteBatchInternal::PutBlobIndex(
13131374
&batch, blob_db_->DefaultColumnFamily()->GetID(), "key", blob_index));
13141375
ASSERT_OK(blob_db_->GetRootDB()->Write(WriteOptions(), &batch));
13151376

1316-
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
1317-
ASSERT_EQ(blob_files.size(), 1);
1318-
auto blob_file = blob_files[0];
1319-
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_file));
1320-
1377+
// Verify compaction fails with IO error due to invalid blob offset.
13211378
ASSERT_TRUE(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)
13221379
.IsIOError());
13231380

13241381
const Statistics* const statistics = db_options.statistics.get();
13251382
assert(statistics);
13261383

1384+
// Verify GC statistics:
1385+
// - Relocated 2 keys ("foo" and "dead") with 7 bytes ("bar" + "beef")
1386+
// - Failed on "key" which has invalid blob offset
1387+
// - Created 1 new GC output file before failing
13271388
ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_FILES), 0);
13281389
ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_NEW_FILES), 1);
13291390
ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_FAILURES), 1);

0 commit comments

Comments
 (0)