Skip to content

Commit e44a99f

Browse files
xingbowangmeta-codesync[bot]
authored andcommitted
Fix false positive corruption in compaction output verification (facebook#14456)
Summary: Fix a bug where `VerifyOutputFiles()` produces false positive "Key-value checksum of compaction output doesn't match what was computed when written" errors when `verify_output_flags` includes `kVerifyIteration` but `paranoid_file_checks` is false. The root cause is a hash enable flag mismatch between writing and verification: - During compaction writing (`OpenCompactionOutputFile`), the `OutputValidator` hash computation was gated solely by `paranoid_file_checks_`. When false, `enable_hash=false` and the hash stays at 0. - During verification (`VerifyOutputFiles`), a new `OutputValidator` is always created with `enable_hash=true`, computing a non-zero hash. - `CompareValidator()` then compares 0 vs non-zero, producing a false positive corruption. This was exposed by the crash test randomization of `verify_output_flags` added in facebook#14433. Before that change, `verify_output_flags` was always 0, so `kVerifyIteration` was only exercised via `paranoid_file_checks=true` (which correctly enabled the hash during writing). The fix ensures hash computation is enabled during writing whenever either `paranoid_file_checks_` is true OR `verify_output_flags` includes `kVerifyIteration`. Pull Request resolved: facebook#14456 Test Plan: - Added `DBCompactionTest.VerifyIterationWithoutParanoidFileChecks` - Added `DBCompactionTest.VerifyAllOutputFlagsWithoutParanoidFileChecks` - Round-trip verified: tests FAIL without fix, PASS with fix Reviewed By: jaykorean Differential Revision: D96371769 Pulled By: xingbowang fbshipit-source-id: 2f7406327496c7b541e4fa2668894df89eb813e8
1 parent 5494bc1 commit e44a99f

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

db/compaction/compaction_job.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,8 +2494,16 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
24942494
return s;
24952495
}
24962496

2497+
// Enable hash computation if paranoid_file_checks is on or if
2498+
// verify_output_flags includes kVerifyIteration, so that
2499+
// VerifyOutputFiles() can compare the hash of the written data
2500+
// against a re-read of the output file.
2501+
bool enable_output_hash =
2502+
paranoid_file_checks_ ||
2503+
!!(sub_compact->compaction->mutable_cf_options().verify_output_flags &
2504+
VerifyOutputFlags::kVerifyIteration);
24972505
outputs.AddOutput(std::move(meta), cfd->internal_comparator(),
2498-
paranoid_file_checks_);
2506+
enable_output_hash);
24992507
}
25002508

25012509
writable_file->SetIOPriority(GetRateLimiterPriority());
@@ -2928,12 +2936,17 @@ void CompactionJob::RestoreCompactionOutputs(
29282936

29292937
const auto& output_files = subcompaction_progress_per_level.GetOutputFiles();
29302938

2939+
const bool enable_output_hash =
2940+
paranoid_file_checks_ ||
2941+
!!(compact_->compaction->mutable_cf_options().verify_output_flags &
2942+
VerifyOutputFlags::kVerifyIteration);
2943+
29312944
for (size_t i = 0; i < output_files.size(); i++) {
29322945
FileMetaData file_copy = output_files[i];
29332946

29342947
outputs_to_restore->AddOutput(std::move(file_copy),
29352948
cfd->internal_comparator(),
2936-
paranoid_file_checks_, true /* finished */);
2949+
enable_output_hash, true /* finished */);
29372950

29382951
outputs_to_restore->UpdateTableProperties(
29392952
*output_files_table_properties[i]);

db/db_compaction_test.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11790,6 +11790,82 @@ TEST_F(DBCompactionTest, VerifyFileChecksumOnCompactionOutput) {
1179011790
SyncPoint::GetInstance()->ClearAllCallBacks();
1179111791
}
1179211792

11793+
// Regression test: verify_output_flags with kVerifyIteration should work
11794+
// correctly even when paranoid_file_checks is false. Before the fix, the
11795+
// OutputValidator hash was only computed during writing when
11796+
// paranoid_file_checks was true, but the verification always computed the
11797+
// hash, leading to a false positive "Key-value checksum of compaction output
11798+
// doesn't match" error.
11799+
TEST_F(DBCompactionTest, VerifyIterationWithoutParanoidFileChecks) {
11800+
Options options = CurrentOptions();
11801+
options.disable_auto_compactions = true;
11802+
options.paranoid_file_checks = false;
11803+
options.verify_output_flags = VerifyOutputFlags::kVerifyIteration |
11804+
VerifyOutputFlags::kEnableForLocalCompaction;
11805+
DestroyAndReopen(options);
11806+
11807+
// Create 2 L0 files to trigger compaction
11808+
for (int i = 0; i < 10; i++) {
11809+
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
11810+
}
11811+
ASSERT_OK(Flush());
11812+
11813+
for (int i = 5; i < 15; i++) {
11814+
ASSERT_OK(Put(Key(i), "value2_" + std::to_string(i)));
11815+
}
11816+
ASSERT_OK(Flush());
11817+
11818+
// Compaction should succeed without false corruption errors
11819+
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
11820+
11821+
// Verify data is intact
11822+
for (int i = 0; i < 15; i++) {
11823+
std::string expected;
11824+
if (i >= 5) {
11825+
expected = "value2_" + std::to_string(i);
11826+
} else {
11827+
expected = "value" + std::to_string(i);
11828+
}
11829+
ASSERT_EQ(Get(Key(i)), expected);
11830+
}
11831+
}
11832+
11833+
// Also test all verification types combined without paranoid_file_checks
11834+
TEST_F(DBCompactionTest, VerifyAllOutputFlagsWithoutParanoidFileChecks) {
11835+
Options options = CurrentOptions();
11836+
options.disable_auto_compactions = true;
11837+
options.paranoid_file_checks = false;
11838+
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
11839+
options.verify_output_flags = VerifyOutputFlags::kVerifyBlockChecksum |
11840+
VerifyOutputFlags::kVerifyIteration |
11841+
VerifyOutputFlags::kVerifyFileChecksum |
11842+
VerifyOutputFlags::kEnableForLocalCompaction;
11843+
DestroyAndReopen(options);
11844+
11845+
for (int i = 0; i < 10; i++) {
11846+
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
11847+
}
11848+
ASSERT_OK(Flush());
11849+
11850+
for (int i = 5; i < 15; i++) {
11851+
ASSERT_OK(Put(Key(i), "value2_" + std::to_string(i)));
11852+
}
11853+
ASSERT_OK(Flush());
11854+
11855+
// Compaction should succeed with all verification types enabled
11856+
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
11857+
11858+
for (int i = 0; i < 15; i++) {
11859+
std::string expected;
11860+
if (i >= 5) {
11861+
expected = "value2_" + std::to_string(i);
11862+
} else {
11863+
expected = "value" + std::to_string(i);
11864+
}
11865+
ASSERT_EQ(Get(Key(i)), expected);
11866+
}
11867+
}
11868+
1179311869
} // namespace ROCKSDB_NAMESPACE
1179411870

1179511871
int main(int argc, char** argv) {

0 commit comments

Comments
 (0)