protect valid backup files when max_valid_backups_to_open is set

Summary:
When `max_valid_backups_to_open` is set, the `BackupEngine` doesn't know about the files referenced by existing backups. This PR prevents us from deleting valid files when that option is set, in cases where we are unable to accurately determine refcount. There are warnings logged when we may miss deleting unreferenced files, and a recommendation in the header for users to periodically unset this option and run a full `GarbageCollect`.
Closes https://github.com/facebook/rocksdb/pull/3518

Differential Revision: D7008331

Pulled By: ajkr

fbshipit-source-id: 87907f964dc9716e229d08636a895d2fc7b72305
main
Andrew Kryczka 7 years ago committed by Facebook Github Bot
parent 6571770030
commit faba3fb53d
  1. 1
      HISTORY.md
  2. 6
      include/rocksdb/utilities/backupable_db.h
  3. 40
      utilities/backupable/backupable_db.cc
  4. 31
      utilities/backupable/backupable_db_test.cc

@ -9,6 +9,7 @@
### Bug Fixes
* Fsync after writing global seq number to the ingestion file in ExternalSstFileIngestionJob.
* Fix WAL corruption caused by race condition between user write thread and FlushWAL when two_write_queue is not set.
* Fix `BackupableDBOptions::max_valid_backups_to_open` to not delete backup files when refcount cannot be accurately determined.
### Java API Changes
* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.

@ -110,6 +110,12 @@ struct BackupableDBOptions {
// When Open() is called, it will open at most this many of the latest
// non-corrupted backups.
//
// Note setting this to a non-default value prevents old files from being
// deleted in the shared directory, as we can't do proper ref-counting. If
// using this option, make sure to occasionally disable it (by resetting to
// INT_MAX) and run GarbageCollect to clean accumulated stale files.
//
// Default: INT_MAX
int max_valid_backups_to_open;

@ -979,17 +979,24 @@ Status BackupEngineImpl::DeleteBackup(BackupID backup_id) {
corrupt_backups_.erase(corrupt);
}
std::vector<std::string> to_delete;
for (auto& itr : backuped_file_infos_) {
if (itr.second->refs == 0) {
Status s = backup_env_->DeleteFile(GetAbsolutePath(itr.first));
ROCKS_LOG_INFO(options_.info_log, "Deleting %s -- %s", itr.first.c_str(),
s.ToString().c_str());
to_delete.push_back(itr.first);
if (options_.max_valid_backups_to_open == port::kMaxInt32) {
std::vector<std::string> to_delete;
for (auto& itr : backuped_file_infos_) {
if (itr.second->refs == 0) {
Status s = backup_env_->DeleteFile(GetAbsolutePath(itr.first));
ROCKS_LOG_INFO(options_.info_log, "Deleting %s -- %s", itr.first.c_str(),
s.ToString().c_str());
to_delete.push_back(itr.first);
}
}
}
for (auto& td : to_delete) {
backuped_file_infos_.erase(td);
for (auto& td : to_delete) {
backuped_file_infos_.erase(td);
}
} else {
ROCKS_LOG_WARN(
options_.info_log,
"DeleteBackup cleanup is limited since `max_valid_backups_to_open` "
"constrains how many backups the engine knows about");
}
// take care of private dirs -- GarbageCollect() will take care of them
@ -1471,9 +1478,18 @@ Status BackupEngineImpl::InsertPathnameToSizeBytes(
Status BackupEngineImpl::GarbageCollect() {
assert(!read_only_);
ROCKS_LOG_INFO(options_.info_log, "Starting garbage collection");
if (options_.max_valid_backups_to_open == port::kMaxInt32) {
ROCKS_LOG_WARN(
options_.info_log,
"Garbage collection is limited since `max_valid_backups_to_open` "
"constrains how many backups the engine knows about");
}
if (options_.share_table_files) {
if (options_.share_table_files &&
options_.max_valid_backups_to_open == port::kMaxInt32) {
// delete obsolete shared files
// we cannot do this when BackupEngine has `max_valid_backups_to_open` set
// as those engines don't know about all shared files.
std::vector<std::string> shared_children;
{
std::string shared_path;
@ -1523,6 +1539,8 @@ Status BackupEngineImpl::GarbageCollect() {
}
}
for (auto& child : private_children) {
// it's ok to do this when BackupEngine has `max_valid_backups_to_open` set
// as the engine always knows all valid backup numbers.
BackupID backup_id = 0;
bool tmp_dir = child.find(".tmp") != std::string::npos;
sscanf(child.c_str(), "%u", &backup_id);

@ -1576,6 +1576,37 @@ TEST_F(BackupableDBTest, WriteOnlyEngine) {
ASSERT_EQ(2, backup_infos[0].backup_id);
}
TEST_F(BackupableDBTest, WriteOnlyEngineNoSharedFileDeletion) {
// Verifies a write-only BackupEngine does not delete files belonging to valid
// backups when GarbageCollect, PurgeOldBackups, or DeleteBackup are called.
const int kNumKeys = 5000;
for (int i = 0; i < 3; ++i) {
OpenDBAndBackupEngine(i == 0 /* destroy_old_data */);
FillDB(db_.get(), i * kNumKeys, (i + 1) * kNumKeys);
ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
CloseDBAndBackupEngine();
backupable_options_->max_valid_backups_to_open = 0;
OpenDBAndBackupEngine();
switch (i) {
case 0:
ASSERT_OK(backup_engine_->GarbageCollect());
break;
case 1:
ASSERT_OK(backup_engine_->PurgeOldBackups(1 /* num_backups_to_keep */));
break;
case 2:
ASSERT_OK(backup_engine_->DeleteBackup(2 /* backup_id */));
break;
default:
assert(false);
}
CloseDBAndBackupEngine();
backupable_options_->max_valid_backups_to_open = port::kMaxInt32;
AssertBackupConsistency(i + 1, 0, (i + 1) * kNumKeys);
}
}
} // anon namespace
} // namespace rocksdb

Loading…
Cancel
Save