Skip to content

Commit 5494bc1

Browse files
jaykoreanmeta-codesync[bot]
authored andcommitted
Add option to verify file checksum of output files (facebook#14433)
Summary: One of the follow ups from facebook#14103. Users will have the option to verify file checksums for all compaction output files before they are installed. This feature helps prevent corrupted SST files from being added to the LSM tree. Pull Request resolved: facebook#14433 Test Plan: Unit test added Reviewed By: archang19 Differential Revision: D95648000 Pulled By: jaykorean fbshipit-source-id: 512f3c1a7449b96a660865531f3537624c89a9cc
1 parent 91f227d commit 5494bc1

10 files changed

Lines changed: 121 additions & 10 deletions

File tree

db/compaction/compaction_job.cc

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "db/range_del_aggregator.h"
3434
#include "db/version_edit.h"
3535
#include "db/version_set.h"
36+
#include "file/file_util.h"
3637
#include "file/filename.h"
3738
#include "file/read_write_util.h"
3839
#include "file/sst_file_manager_impl.h"
@@ -903,17 +904,17 @@ Status CompactionJob::VerifyOutputFiles() {
903904
// use_direct_io_for_flush_and_compaction is true, we will regard this
904905
// verification as user reads since the goal is to cache it here for
905906
// further user reads
906-
ReadOptions verify_table_read_options(Env::IOActivity::kCompaction);
907-
verify_table_read_options.verify_checksums = true;
908-
verify_table_read_options.readahead_size =
907+
ReadOptions verification_read_options(Env::IOActivity::kCompaction);
908+
verification_read_options.verify_checksums = true;
909+
verification_read_options.readahead_size =
909910
file_options_for_read_.compaction_readahead_size;
910911

911912
std::unique_ptr<TableReader> table_reader_guard;
912913
TableReader* table_reader_ptr = table_reader_guard.get();
913-
verify_table_read_options.rate_limiter_priority =
914+
verification_read_options.rate_limiter_priority =
914915
GetRateLimiterPriority();
915916
InternalIterator* iter = cfd->table_cache()->NewIterator(
916-
verify_table_read_options, file_options_, cfd->internal_comparator(),
917+
verification_read_options, file_options_, cfd->internal_comparator(),
917918
output_file.meta,
918919
/*range_del_agg=*/nullptr, compact_->compaction->mutable_cf_options(),
919920
/*table_reader_ptr=*/&table_reader_ptr,
@@ -941,12 +942,17 @@ Status CompactionJob::VerifyOutputFiles() {
941942
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
942943
const bool should_verify_iteration =
943944
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
945+
const bool should_verify_file_checksum =
946+
!!(verify_output_flags &
947+
VerifyOutputFlags::kVerifyFileChecksum) &&
948+
db_options_.file_checksum_gen_factory != nullptr &&
949+
output_file.meta.file_checksum != kUnknownFileChecksum;
944950
if (should_verify_block_checksum) {
945951
assert(table_reader_ptr != nullptr);
946952
// If verifying iteration as well, verify meta blocks here only to
947953
// avoid redundant checks on data blocks
948954
s = table_reader_ptr->VerifyChecksum(
949-
verify_table_read_options, TableReaderCaller::kCompaction,
955+
verification_read_options, TableReaderCaller::kCompaction,
950956
/*meta_blocks_only=*/should_verify_iteration);
951957
}
952958
if (s.ok() && should_verify_iteration) {
@@ -967,6 +973,24 @@ Status CompactionJob::VerifyOutputFiles() {
967973
"was computed when written");
968974
}
969975
}
976+
if (s.ok() && should_verify_file_checksum) {
977+
std::string file_checksum;
978+
std::string file_checksum_func_name;
979+
std::string fname =
980+
GetTableFileName(output_file.meta.fd.GetNumber());
981+
s = GenerateOneFileChecksum(
982+
fs_.get(), fname, db_options_.file_checksum_gen_factory.get(),
983+
output_file.meta.file_checksum_func_name, &file_checksum,
984+
&file_checksum_func_name,
985+
verification_read_options.readahead_size,
986+
db_options_.allow_mmap_reads, io_tracer_,
987+
db_options_.rate_limiter.get(), verification_read_options,
988+
stats_, db_options_.clock, file_options_for_read_);
989+
if (s.ok() && file_checksum != output_file.meta.file_checksum) {
990+
s = Status::Corruption(
991+
"File checksum mismatch for compaction output file " + fname);
992+
}
993+
}
970994
}
971995
}
972996

db/compaction/compaction_service_test.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,8 @@ TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
11131113
VerifyOutputFlags::kVerifyBlockChecksum,
11141114
VerifyOutputFlags::kEnableForRemoteCompaction |
11151115
VerifyOutputFlags::kVerifyIteration,
1116+
VerifyOutputFlags::kEnableForRemoteCompaction |
1117+
VerifyOutputFlags::kVerifyFileChecksum,
11161118
VerifyOutputFlags::kVerifyAll}) {
11171119
SCOPED_TRACE(
11181120
"verify_output_flags=" +
@@ -1124,6 +1126,7 @@ TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
11241126
options.disable_auto_compactions = true;
11251127
options.paranoid_file_checks = false;
11261128
options.verify_output_flags = verify_output_flags;
1129+
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
11271130
ReopenWithCompactionService(&options);
11281131
GenerateTestData();
11291132

@@ -1159,10 +1162,13 @@ TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
11591162
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
11601163
const bool should_verify_iteration =
11611164
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
1165+
const bool should_verify_file_checksum =
1166+
!!(verify_output_flags & VerifyOutputFlags::kVerifyFileChecksum);
11621167

11631168
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
11641169
if (is_enabled_for_remote_compaction &&
1165-
(should_verify_block_checksum || should_verify_iteration)) {
1170+
(should_verify_block_checksum || should_verify_iteration ||
1171+
should_verify_file_checksum)) {
11661172
ASSERT_NOK(s);
11671173
ASSERT_TRUE(s.IsCorruption());
11681174
} else {

db/db_compaction_test.cc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
#include "db/db_test_util.h"
1515
#include "db/dbformat.h"
1616
#include "env/mock_env.h"
17+
#include "file/filename.h"
1718
#include "port/port.h"
1819
#include "port/stack_trace.h"
1920
#include "rocksdb/advanced_options.h"
2021
#include "rocksdb/concurrent_task_limiter.h"
2122
#include "rocksdb/experimental.h"
23+
#include "rocksdb/file_checksum.h"
2224
#include "rocksdb/iostats_context.h"
2325
#include "rocksdb/sst_file_writer.h"
2426
#include "test_util/mock_time_env.h"
@@ -11735,6 +11737,59 @@ TEST_F(DBCompactionTest, RoundRobinCleanCutWithSharedBoundary) {
1173511737
ASSERT_EQ(Get("key" + std::to_string(k)), "v4");
1173611738
}
1173711739
}
11740+
11741+
TEST_F(DBCompactionTest, VerifyFileChecksumOnCompactionOutput) {
11742+
Options options = CurrentOptions();
11743+
options.disable_auto_compactions = true;
11744+
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
11745+
options.verify_output_flags = VerifyOutputFlags::kVerifyFileChecksum |
11746+
VerifyOutputFlags::kEnableForLocalCompaction;
11747+
DestroyAndReopen(options);
11748+
11749+
// Create 2 L0 files to trigger compaction
11750+
for (int i = 0; i < 10; i++) {
11751+
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
11752+
}
11753+
ASSERT_OK(Flush());
11754+
11755+
for (int i = 5; i < 15; i++) {
11756+
ASSERT_OK(Put(Key(i), "value2_" + std::to_string(i)));
11757+
}
11758+
ASSERT_OK(Flush());
11759+
11760+
// Corrupt output files right before verification
11761+
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
11762+
"CompactionJob::Run:BeforeVerify", [&](void* /*arg*/) {
11763+
// Find and corrupt the newest SST file (compaction output)
11764+
std::vector<std::string> filenames;
11765+
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
11766+
uint64_t max_number = 0;
11767+
std::string target_fname;
11768+
for (const auto& f : filenames) {
11769+
uint64_t number;
11770+
FileType type;
11771+
if (ParseFileName(f, &number, &type) && type == kTableFile &&
11772+
number > max_number) {
11773+
max_number = number;
11774+
target_fname = dbname_ + "/" + f;
11775+
}
11776+
}
11777+
ASSERT_FALSE(target_fname.empty());
11778+
ASSERT_OK(test::CorruptFile(env_, target_fname, 0, 1,
11779+
false /* verifyChecksum */));
11780+
});
11781+
SyncPoint::GetInstance()->EnableProcessing();
11782+
11783+
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
11784+
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
11785+
ASSERT_TRUE(
11786+
std::strstr(s.getState(), "File checksum mismatch for compaction output"))
11787+
<< s.ToString();
11788+
11789+
SyncPoint::GetInstance()->DisableProcessing();
11790+
SyncPoint::GetInstance()->ClearAllCallBacks();
11791+
}
11792+
1173811793
} // namespace ROCKSDB_NAMESPACE
1173911794

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

