Skip to content

Commit cb37ddf

Browse files
committed
Feature requests for BackupableDB
Summary: This diff introduces some features that were requested by two internal customers: * Ability for backups not to share table files, because we can't guarantee that equal filename means equal content accross replicas * Ability for two threads to call EnableFileDeletions() and DisableFileDeletions() * Ability to stop backup from another thread and not slow down the DB close * Copy the files to the temporary folder first and then atomically rename Test Plan: Added some tests to backupable_db_test Reviewers: dhruba, sanketh, muthu, sdong, haobo Reviewed By: haobo CC: leveldb, sanketh, muthu Differential Revision: https://reviews.facebook.net/D14769
1 parent d040667 commit cb37ddf

File tree

3 files changed

+112
-22
lines changed

3 files changed

+112
-22
lines changed

include/utilities/backupable_db.h

+21-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ struct BackupableDBOptions {
3131
// Default: nullptr
3232
Env* backup_env;
3333

34+
// If share_table_files == true, backup will assume that table files with
35+
// same name have the same contents. This enables incremental backups and
36+
// avoids unnecessary data copies.
37+
// If share_table_files == false, each backup will be on its own and will
38+
// not share any data with other backups.
39+
// default: true
40+
bool share_table_files;
41+
3442
// Backup info and error messages will be written to info_log
3543
// if non-nullptr.
3644
// Default: nullptr
@@ -49,6 +57,7 @@ struct BackupableDBOptions {
4957

5058
explicit BackupableDBOptions(const std::string& _backup_dir,
5159
Env* _backup_env = nullptr,
60+
bool _share_table_files = true,
5261
Logger* _info_log = nullptr,
5362
bool _sync = true,
5463
bool _destroy_old_data = false) :
@@ -93,6 +102,14 @@ class BackupableDB : public StackableDB {
93102
Status PurgeOldBackups(uint32_t num_backups_to_keep);
94103
// deletes a specific backup
95104
Status DeleteBackup(BackupID backup_id);
105+
// Call this from another thread if you want to stop the backup
106+
// that is currently happening. It will return immediatelly, will
107+
// not wait for the backup to stop.
108+
// The backup will stop ASAP and the call to CreateNewBackup will
109+
// return Status::Incomplete(). It will not clean up after itself, but
110+
// the state will remain consistent. The state will be cleaned up
111+
// next time you create BackupableDB or RestoreBackupableDB.
112+
void StopBackup();
96113

97114
private:
98115
BackupEngine* backup_engine_;
@@ -108,9 +125,10 @@ class RestoreBackupableDB {
108125
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
109126

110127
// restore from backup with backup_id
111-
// IMPORTANT -- if you restore from some backup that is not the latest,
112-
// and you start creating new backups from the new DB, all the backups
113-
// that were newer than the backup you restored from will be deleted
128+
// IMPORTANT -- if options_.share_table_files == true and you restore DB
129+
// from some backup that is not the latest, and you start creating new
130+
// backups from the new DB, all the backups that were newer than the
131+
// backup you restored from will be deleted
114132
//
115133
// Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
116134
// If you try creating a new backup now, old backups 4 and 5 will be deleted

utilities/backupable/backupable_db.cc

+55-17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <map>
2121
#include <string>
2222
#include <limits>
23+
#include <atomic>
2324

2425
namespace rocksdb {
2526

@@ -31,6 +32,9 @@ class BackupEngine {
3132
Status CreateNewBackup(DB* db, bool flush_before_backup = false);
3233
Status PurgeOldBackups(uint32_t num_backups_to_keep);
3334
Status DeleteBackup(BackupID backup_id);
35+
void StopBackup() {
36+
stop_backup_.store(true, std::memory_order_release);
37+
}
3438

3539
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
3640
Status RestoreDBFromBackup(BackupID backup_id, const std::string &db_dir,
@@ -106,13 +110,16 @@ class BackupEngine {
106110
return "private";
107111
}
108112
inline std::string GetPrivateFileRel(BackupID backup_id,
109-
const std::string &file = "") const {
113+
bool tmp = false,
114+
const std::string& file = "") const {
110115
assert(file.size() == 0 || file[0] != '/');
111-
return GetPrivateDirRel() + "/" + std::to_string(backup_id) + "/" + file;
116+
return GetPrivateDirRel() + "/" + std::to_string(backup_id) +
117+
(tmp ? ".tmp" : "") + "/" + file;
112118
}
113-
inline std::string GetSharedFileRel(const std::string& file = "") const {
119+
inline std::string GetSharedFileRel(const std::string& file = "",
120+
bool tmp = false) const {
114121
assert(file.size() == 0 || file[0] != '/');
115-
return "shared/" + file;
122+
return "shared/" + file + (tmp ? ".tmp" : "");
116123
}
117124
inline std::string GetLatestBackupFile(bool tmp = false) const {
118125
return GetAbsolutePath(std::string("LATEST_BACKUP") + (tmp ? ".tmp" : ""));
@@ -151,6 +158,7 @@ class BackupEngine {
151158
std::map<BackupID, BackupMeta> backups_;
152159
std::unordered_map<std::string, int> backuped_file_refs_;
153160
std::vector<BackupID> obsolete_backups_;
161+
std::atomic<bool> stop_backup_;
154162

155163
// options data
156164
BackupableDBOptions options_;
@@ -161,13 +169,17 @@ class BackupEngine {
161169
};
162170

163171
BackupEngine::BackupEngine(Env* db_env, const BackupableDBOptions& options)
164-
: options_(options),
165-
db_env_(db_env),
166-
backup_env_(options.backup_env != nullptr ? options.backup_env : db_env_) {
172+
: stop_backup_(false),
173+
options_(options),
174+
db_env_(db_env),
175+
backup_env_(options.backup_env != nullptr ? options.backup_env
176+
: db_env_) {
167177

168178
// create all the dirs we need
169179
backup_env_->CreateDirIfMissing(GetAbsolutePath());
170-
backup_env_->CreateDirIfMissing(GetAbsolutePath(GetSharedFileRel()));
180+
if (!options_.share_table_files) {
181+
backup_env_->CreateDirIfMissing(GetAbsolutePath(GetSharedFileRel()));
182+
}
171183
backup_env_->CreateDirIfMissing(GetAbsolutePath(GetPrivateDirRel()));
172184
backup_env_->CreateDirIfMissing(GetBackupMetaDir());
173185

@@ -298,8 +310,9 @@ Status BackupEngine::CreateNewBackup(DB* db, bool flush_before_backup) {
298310
Log(options_.info_log, "Started the backup process -- creating backup %u",
299311
new_backup_id);
300312

301-
// create private dir
302-
s = backup_env_->CreateDir(GetAbsolutePath(GetPrivateFileRel(new_backup_id)));
313+
// create temporary private dir
314+
s = backup_env_->CreateDir(
315+
GetAbsolutePath(GetPrivateFileRel(new_backup_id, true)));
303316

304317
// copy live_files
305318
for (size_t i = 0; s.ok() && i < live_files.size(); ++i) {
@@ -320,7 +333,7 @@ Status BackupEngine::CreateNewBackup(DB* db, bool flush_before_backup) {
320333
// * if it's kDescriptorFile, limit the size to manifest_file_size
321334
s = BackupFile(new_backup_id,
322335
&new_backup,
323-
type == kTableFile, /* shared */
336+
options_.share_table_files && type == kTableFile,
324337
db->GetName(), /* src_dir */
325338
live_files[i], /* src_fname */
326339
(type == kDescriptorFile) ? manifest_file_size : 0);
@@ -342,6 +355,13 @@ Status BackupEngine::CreateNewBackup(DB* db, bool flush_before_backup) {
342355
// we copied all the files, enable file deletions
343356
db->EnableFileDeletions();
344357

358+
if (s.ok()) {
359+
// move tmp private backup to real backup folder
360+
s = backup_env_->RenameFile(
361+
GetAbsolutePath(GetPrivateFileRel(new_backup_id, true)), // tmp
362+
GetAbsolutePath(GetPrivateFileRel(new_backup_id, false)));
363+
}
364+
345365
if (s.ok()) {
346366
// persist the backup metadata on the disk
347367
s = new_backup.StoreToFile(options_.sync);
@@ -561,6 +581,9 @@ Status BackupEngine::CopyFile(const std::string& src,
561581
Slice data;
562582

563583
do {
584+
if (stop_backup_.load(std::memory_order_acquire)) {
585+
return Status::Incomplete("Backup stopped");
586+
}
564587
size_t buffer_to_read = (copy_file_buffer_size_ < size_limit) ?
565588
copy_file_buffer_size_ : size_limit;
566589
s = src_file->Read(buffer_to_read, &data, buf.get());
@@ -590,12 +613,16 @@ Status BackupEngine::BackupFile(BackupID backup_id,
590613

591614
assert(src_fname.size() > 0 && src_fname[0] == '/');
592615
std::string dst_relative = src_fname.substr(1);
616+
std::string dst_relative_tmp;
593617
if (shared) {
594-
dst_relative = GetSharedFileRel(dst_relative);
618+
dst_relative_tmp = GetSharedFileRel(dst_relative, true);
619+
dst_relative = GetSharedFileRel(dst_relative, false);
595620
} else {
596-
dst_relative = GetPrivateFileRel(backup_id, dst_relative);
621+
dst_relative_tmp = GetPrivateFileRel(backup_id, true, dst_relative);
622+
dst_relative = GetPrivateFileRel(backup_id, false, dst_relative);
597623
}
598624
std::string dst_path = GetAbsolutePath(dst_relative);
625+
std::string dst_path_tmp = GetAbsolutePath(dst_relative_tmp);
599626
Status s;
600627
uint64_t size;
601628

@@ -607,12 +634,15 @@ Status BackupEngine::BackupFile(BackupID backup_id,
607634
} else {
608635
Log(options_.info_log, "Copying %s", src_fname.c_str());
609636
s = CopyFile(src_dir + src_fname,
610-
dst_path,
637+
dst_path_tmp,
611638
db_env_,
612639
backup_env_,
613640
options_.sync,
614641
&size,
615642
size_limit);
643+
if (s.ok() && shared) {
644+
s = backup_env_->RenameFile(dst_path_tmp, dst_path);
645+
}
616646
}
617647
if (s.ok()) {
618648
backup->AddFile(dst_relative, size);
@@ -671,14 +701,16 @@ void BackupEngine::GarbageCollection(bool full_scan) {
671701
&private_children);
672702
for (auto& child : private_children) {
673703
BackupID backup_id = 0;
704+
bool tmp_dir = child.find(".tmp") != std::string::npos;
674705
sscanf(child.c_str(), "%u", &backup_id);
675-
if (backup_id == 0 || backups_.find(backup_id) != backups_.end()) {
706+
if (!tmp_dir && // if it's tmp_dir, delete it
707+
(backup_id == 0 || backups_.find(backup_id) != backups_.end())) {
676708
// it's either not a number or it's still alive. continue
677709
continue;
678710
}
679711
// here we have to delete the dir and all its children
680712
std::string full_private_path =
681-
GetAbsolutePath(GetPrivateFileRel(backup_id));
713+
GetAbsolutePath(GetPrivateFileRel(backup_id, tmp_dir));
682714
std::vector<std::string> subchildren;
683715
backup_env_->GetChildren(full_private_path, &subchildren);
684716
for (auto& subchild : subchildren) {
@@ -813,7 +845,9 @@ Status BackupEngine::BackupMeta::StoreToFile(bool sync) {
813845

814846
BackupableDB::BackupableDB(DB* db, const BackupableDBOptions& options)
815847
: StackableDB(db), backup_engine_(new BackupEngine(db->GetEnv(), options)) {
816-
backup_engine_->DeleteBackupsNewerThan(GetLatestSequenceNumber());
848+
if (options.share_table_files) {
849+
backup_engine_->DeleteBackupsNewerThan(GetLatestSequenceNumber());
850+
}
817851
}
818852

819853
BackupableDB::~BackupableDB() {
@@ -836,6 +870,10 @@ Status BackupableDB::DeleteBackup(BackupID backup_id) {
836870
return backup_engine_->DeleteBackup(backup_id);
837871
}
838872

873+
void BackupableDB::StopBackup() {
874+
backup_engine_->StopBackup();
875+
}
876+
839877
// --- RestoreBackupableDB methods ------
840878

841879
RestoreBackupableDB::RestoreBackupableDB(Env* db_env,

utilities/backupable/backupable_db_test.cc

+36-2
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ class BackupableDBTest {
305305
CreateLoggerFromOptions(dbname_, backupdir_, env_,
306306
Options(), &logger_);
307307
backupable_options_.reset(new BackupableDBOptions(
308-
backupdir_, test_backup_env_.get(), logger_.get(), true));
308+
backupdir_, test_backup_env_.get(), true, logger_.get(), true));
309309

310310
// delete old files in db
311311
DestroyDB(dbname_, Options());
@@ -317,7 +317,8 @@ class BackupableDBTest {
317317
return db;
318318
}
319319

320-
void OpenBackupableDB(bool destroy_old_data = false, bool dummy = false) {
320+
void OpenBackupableDB(bool destroy_old_data = false, bool dummy = false,
321+
bool share_table_files = true) {
321322
// reset all the defaults
322323
test_backup_env_->SetLimitWrittenFiles(1000000);
323324
test_db_env_->SetLimitWrittenFiles(1000000);
@@ -331,6 +332,7 @@ class BackupableDBTest {
331332
ASSERT_OK(DB::Open(options_, dbname_, &db));
332333
}
333334
backupable_options_->destroy_old_data = destroy_old_data;
335+
backupable_options_->share_table_files = share_table_files;
334336
db_.reset(new BackupableDB(db, *backupable_options_));
335337
}
336338

@@ -659,6 +661,38 @@ TEST(BackupableDBTest, DeleteNewerBackups) {
659661
CloseRestoreDB();
660662
}
661663

664+
TEST(BackupableDBTest, NoShareTableFiles) {
665+
const int keys_iteration = 5000;
666+
OpenBackupableDB(true, false, false);
667+
for (int i = 0; i < 5; ++i) {
668+
FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
669+
ASSERT_OK(db_->CreateNewBackup(!!(i % 2)));
670+
}
671+
CloseBackupableDB();
672+
673+
for (int i = 0; i < 5; ++i) {
674+
AssertBackupConsistency(i + 1, 0, keys_iteration * (i + 1),
675+
keys_iteration * 6);
676+
}
677+
}
678+
679+
TEST(BackupableDBTest, DeleteTmpFiles) {
680+
OpenBackupableDB();
681+
CloseBackupableDB();
682+
std::string shared_tmp = backupdir_ + "/shared/00006.sst.tmp";
683+
std::string private_tmp_dir = backupdir_ + "/private/10.tmp";
684+
std::string private_tmp_file = private_tmp_dir + "/00003.sst";
685+
file_manager_->WriteToFile(shared_tmp, "tmp");
686+
file_manager_->CreateDir(private_tmp_dir);
687+
file_manager_->WriteToFile(private_tmp_file, "tmp");
688+
ASSERT_EQ(true, file_manager_->FileExists(private_tmp_dir));
689+
OpenBackupableDB();
690+
CloseBackupableDB();
691+
ASSERT_EQ(false, file_manager_->FileExists(shared_tmp));
692+
ASSERT_EQ(false, file_manager_->FileExists(private_tmp_file));
693+
ASSERT_EQ(false, file_manager_->FileExists(private_tmp_dir));
694+
}
695+
662696
} // anon namespace
663697

664698
} // namespace rocksdb

0 commit comments

Comments
 (0)