Skip to content

Commit d4b8274

Browse files
committed
Add API to verify whole sst file checksum (facebook#7578)
Summary: Existing API `VerifyChecksum()` allows application to verify sst files' block checksums. Since whole file, user-specified checksum is tracked in MANIFEST, we can expose a new API to verify sst files' file checksums. ``` // Compute table file checksums if applicable and compare with MANIFEST. // Returns OK if no file has mismatching whole-file checksum. Status DB::VerifyFileChecksums(const ReadOptions& /*read_options*/); ``` Pull Request resolved: facebook#7578 Test Plan: make check Reviewed By: pdillinger Differential Revision: D24436783 Pulled By: riversand963 fbshipit-source-id: 52b51519b842f2b3c4e3351998a97c86cbec85b3
1 parent 47d839a commit d4b8274

File tree

6 files changed

+161
-6
lines changed

6 files changed

+161
-6
lines changed

db/corruption_test.cc

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
namespace ROCKSDB_NAMESPACE {
4040

41-
static const int kValueSize = 1000;
41+
static constexpr int kValueSize = 1000;
4242

4343
class CorruptionTest : public testing::Test {
4444
public:
@@ -69,9 +69,16 @@ class CorruptionTest : public testing::Test {
6969
}
7070

7171
~CorruptionTest() override {
72+
SyncPoint::GetInstance()->DisableProcessing();
73+
SyncPoint::GetInstance()->LoadDependency({});
74+
SyncPoint::GetInstance()->ClearAllCallBacks();
7275
delete db_;
7376
db_ = nullptr;
74-
DestroyDB(dbname_, Options());
77+
if (getenv("KEEP_DB")) {
78+
fprintf(stdout, "db is still at %s\n", dbname_.c_str());
79+
} else {
80+
EXPECT_OK(DestroyDB(dbname_, Options()));
81+
}
7582
}
7683

7784
void CloseDb() {
@@ -821,6 +828,41 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
821828
}
822829
}
823830

831+
TEST_F(CorruptionTest, VerifyWholeTableChecksum) {
832+
CloseDb();
833+
Options options;
834+
options.env = &env_;
835+
ASSERT_OK(DestroyDB(dbname_, options));
836+
options.create_if_missing = true;
837+
options.file_checksum_gen_factory =
838+
ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory();
839+
Reopen(&options);
840+
841+
Build(10, 5);
842+
843+
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
844+
CloseDb();
845+
846+
// Corrupt the first byte of each table file, this must be data block.
847+
Corrupt(kTableFile, 0, 1);
848+
849+
ASSERT_OK(TryReopen(&options));
850+
851+
SyncPoint::GetInstance()->DisableProcessing();
852+
SyncPoint::GetInstance()->ClearAllCallBacks();
853+
int count{0};
854+
SyncPoint::GetInstance()->SetCallBack(
855+
"DBImpl::VerifySstFileChecksum:mismatch", [&](void* arg) {
856+
auto* s = reinterpret_cast<Status*>(arg);
857+
assert(s);
858+
++count;
859+
ASSERT_NOK(*s);
860+
});
861+
SyncPoint::GetInstance()->EnableProcessing();
862+
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption());
863+
ASSERT_EQ(1, count);
864+
}
865+
824866
} // namespace ROCKSDB_NAMESPACE
825867

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

db/db_basic_test.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3303,6 +3303,28 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
33033303
Reopen(options);
33043304
}
33053305

3306+
#ifndef ROCKSDB_LITE
3307+
TEST_F(DBBasicTest, VerifyFileChecksums) {
3308+
Options options = GetDefaultOptions();
3309+
options.create_if_missing = true;
3310+
options.env = env_;
3311+
DestroyAndReopen(options);
3312+
ASSERT_OK(Put("a", "value"));
3313+
ASSERT_OK(Flush());
3314+
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
3315+
3316+
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
3317+
Reopen(options);
3318+
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
3319+
3320+
// Write an L0 with checksum computed.
3321+
ASSERT_OK(Put("b", "value"));
3322+
ASSERT_OK(Flush());
3323+
3324+
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
3325+
}
3326+
#endif // !ROCKSDB_LITE
3327+
33063328
// A test class for intercepting random reads and injecting artificial
33073329
// delays. Used for testing the deadline/timeout feature
33083330
class DBBasicTestDeadline

db/db_impl/db_impl.cc

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <cstdio>
1919
#include <map>
2020
#include <set>
21+
#include <sstream>
2122
#include <stdexcept>
2223
#include <string>
2324
#include <unordered_map>
@@ -4725,8 +4726,29 @@ Status DBImpl::CreateColumnFamilyWithImport(
47254726
return status;
47264727
}
47274728