db_stress_tool/db_stress_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ DECLARE_bool(verification_only);
287287
DECLARE_string(last_level_temperature);
288288
DECLARE_string(default_write_temperature);
289289
DECLARE_string(default_temperature);
290+
DECLARE_uint32(verify_output_flags);
290291
DECLARE_bool(paranoid_memory_checks);
291292
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
292293

db_stress_tool/db_stress_gflags.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,10 @@ DEFINE_uint32(uncache_aggressiveness,
15361536
"obsolete. 0 = disabled, 1 = minimum, 100 = moderate, 10000 = "
15371537
"normal max");
15381538

1539+
DEFINE_uint32(verify_output_flags, 0,
1540+
"Sets CF option verify_output_flags as a uint32_t bitmask. "
1541+
"See VerifyOutputFlags enum for bit definitions.");
1542+
15391543
DEFINE_bool(paranoid_memory_checks,
15401544
ROCKSDB_NAMESPACE::Options().paranoid_memory_checks,
15411545
"Sets CF option paranoid_memory_checks.");

db_stress_tool/db_stress_test_base.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4532,6 +4532,8 @@ void InitializeOptionsFromFlags(
45324532
options.memtable_protection_bytes_per_key =
45334533
FLAGS_memtable_protection_bytes_per_key;
45344534
options.block_protection_bytes_per_key = FLAGS_block_protection_bytes_per_key;
4535+
options.verify_output_flags =
4536+
static_cast<VerifyOutputFlags>(FLAGS_verify_output_flags);
45354537
options.paranoid_memory_checks = FLAGS_paranoid_memory_checks;
45364538
options.memtable_veirfy_per_key_checksum_on_seek =
45374539
FLAGS_memtable_veirfy_per_key_checksum_on_seek;

include/rocksdb/advanced_options.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,7 @@ enum class VerifyOutputFlags : uint32_t {
213213
// by comparing the one inserted into a
214214
// file, and what is read back.
215215

216-
// TODO - Implement
217-
// kVerifyFileChecksum = 1 << 2, // Verify file-level checksum
216+
kVerifyFileChecksum = 1 << 2, // Verify file-level checksum
218217

219218
// Second set of bits: when to enable verification
220219
kEnableForLocalCompaction = 1 << 10, // Enable for local compaction

options/options_settable_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
694694
"memtable_op_scan_flush_trigger=123;"
695695
"memtable_avg_op_scan_flush_trigger=12;"
696696
"cf_allow_ingest_behind=1;"
697-
"verify_output_flags=2049;",
697+
"verify_output_flags=2053;",
698698
new_options));
699699

