如何备份RocksDB

Backup API

对于C++ API,请参见 include/rocksdb/utilities/backupable_db.h。关键抽象是备份引擎,它公开了创建备份,获取有关备份的信息和从备份恢复的简单接口。 备份引擎有两种不同的标识形式:

  1. 1)用于创建新备份的备份引擎。
  2. 2)仅用于从备份中恢复的备份引擎。

其中任何一个都可以用来获取关于备份的信息。

创建和验证备份

在RocksDB中,我们实现了一种简单的方法来备份数据库并验证正确性。下面是一个简单的例子:

  1. #include "rocksdb/db.h"
  2. #include "rocksdb/utilities/backupable_db.h"
  3. #include <vector>
  4. using namespace rocksdb;
  5. int main() {
  6. Options options;
  7. options.create_if_missing = true;
  8. DB* db;
  9. Status s = DB::Open(options, "/tmp/rocksdb", &db);
  10. assert(s.ok());
  11. db->Put(...); // do your thing
  12. BackupEngine* backup_engine;
  13. s = BackupEngine::Open(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup"), &backup_engine);
  14. assert(s.ok());
  15. s = backup_engine->CreateNewBackup(db);
  16. assert(s.ok());
  17. db->Put(...); // make some more changes
  18. s = backup_engine->CreateNewBackup(db);
  19. assert(s.ok());
  20. std::vector<BackupInfo> backup_info;
  21. backup_engine->GetBackupInfo(&backup_info);
  22. // you can get IDs from backup_info if there are more than two
  23. s = backup_engine->VerifyBackup(1 /* ID */);
  24. assert(s.ok());
  25. s = backup_engine->VerifyBackup(2 /* ID */);
  26. assert(s.ok());
  27. delete db;
  28. delete backup_engine;
  29. }

这个简单的示例将在”/tmp/rocksdb_backup”中创建一对备份。注意,您可以使用同一个引擎创建和验证多个备份。

备份通常是增量的(参见BackupableDBOptions::share_table_files)。您可以使用BackupEngine::CreateNewBackup()创建一个新的备份,并且只有新数据才会被复制到backup目录中(有关复制内容的更多细节,请参阅目录下 (https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F#under-the-hood) )。

一旦你有备份保存,可以发出BackupEngine:GetBackupInfo()调用来获得所有备份的列表一起的时间戳信息备份和大小(请注意,所有备份的大小之和大于实际的备份目录的大小,因为一些数据由多个备份)共享。 备份由其始终递增的id标识。

当调用BackupEngine::verifybackup()时,它将根据db目录中相应文件的原始大小检查备份目录中的文件大小。但是,我们不验证校验和,因为它需要读取所有数据。 注意,BackupEngine::verifybackup()惟一有效的用例是在创建备份时使用相同的引擎之后在备份引擎上调用它,因为它使用在备份期间捕获的状态。

恢复备份

恢复也很容易:

  1. #include "rocksdb/db.h"
  2. #include "rocksdb/utilities/backupable_db.h"
  3. using namespace rocksdb;
  4. int main() {
  5. BackupEngineReadOnly* backup_engine;
  6. Status s = BackupEngineReadOnly::Open(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup"), &backup_engine);
  7. assert(s.ok());
  8. backup_engine->RestoreDBFromBackup(1, "/tmp/rocksdb", "/tmp/rocksdb");
  9. delete backup_engine;
  10. }

这段代码将把第一个备份恢复到”/tmp/rocksdb”。BackupEngineReadOnly::RestoreDBFromBackup()的第一个参数是备份ID,第二个参数是目标DB目录,第三个参数是日志文件的目标位置(在某些DBs中,它们与DB目录不同,但通常是相同的。有关更多信息,请参见选项::wal_dir)。 BackupEngineReadOnly::RestoreDBFromLatestBackup()将从最新的备份中恢复数据库,即,ID最高的那个。

对任何已恢复的文件计算校验和,并与备份期间存储的文件进行比较。如果检测到校验和不匹配,将中止恢复过程并返回Status::Corruption。

必须重新打开任何活动数据库才能看到恢复后的数据。

备份目录结构

  1. /tmp/rocksdb_backup/
  2. ├── LATEST_BACKUP
  3. ├── meta
  4. └── 1
  5. ├── private
  6. └── 1
  7. ├── CURRENT
  8. ├── MANIFEST-000008
  9. | └── OPTIONS-000009
  10. └── shared_checksum
  11. └── 000007_1498774076_590.sst

LATEST_BACKUP是一个包含最高备份ID的文件。在上面的示例中,它包”1”。它用于获取最新的备份号,但不再需要,因为有更容易的方法从元数据获取号码。该文件将在RocksDB 5.0中删除。

元目录包含一个描述每个备份的”元文件”,其中它的名称是备份ID。例如,元文件包含属于该备份的所有文件的列表。该格式在实现文件(实用程序/backupable/backupable_db.cc)中得到了完整的描述。

私有目录总是包含非sst文件(选项、当前、清单和WALs)。如果Options::share_table_files未设置,它还包含SST文件。

在设置选项::share_table_files和取消设置选项::share_files_with_checksum时,共享目录(未显示)包含SST文件。在这个目录中,文件仅使用原始DB中的名称来命名。 因此,它应该只用于备份一个RocksDB实例;否则,文件名可能会冲突。

当设置了选项::share_table_files和选项::share_files_with_checksum时,shared_checksum目录包含SST文件。这些属性惟一地标识来自多个RocksDB实例的文件。

备份性能

注意,备份引擎的Open()花费的时间与现有备份的数量成正比,因为我们在每个现有备份中初始化有关文件的信息。因此,如果您的目标是一个远程文件系统(如HDFS),并且您有很多备份,那么由于所有的网络往返,初始化备份引擎可能需要一些时间。我们建议保持您的备份引擎处于活动状态,不要在每次需要进行备份或恢复时重新创建它。

另一种保持引擎快速初始化的方法是删除不必要的备份。要删除不必要的备份,只需调用purgeoldbackup (N),其中N是您希望保留的备份数量。除最新的N个备份外,所有备份都将被删除。您还可以选择使用DeleteBackup(id)删除任意备份。

还要注意,性能取决于从本地数据库读取数据并复制到备份。由于您可能使用不同的环境进行读取和复制,所以并行性瓶颈可能出现在两种情况之一。例如,如果本地db在HDD上,使用更多的线程进行备份(请参阅高级用法)将不会有什么帮助,因为这种情况下的瓶颈是磁盘读取能力饱和。此外,一个糟糕的小型HDFS集群不能显示良好的并行性。如果本地db位于SSD上,并且备份目标是高容量HDFS,这将是有益的。在我们的基准测试中,使用16个线程将把备份时间减少到单线程作业的1/3。

引擎盖下

当您调用BackupEngine::CreateNewBackup()时,它执行以下操作:

1.禁用文件删除。 2.获取活动文件(包括table files、current、options和manifest文件)。 3.将活动文件复制到备份目录。由于表文件是不可变的,文件名是惟一的,所以我们不会复制备份目录中已经存在的表文件。 例如,如果有一个文件00050.sst已经备份,GetLiveFiles()返回 00050.sst,我们不会将该文件复制到备份目录。 但是,无论是否需要复制某个文件,都会计算所有文件的校验和。如果文件已经存在,则将计算的校验和与之前计算的校验和进行比较,以确保备份之间没有发生异常。 如果检测到不匹配,则会中止备份,并在调用BackupEngine::CreateNewBackup()之前将系统恢复到状态。 需要注意的一点是,备份终止可能意味着备份目录中的文件或当前DB中相应的活动文件的损坏。选项、清单和当前文件总是复制到私有目录,因为它们不是不可变的。 4.如果flush_before_backup被设置为false,我们还需要将日志文件复制到备份目录。我们调用GetSortedWalFiles()并将所有活动文件复制到备份目录。 5.重新启用文件删除。

高级用法

我们可以在备份中存储用户定义的元数据。将元数据传递给BackupEngine::CreateNewBackupWithMetadata(),然后稍后使用BackupEngine::GetBackupInfo()读取它。 例如,可以使用不同于自动递增id的标识符来标识备份。

我们现在还备份和恢复选项文件。恢复之后,可以使用rocksdb::LoadLatestOptions()或rocksdb::LoadOptionsFromFile()从db目录加载选项。 限制是,options对象中的所有内容都不能转换为文件中的文本。在恢复和加载之后,仍然需要几个步骤手动设置选项中缺少的项。 好消息是,你需要的比以前少得多。

您需要实例化一些env并为backup_target初始化BackupableDBOptions::backup_env。 将备份根目录放在BackupableDBOptions::backup_dir中。目录下的文件将按照上面提到的结构组织

BackupableDBOptions::share_table_files控制备份是否以增量方式执行。 如果为真,SST文件将放在”shared/“子目录下。当不同的SST文件使用相同的名称时(例如,当多个数据库具有相同的目标备份目录时),可能会出现冲突

BackupableDBOptions::share_files_with_checksum控制如何标识共享文件。 如果为真,则使用校验和、大小和seqnum标识共享的SST文件。 当多个数据库使用公共目标备份目录时,这可以防止上述冲突。

BackupableDBOptions::max_background_operations控制在备份和恢复期间用于复制文件的线程数。 对于像HDFS这样的分布式文件系统,增加复制并行性是非常有益的。

BackupableDBOptions::info_log 是一个日志记录器对象,如果not-nullptr,则用于打印日志消息。查阅 Logger wiki (https://github.com/facebook/rocksdb/wiki/Logger)

如果BackupableDBOptions::sync为真,我们将在每次文件写入之后使用fsync(2)将文件数据和元数据同步到磁盘,确保在重新启动或机器崩溃后备份是一致的。 将其设置为false会稍微加快速度,但是一些(newer)备份可能不一致。不过,在大多数情况下,一切都应该很好。

如果将BackupableDBOptions::destroy_old_data设置为true,则创建新的BackupEngine将删除备份目录中的所有旧备份。

BackupEngine::CreateNewBackup() 方法参数为flush_before_backup,默认情况下为false。 当flush_before_backup为真时,BackupEngine将首先发出memtable flush,然后才将DB文件复制到备份目录。 这样做将防止将日志文件复制到备份目录(因为flush将删除它们)。 如果flush_before_backup为false, backup将不会在启动备份之前发出flush。 在这种情况下,备份还将包括与活动memtables对应的日志文件。 无论flush_before_backup参数是什么,备份都将与数据库的当前状态保持一致。

进一步的阅读

有关实现,请参见实用程序/backupable/backupable_db.cc