4729+
Status DBImpl::VerifyFileChecksums(const ReadOptions& read_options) {
4730+
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/true);
4731+
}
4732+
47284733
Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
4734+
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/false);
4735+
}
4736+
4737+
Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
4738+
bool use_file_checksum) {
47294739
Status s;
4740+
4741+
if (use_file_checksum) {
4742+
FileChecksumGenFactory* const file_checksum_gen_factory =
4743+
immutable_db_options_.file_checksum_gen_factory.get();
4744+
if (!file_checksum_gen_factory) {
4745+
s = Status::InvalidArgument(
4746+
"Cannot verify file checksum if options.file_checksum_gen_factory is "
4747+
"null");
4748+
return s;
4749+
}
4750+
}
4751+
47304752
std::vector<ColumnFamilyData*> cfd_list;
47314753
{
47324754
InstrumentedMutexLock l(&mutex_);
@@ -4741,23 +4763,31 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
47414763
for (auto cfd : cfd_list) {
47424764
sv_list.push_back(cfd->GetReferencedSuperVersion(this));
47434765
}
4766+
47444767
for (auto& sv : sv_list) {
47454768
VersionStorageInfo* vstorage = sv->current->storage_info();
47464769
ColumnFamilyData* cfd = sv->current->cfd();
47474770
Options opts;
4748-
{
4771+
if (!use_file_checksum) {
47494772
InstrumentedMutexLock l(&mutex_);
47504773
opts = Options(BuildDBOptions(immutable_db_options_, mutable_db_options_),
47514774
cfd->GetLatestCFOptions());
47524775
}
47534776
for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) {
47544777
for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok();
47554778
j++) {
4756-
const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
4779+
const auto& fd_with_krange = vstorage->LevelFilesBrief(i).files[j];
4780+
const auto& fd = fd_with_krange.fd;
4781+
const FileMetaData* fmeta = fd_with_krange.file_metadata;
4782+
assert(fmeta);
47574783
std::string fname = TableFileName(cfd->ioptions()->cf_paths,
47584784
fd.GetNumber(), fd.GetPathId());
4759-
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
4760-
read_options, fname);
4785+
if (use_file_checksum) {
4786+
s = VerifySstFileChecksum(*fmeta, fname, read_options);
4787+
} else {
4788+
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
4789+
read_options, fname);
4790+
}
47614791
}
47624792
}
47634793
if (!s.ok()) {
@@ -4788,6 +4818,34 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
47884818
return s;
47894819
}
47904820

4821+
Status DBImpl::VerifySstFileChecksum(const FileMetaData& fmeta,
4822+
const std::string& fname,
4823+
const ReadOptions& read_options) {
4824+
Status s;
4825+
if (fmeta.file_checksum == kUnknownFileChecksum) {
4826+
return s;
4827+
}
4828+
std::string file_checksum;
4829+
std::string func_name;
4830+
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
4831+
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
4832+
fmeta.file_checksum_func_name, &file_checksum, &func_name,
4833+
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
4834+
io_tracer_);
4835+
if (s.ok()) {
4836+
assert(fmeta.file_checksum_func_name == func_name);
4837+
if (file_checksum != fmeta.file_checksum) {
4838+
std::ostringstream oss;
4839+
oss << fname << " file checksum mismatch, ";
4840+
oss << "expecting " << Slice(fmeta.file_checksum).ToString(/*hex=*/true);
4841+
oss << ", but actual " << Slice(file_checksum).ToString(/*hex=*/true);
4842+
s = Status::Corruption(oss.str());
4843+
TEST_SYNC_POINT_CALLBACK("DBImpl::VerifySstFileChecksum:mismatch", &s);
4844+
}
4845+
}
4846+
return s;
4847+
}
4848+
47914849
void DBImpl::NotifyOnExternalFileIngested(
47924850
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) {
47934851
if (immutable_db_options_.listeners.empty()) {

db/db_impl/db_impl.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,28 @@ class DBImpl : public DB {
431431
const ExportImportFilesMetaData& metadata,
432432
ColumnFamilyHandle** handle) override;
433433

434+
using DB::VerifyFileChecksums;
435+
Status VerifyFileChecksums(const ReadOptions& read_options) override;
436+
434437
using DB::VerifyChecksum;
435438
virtual Status VerifyChecksum(const ReadOptions& /*read_options*/) override;
439+
// Verify the checksums of files in db. Currently only tables are checked.
440+
//
441+
// read_options: controls file I/O behavior, e.g. read ahead size while
442+
// reading all the live table files.
443+
//
444+
// use_file_checksum: if false, verify the block checksums of all live table
445+
// in db. Otherwise, obtain the file checksums and compare
446+
// with the MANIFEST. Currently, file checksums are
447+
// recomputed by reading all table files.
448+
//
449+
// Returns: OK if there is no file whose file or block checksum mismatches.
450+
Status VerifyChecksumInternal(const ReadOptions& read_options,
451+
bool use_file_checksum);
452+
453+
Status VerifySstFileChecksum(const FileMetaData& fmeta,
454+
const std::string& fpath,
455+
const ReadOptions& read_options);
436456

437457
using DB::StartTrace;
438458
virtual Status StartTrace(

include/rocksdb/db.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,14 @@ class DB {
14431443
const ExportImportFilesMetaData& metadata,
14441444
ColumnFamilyHandle** handle) = 0;
14451445

1446+
// Verify the checksums of files in db. Currently the whole-file checksum of
1447+
// table files are checked.
1448+
virtual Status VerifyFileChecksums(const ReadOptions& /*read_options*/) {
1449+
return Status::NotSupported("File verification not supported");
1450+
}
1451+
1452+
// Verify the block checksums of files in db. The block checksums of table
1453+
// files are checked.
14461454
virtual Status VerifyChecksum(const ReadOptions& read_options) = 0;
14471455

14481456
virtual Status VerifyChecksum() { return VerifyChecksum(ReadOptions()); }

include/rocksdb/utilities/stackable_db.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ class StackableDB : public DB {
141141
import_options, metadata, handle);
142142
}
143143

144+
using DB::VerifyFileChecksums;
145+
Status VerifyFileChecksums(const ReadOptions& read_opts) override {
146+
return db_->VerifyFileChecksums(read_opts);
147+
}
148+
144149
virtual Status VerifyChecksum() override { return db_->VerifyChecksum(); }
145150

146151
virtual Status VerifyChecksum(const ReadOptions& options) override {

0 commit comments

Comments
 (0)