700700
ASSERT_NE(new_options->blob_cache.get(), nullptr);

options/options_test.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,19 @@ TEST_F(OptionsTest, MutableCFOptions) {
17521752
config_options, cf_opts, {{"paranoid_file_checks", "false"}}, &cf_opts));
17531753
ASSERT_EQ(cf_opts.paranoid_file_checks, false);
17541754

1755+
// Test verify_output_flags with kVerifyFileChecksum (bit 2)
1756+
// 2052 = 4 (kVerifyFileChecksum) | 2048 (kEnableForRemoteCompaction)
1757+
ASSERT_OK(GetColumnFamilyOptionsFromString(
1758+
config_options, cf_opts, "verify_output_flags=2052;", &cf_opts));
1759+
ASSERT_NE(
1760+
(cf_opts.verify_output_flags & VerifyOutputFlags::kVerifyFileChecksum),
1761+
VerifyOutputFlags::kVerifyNone);
1762+
ASSERT_NE((cf_opts.verify_output_flags &
1763+
VerifyOutputFlags::kEnableForRemoteCompaction),
1764+
VerifyOutputFlags::kVerifyNone);
1765+
ASSERT_EQ(
1766+
(cf_opts.verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum),
1767+
VerifyOutputFlags::kVerifyNone);
17551768
// Should replace the factory with the new setting
17561769
ASSERT_OK(GetColumnFamilyOptionsFromMap(
17571770
config_options, cf_opts,

tools/db_crashtest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,13 @@ def apply_random_seed_per_iteration():
429429
"check_multiget_entity_consistency": lambda: random.choice([0, 0, 0, 1]),
430430
"use_timed_put_one_in": lambda: random.choice([0] * 7 + [1, 5, 10]),
431431
"universal_max_read_amp": lambda: random.choice([-1] * 3 + [0, 4, 10]),
432+
# verify_output_flags is a bitmask: bits 0-2 are verification types
433+
# (block checksum, iteration, file checksum), bits 10-11 are when to
434+
# enable (local compaction, remote compaction). 0x407 = all types +
435+
# local, 0xC07 = all types + local + remote, 0xFFFFFFFF = all.
436+
"verify_output_flags": lambda: random.choice(
437+
[0] * 3 + [0x407, 0xC07, 0xFFFFFFFF]
438+
),
432439
"paranoid_memory_checks": lambda: random.choice([0] * 7 + [1]),
433440
"memtable_veirfy_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
434441
"allow_unprepared_value": lambda: random.choice([0, 1]),

0 commit comments

Comments
 (0)