diff --git a/HISTORY.md b/HISTORY.md index e1c4a9ee0..2fd37cc01 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -13,8 +13,8 @@ * Chagned Options.prefix_extractor from raw pointer to shared_ptr (take ownership) Changed HashSkipListRepFactory and HashLinkListRepFactory constructor to not take SliceTransform object (use Options.prefix_extractor implicitly) * Added Env::GetThreadPoolQueueLen(), which returns the waiting queue length of thread pools -* Added DB::CheckConsistency(), which checks the consistency of live files' metadata - Added a corresponding command "checkconsistency" in ldb tool +* Added a command "checkconsistency" in ldb tool, which checks + if file system state matches DB state (file existence and file sizes) ### New Features * If we find one truncated record at the end of the MANIFEST or WAL files, diff --git a/db/corruption_test.cc b/db/corruption_test.cc index 0bf385b08..55e2c0374 100644 --- a/db/corruption_test.cc +++ b/db/corruption_test.cc @@ -376,6 +376,38 @@ TEST(CorruptionTest, UnrelatedKeys) { ASSERT_EQ(Value(1000, &tmp2).ToString(), v); } +TEST(CorruptionTest, FileSystemStateCorrupted) { + for (int iter = 0; iter < 2; ++iter) { + Options options; + options.paranoid_checks = true; + options.create_if_missing = true; + Reopen(&options); + Build(10); + ASSERT_OK(db_->Flush(FlushOptions())); + DBImpl* dbi = reinterpret_cast(db_); + std::vector metadata; + dbi->GetLiveFilesMetaData(&metadata); + ASSERT_GT(metadata.size(), 0); + std::string filename = dbname_ + metadata[0].name; + + delete db_; + + if (iter == 0) { // corrupt file size + unique_ptr file; + env_.NewWritableFile(filename, &file, EnvOptions()); + file->Append(Slice("corrupted sst")); + file.reset(); + } else { // delete the file + env_.DeleteFile(filename); + } + + Status x = TryReopen(&options); + ASSERT_TRUE(x.IsCorruption()); + DestroyDB(dbname_, options_); + Reopen(&options); + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/db/db_impl.cc b/db/db_impl.cc index 7aaf7dda1..5b79807f5 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -969,6 +969,9 @@ Status DBImpl::Recover(bool read_only, bool error_if_log_file_exist) { } Status s = versions_->Recover(); + if (options_.paranoid_checks && s.ok()) { + s = CheckConsistency(); + } if (s.ok()) { SequenceNumber max_sequence(0); @@ -3828,11 +3831,38 @@ Status DBImpl::DeleteFile(std::string name) { return status; } -void DBImpl::GetLiveFilesMetaData(std::vector *metadata) { +void DBImpl::GetLiveFilesMetaData(std::vector* metadata) { MutexLock l(&mutex_); return versions_->GetLiveFilesMetaData(metadata); } +Status DBImpl::CheckConsistency() { + mutex_.AssertHeld(); + std::vector metadata; + versions_->GetLiveFilesMetaData(&metadata); + + std::string corruption_messages; + for (const auto& md : metadata) { + std::string file_path = dbname_ + md.name; + uint64_t fsize = 0; + Status s = env_->GetFileSize(file_path, &fsize); + if (!s.ok()) { + corruption_messages += + "Can't access " + md.name + ": " + s.ToString() + "\n"; + } else if (fsize != md.size) { + corruption_messages += "Sst file size mismatch: " + md.name + + ". Size recorded in manifest " + + std::to_string(md.size) + ", actual size " + + std::to_string(fsize) + "\n"; + } + } + if (corruption_messages.size() == 0) { + return Status::OK(); + } else { + return Status::Corruption(corruption_messages); + } +} + void DBImpl::TEST_GetFilesMetaData( std::vector>* metadata) { MutexLock l(&mutex_); diff --git a/db/db_impl.h b/db/db_impl.h index dbcfb39aa..7ce768d31 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -94,6 +94,10 @@ class DBImpl : public DB { virtual void GetLiveFilesMetaData( std::vector *metadata); + // checks if all live files exist on file system and that their file sizes + // match to our in-memory records + virtual Status CheckConsistency(); + virtual Status GetDbIdentity(std::string& identity); Status RunManualCompaction(int input_level, diff --git a/db/db_impl_readonly.cc b/db/db_impl_readonly.cc index ba780b18f..d130783ea 100644 --- a/db/db_impl_readonly.cc +++ b/db/db_impl_readonly.cc @@ -99,46 +99,4 @@ Status DB::OpenForReadOnly(const Options& options, const std::string& dbname, return s; } -Status DB::CheckConsistency(const Options& options, - const std::string& name) { - DB *db = nullptr; - Status st; - - st = DB::OpenForReadOnly(options, name, &db); - if (!st.ok()) { - return st; - } - - std::vector metadata; - db->GetLiveFilesMetaData(&metadata); - - for (const auto& md : metadata) { - std::string file_path = name + md.name; - - if (!db->GetEnv()->FileExists(file_path)) { - st = Status::Corruption("sst file " + md.name + " doesn't exist"); - break; - } - - uint64_t fsize = 0; - st = db->GetEnv()->GetFileSize(file_path, &fsize); - if (!st.ok()) { - st = Status::Corruption( - "Failed to determine the actual size of file " + md.name + - ": " + st.ToString()); - break; - } - if (fsize != md.size) { - st = Status::Corruption( - "sst file size mismatch: " + md.name + - ". Size recorded in manifest " + std::to_string(md.size) + - ", actual size " + std::to_string(fsize)); - break; - } - } - - delete db; - return st; -} - } // namespace rocksdb diff --git a/include/rocksdb/db.h b/include/rocksdb/db.h index a0098b6b0..1f8d2f37e 100644 --- a/include/rocksdb/db.h +++ b/include/rocksdb/db.h @@ -91,14 +91,6 @@ class DB { const std::string& name, DB** dbptr, bool error_if_log_file_exist = false); - // Check the consistency of live files' metadata. - // It will return Corruption Status when a file in manifest - // doesn't actually exist or doesn't match the actual file size. - // Note: This call should be invoked only when the database is - // not already open and serving data. - static Status CheckConsistency(const Options& options, - const std::string& name); - DB() { } virtual ~DB(); diff --git a/util/ldb_cmd.cc b/util/ldb_cmd.cc index 8af476323..30288e721 100644 --- a/util/ldb_cmd.cc +++ b/util/ldb_cmd.cc @@ -1765,10 +1765,13 @@ void CheckConsistencyCommand::Help(string& ret) { void CheckConsistencyCommand::DoCommand() { Options opt = PrepareOptionsForOpenDB(); + opt.paranoid_checks = true; if (!exec_state_.IsNotStarted()) { return; } - Status st = DB::CheckConsistency(opt, db_path_); + DB* db; + Status st = DB::OpenForReadOnly(opt, db_path_, &db, false); + delete db; if (st.ok()) { fprintf(stdout, "OK\n"); } else {