Summary: Merge pull request #665 by adamretter Exposes BackupEngine from C++ to the Java API. Previously only BackupableDB was available Test Plan: BackupEngineTest.java Reviewers: fyrz, igor, ankgup87, yhchiang Reviewed By: yhchiang Subscribers: dhruba Differential Revision: https://reviews.facebook.net/D42873main
parent
b0d12a135f
commit
ce21afd205
@ -0,0 +1,216 @@ |
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// This file implements the "bridge" between Java and C++ and enables
|
||||
// calling C++ rocksdb::BackupEngine methods from the Java side.
|
||||
|
||||
#include <jni.h> |
||||
#include <vector> |
||||
|
||||
#include "include/org_rocksdb_BackupEngine.h" |
||||
#include "rocksdb/utilities/backupable_db.h" |
||||
#include "rocksjni/portal.h" |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: open |
||||
* Signature: (JJ)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_open( |
||||
JNIEnv* env, jobject jbe, jlong env_handle, |
||||
jlong backupable_db_options_handle) { |
||||
auto* rocks_env = reinterpret_cast<rocksdb::Env*>(env_handle); |
||||
auto* backupable_db_options = |
||||
reinterpret_cast<rocksdb::BackupableDBOptions*>( |
||||
backupable_db_options_handle); |
||||
rocksdb::BackupEngine* backup_engine; |
||||
auto status = rocksdb::BackupEngine::Open(rocks_env, |
||||
*backupable_db_options, &backup_engine); |
||||
|
||||
if (status.ok()) { |
||||
rocksdb::BackupEngineJni::setHandle(env, jbe, backup_engine); |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: createNewBackup |
||||
* Signature: (JJZ)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_createNewBackup( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle, jlong db_handle, |
||||
jboolean jflush_before_backup) { |
||||
auto* db = reinterpret_cast<rocksdb::DB*>(db_handle); |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
auto status = backup_engine->CreateNewBackup(db, |
||||
static_cast<bool>(jflush_before_backup)); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: getBackupInfo |
||||
* Signature: (J)Ljava/util/List; |
||||
*/ |
||||
jobject Java_org_rocksdb_BackupEngine_getBackupInfo( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
std::vector<rocksdb::BackupInfo> backup_infos; |
||||
backup_engine->GetBackupInfo(&backup_infos); |
||||
return rocksdb::BackupInfoListJni::getBackupInfo(env, backup_infos); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: getCorruptedBackups |
||||
* Signature: (J)[I |
||||
*/ |
||||
jintArray Java_org_rocksdb_BackupEngine_getCorruptedBackups( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
std::vector<rocksdb::BackupID> backup_ids; |
||||
backup_engine->GetCorruptedBackups(&backup_ids); |
||||
// store backupids in int array
|
||||
const std::vector<rocksdb::BackupID>::size_type |
||||
kIdSize = backup_ids.size(); |
||||
int int_backup_ids[kIdSize]; |
||||
for (std::vector<rocksdb::BackupID>::size_type i = 0; |
||||
i != kIdSize; i++) { |
||||
int_backup_ids[i] = backup_ids[i]; |
||||
} |
||||
// Store ints in java array
|
||||
jintArray ret_backup_ids; |
||||
// Its ok to loose precision here (64->32)
|
||||
jsize ret_backup_ids_size = static_cast<jsize>(kIdSize); |
||||
ret_backup_ids = env->NewIntArray(ret_backup_ids_size); |
||||
env->SetIntArrayRegion(ret_backup_ids, 0, ret_backup_ids_size, |
||||
int_backup_ids); |
||||
return ret_backup_ids; |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: garbageCollect |
||||
* Signature: (J)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_garbageCollect( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
auto status = backup_engine->GarbageCollect(); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: purgeOldBackups |
||||
* Signature: (JI)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_purgeOldBackups( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle, jint jnum_backups_to_keep) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
auto status = |
||||
backup_engine-> |
||||
PurgeOldBackups(static_cast<uint32_t>(jnum_backups_to_keep)); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: deleteBackup |
||||
* Signature: (JI)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_deleteBackup( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle, jint jbackup_id) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
auto status = |
||||
backup_engine->DeleteBackup(static_cast<rocksdb::BackupID>(jbackup_id)); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: restoreDbFromBackup |
||||
* Signature: (JILjava/lang/String;Ljava/lang/String;J)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_restoreDbFromBackup( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle, jint jbackup_id, |
||||
jstring jdb_dir, jstring jwal_dir, jlong jrestore_options_handle) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
const char* db_dir = env->GetStringUTFChars(jdb_dir, 0); |
||||
const char* wal_dir = env->GetStringUTFChars(jwal_dir, 0); |
||||
auto* restore_options = |
||||
reinterpret_cast<rocksdb::RestoreOptions*>(jrestore_options_handle); |
||||
auto status = |
||||
backup_engine->RestoreDBFromBackup( |
||||
static_cast<rocksdb::BackupID>(jbackup_id), db_dir, wal_dir, |
||||
*restore_options); |
||||
env->ReleaseStringUTFChars(jwal_dir, wal_dir); |
||||
env->ReleaseStringUTFChars(jdb_dir, db_dir); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: restoreDbFromLatestBackup |
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;J)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_restoreDbFromLatestBackup( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle, jstring jdb_dir, |
||||
jstring jwal_dir, jlong jrestore_options_handle) { |
||||
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
const char* db_dir = env->GetStringUTFChars(jdb_dir, 0); |
||||
const char* wal_dir = env->GetStringUTFChars(jwal_dir, 0); |
||||
auto* restore_options = |
||||
reinterpret_cast<rocksdb::RestoreOptions*>(jrestore_options_handle); |
||||
auto status = |
||||
backup_engine->RestoreDBFromLatestBackup(db_dir, wal_dir, |
||||
*restore_options); |
||||
env->ReleaseStringUTFChars(jwal_dir, wal_dir); |
||||
env->ReleaseStringUTFChars(jdb_dir, db_dir); |
||||
|
||||
if (status.ok()) { |
||||
return; |
||||
} |
||||
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); |
||||
} |
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupEngine |
||||
* Method: disposeInternal |
||||
* Signature: (J)V |
||||
*/ |
||||
void Java_org_rocksdb_BackupEngine_disposeInternal( |
||||
JNIEnv* env, jobject jbe, jlong jbe_handle) { |
||||
delete reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle); |
||||
} |
@ -0,0 +1,222 @@ |
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
package org.rocksdb; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* BackupEngine allows you to backup |
||||
* and restore the database |
||||
* |
||||
* Be aware, that `new BackupEngine` takes time proportional to the amount |
||||
* of backups. So if you have a slow filesystem to backup (like HDFS) |
||||
* and you have a lot of backups then restoring can take some time. |
||||
* That's why we recommend to limit the number of backups. |
||||
* Also we recommend to keep BackupEngine alive and not to recreate it every |
||||
* time you need to do a backup. |
||||
*/ |
||||
public class BackupEngine extends RocksObject implements AutoCloseable { |
||||
|
||||
protected BackupEngine() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* Opens a new Backup Engine |
||||
* |
||||
* @param env The environment that the backup engine should operate within |
||||
* @param options Any options for the backup engine |
||||
* |
||||
* @return A new BackupEngine instance |
||||
*/ |
||||
public static BackupEngine open(final Env env, |
||||
final BackupableDBOptions options) throws RocksDBException { |
||||
final BackupEngine be = new BackupEngine(); |
||||
be.open(env.nativeHandle_, options.nativeHandle_); |
||||
return be; |
||||
} |
||||
|
||||
/** |
||||
* Captures the state of the database in the latest backup |
||||
* |
||||
* Just a convenience for {@link #createNewBackup(RocksDB, boolean)} with |
||||
* the flushBeforeBackup parameter set to false |
||||
* |
||||
* @param db The database to backup |
||||
* |
||||
* Note - This method is not thread safe |
||||
*/ |
||||
public void createNewBackup(final RocksDB db) throws RocksDBException { |
||||
createNewBackup(db, false); |
||||
} |
||||
|
||||
/** |
||||
* Captures the state of the database in the latest backup |
||||
* |
||||
* @param db The database to backup |
||||
* @param flushBeforeBackup When true, the Backup Engine will first issue a |
||||
* memtable flush and only then copy the DB files to |
||||
* the backup directory. Doing so will prevent log |
||||
* files from being copied to the backup directory |
||||
* (since flush will delete them). |
||||
* When false, the Backup Engine will not issue a |
||||
* flush before starting the backup. In that case, |
||||
* the backup will also include log files |
||||
* corresponding to live memtables. The backup will |
||||
* always be consistent with the current state of the |
||||
* database regardless of the flushBeforeBackup |
||||
* parameter. |
||||
* |
||||
* Note - This method is not thread safe |
||||
*/ |
||||
public void createNewBackup( |
||||
final RocksDB db, final boolean flushBeforeBackup) |
||||
throws RocksDBException { |
||||
assert (isInitialized()); |
||||
createNewBackup(nativeHandle_, db.nativeHandle_, flushBeforeBackup); |
||||
} |
||||
|
||||
/** |
||||
* Gets information about the available |
||||
* backups |
||||
* |
||||
* @return A list of information about each available backup |
||||
*/ |
||||
public List<BackupInfo> getBackupInfo() { |
||||
assert (isInitialized()); |
||||
return getBackupInfo(nativeHandle_); |
||||
} |
||||
|
||||
/** |
||||
* <p>Returns a list of corrupted backup ids. If there |
||||
* is no corrupted backup the method will return an |
||||
* empty list.</p> |
||||
* |
||||
* @return array of backup ids as int ids. |
||||
*/ |
||||
public int[] getCorruptedBackups() { |
||||
assert(isInitialized()); |
||||
return getCorruptedBackups(nativeHandle_); |
||||
} |
||||
|
||||
/** |
||||
* <p>Will delete all the files we don't need anymore. It will |
||||
* do the full scan of the files/ directory and delete all the |
||||
* files that are not referenced.</p> |
||||
* |
||||
* @throws RocksDBException thrown if error happens in underlying |
||||
* native library. |
||||
*/ |
||||
public void garbageCollect() throws RocksDBException { |
||||
assert(isInitialized()); |
||||
garbageCollect(nativeHandle_); |
||||
} |
||||
|
||||
/** |
||||
* Deletes old backups, keeping just the latest numBackupsToKeep |
||||
* |
||||
* @param numBackupsToKeep The latest n backups to keep |
||||
*/ |
||||
public void purgeOldBackups( |
||||
final int numBackupsToKeep) throws RocksDBException { |
||||
assert (isInitialized()); |
||||
purgeOldBackups(nativeHandle_, numBackupsToKeep); |
||||
} |
||||
|
||||
/** |
||||
* Deletes a backup |
||||
* |
||||
* @param backupId The id of the backup to delete |
||||
*/ |
||||
public void deleteBackup(final int backupId) throws RocksDBException { |
||||
assert (isInitialized()); |
||||
deleteBackup(nativeHandle_, backupId); |
||||
} |
||||
|
||||
/** |
||||
* Restore the database from a backup |
||||
* |
||||
* IMPORTANT: if options.share_table_files == true and you restore the DB |
||||
* from some backup that is not the latest, and you start creating new |
||||
* backups from the new DB, they will probably fail! |
||||
* |
||||
* Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3. |
||||
* If you add new data to the DB and try creating a new backup now, the |
||||
* database will diverge from backups 4 and 5 and the new backup will fail. |
||||
* If you want to create new backup, you will first have to delete backups 4 |
||||
* and 5. |
||||
* |
||||
* @param backupId The id of the backup to restore |
||||
* @param dbDir The directory to restore the backup to, i.e. where your |
||||
* database is |
||||
* @param walDir The location of the log files for your database, |
||||
* often the same as dbDir |
||||
* @param restoreOptions Options for controlling the restore |
||||
*/ |
||||
public void restoreDbFromBackup( |
||||
final int backupId, final String dbDir, final String walDir, |
||||
final RestoreOptions restoreOptions) throws RocksDBException { |
||||
assert (isInitialized()); |
||||
restoreDbFromBackup(nativeHandle_, backupId, dbDir, walDir, |
||||
restoreOptions.nativeHandle_); |
||||
} |
||||
|
||||
/** |
||||
* Restore the database from the latest backup |
||||
* |
||||
* @param dbDir The directory to restore the backup to, i.e. where your database is |
||||
* @param walDir The location of the log files for your database, often the same as dbDir |
||||
* @param restoreOptions Options for controlling the restore |
||||
*/ |
||||
public void restoreDbFromLatestBackup( |
||||
final String dbDir, final String walDir, |
||||
final RestoreOptions restoreOptions) throws RocksDBException { |
||||
assert (isInitialized()); |
||||
restoreDbFromLatestBackup(nativeHandle_, dbDir, walDir, |
||||
restoreOptions.nativeHandle_); |
||||
} |
||||
|
||||
/** |
||||
* Close the Backup Engine |
||||
*/ |
||||
@Override |
||||
public void close() throws RocksDBException { |
||||
dispose(); |
||||
} |
||||
|
||||
@Override |
||||
protected void disposeInternal() { |
||||
assert (isInitialized()); |
||||
disposeInternal(nativeHandle_); |
||||
} |
||||
|
||||
private native void open(final long env, final long backupableDbOptions) |
||||
throws RocksDBException; |
||||
|
||||
private native void createNewBackup(final long handle, final long dbHandle, |
||||
final boolean flushBeforeBackup) throws RocksDBException; |
||||
|
||||
private native List<BackupInfo> getBackupInfo(final long handle); |
||||
|
||||
private native int[] getCorruptedBackups(final long handle); |
||||
|
||||
private native void garbageCollect(final long handle) throws RocksDBException; |
||||
|
||||
private native void purgeOldBackups(final long handle, |
||||
final int numBackupsToKeep) throws RocksDBException; |
||||
|
||||
private native void deleteBackup(final long handle, final int backupId) |
||||
throws RocksDBException; |
||||
|
||||
private native void restoreDbFromBackup(final long handle, final int backupId, |
||||
final String dbDir, final String walDir, final long restoreOptionsHandle) |
||||
throws RocksDBException; |
||||
|
||||
private native void restoreDbFromLatestBackup(final long handle, |
||||
final String dbDir, final String walDir, final long restoreOptionsHandle) |
||||
throws RocksDBException; |
||||
|
||||
private native void disposeInternal(final long handle); |
||||
} |
@ -0,0 +1,305 @@ |
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
package org.rocksdb; |
||||
|
||||
import org.junit.ClassRule; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.TemporaryFolder; |
||||
|
||||
import java.util.List; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
public class BackupEngineTest { |
||||
|
||||
@ClassRule |
||||
public static final RocksMemoryResource rocksMemoryResource = |
||||
new RocksMemoryResource(); |
||||
|
||||
@Rule |
||||
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||
|
||||
@Rule |
||||
public TemporaryFolder backupFolder = new TemporaryFolder(); |
||||
|
||||
@Test |
||||
public void backupDb() throws RocksDBException { |
||||
Options opt = null; |
||||
RocksDB db = null; |
||||
try { |
||||
opt = new Options().setCreateIfMissing(true); |
||||
// Open empty database.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Fill database with some test values
|
||||
prepareDatabase(db); |
||||
// Create two backups
|
||||
BackupableDBOptions bopt = null; |
||||
try { |
||||
bopt = new BackupableDBOptions( |
||||
backupFolder.getRoot().getAbsolutePath()); |
||||
try(final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { |
||||
be.createNewBackup(db, false); |
||||
be.createNewBackup(db, true); |
||||
verifyNumberOfValidBackups(be, 2); |
||||
} |
||||
} finally { |
||||
if(bopt != null) { |
||||
bopt.dispose(); |
||||
} |
||||
} |
||||
} finally { |
||||
if (db != null) { |
||||
db.close(); |
||||
} |
||||
if (opt != null) { |
||||
opt.dispose(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void deleteBackup() throws RocksDBException { |
||||
Options opt = null; |
||||
RocksDB db = null; |
||||
try { |
||||
opt = new Options().setCreateIfMissing(true); |
||||
// Open empty database.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Fill database with some test values
|
||||
prepareDatabase(db); |
||||
// Create two backups
|
||||
BackupableDBOptions bopt = null; |
||||
try { |
||||
bopt = new BackupableDBOptions( |
||||
backupFolder.getRoot().getAbsolutePath()); |
||||
try(final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { |
||||
be.createNewBackup(db, false); |
||||
be.createNewBackup(db, true); |
||||
final List<BackupInfo> backupInfo = |
||||
verifyNumberOfValidBackups(be, 2); |
||||
// Delete the first backup
|
||||
be.deleteBackup(backupInfo.get(0).backupId()); |
||||
final List<BackupInfo> newBackupInfo = |
||||
verifyNumberOfValidBackups(be, 1); |
||||
|
||||
// The second backup must remain.
|
||||
assertThat(newBackupInfo.get(0).backupId()). |
||||
isEqualTo(backupInfo.get(1).backupId()); |
||||
} |
||||
} finally { |
||||
if(bopt != null) { |
||||
bopt.dispose(); |
||||
} |
||||
} |
||||
} finally { |
||||
if (db != null) { |
||||
db.close(); |
||||
} |
||||
if (opt != null) { |
||||
opt.dispose(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void purgeOldBackups() throws RocksDBException { |
||||
Options opt = null; |
||||
RocksDB db = null; |
||||
try { |
||||
opt = new Options().setCreateIfMissing(true); |
||||
// Open empty database.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Fill database with some test values
|
||||
prepareDatabase(db); |
||||
// Create four backups
|
||||
BackupableDBOptions bopt = null; |
||||
try { |
||||
bopt = new BackupableDBOptions( |
||||
backupFolder.getRoot().getAbsolutePath()); |
||||
try(final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { |
||||
be.createNewBackup(db, false); |
||||
be.createNewBackup(db, true); |
||||
be.createNewBackup(db, true); |
||||
be.createNewBackup(db, true); |
||||
final List<BackupInfo> backupInfo = |
||||
verifyNumberOfValidBackups(be, 4); |
||||
// Delete everything except the latest backup
|
||||
be.purgeOldBackups(1); |
||||
final List<BackupInfo> newBackupInfo = |
||||
verifyNumberOfValidBackups(be, 1); |
||||
// The latest backup must remain.
|
||||
assertThat(newBackupInfo.get(0).backupId()). |
||||
isEqualTo(backupInfo.get(3).backupId()); |
||||
} |
||||
} finally { |
||||
if(bopt != null) { |
||||
bopt.dispose(); |
||||
} |
||||
} |
||||
} finally { |
||||
if (db != null) { |
||||
db.close(); |
||||
} |
||||
if (opt != null) { |
||||
opt.dispose(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void restoreLatestBackup() |
||||
throws RocksDBException { |
||||
Options opt = null; |
||||
RocksDB db = null; |
||||
try { |
||||
opt = new Options().setCreateIfMissing(true); |
||||
// Open empty database.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Fill database with some test values
|
||||
prepareDatabase(db); |
||||
BackupableDBOptions bopt = null; |
||||
try { |
||||
bopt = new BackupableDBOptions( |
||||
backupFolder.getRoot().getAbsolutePath()); |
||||
try (final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { |
||||
be.createNewBackup(db, true); |
||||
verifyNumberOfValidBackups(be, 1); |
||||
db.put("key1".getBytes(), "valueV2".getBytes()); |
||||
db.put("key2".getBytes(), "valueV2".getBytes()); |
||||
be.createNewBackup(db, true); |
||||
verifyNumberOfValidBackups(be, 2); |
||||
db.put("key1".getBytes(), "valueV3".getBytes()); |
||||
db.put("key2".getBytes(), "valueV3".getBytes()); |
||||
assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); |
||||
assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); |
||||
|
||||
db.close(); |
||||
|
||||
verifyNumberOfValidBackups(be, 2); |
||||
// restore db from latest backup
|
||||
be.restoreDbFromLatestBackup(dbFolder.getRoot().getAbsolutePath(), |
||||
dbFolder.getRoot().getAbsolutePath(), |
||||
new RestoreOptions(false)); |
||||
// Open database again.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Values must have suffix V2 because of restoring latest backup.
|
||||
assertThat(new String(db.get("key1".getBytes()))).endsWith("V2"); |
||||
assertThat(new String(db.get("key2".getBytes()))).endsWith("V2"); |
||||
} |
||||
} finally { |
||||
if(bopt != null) { |
||||
bopt.dispose(); |
||||
} |
||||
} |
||||
} finally { |
||||
if (db != null) { |
||||
db.close(); |
||||
} |
||||
if (opt != null) { |
||||
opt.dispose(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void restoreFromBackup() |
||||
throws RocksDBException { |
||||
Options opt = null; |
||||
RocksDB db = null; |
||||
try { |
||||
opt = new Options().setCreateIfMissing(true); |
||||
// Open empty database.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Fill database with some test values
|
||||
prepareDatabase(db); |
||||
BackupableDBOptions bopt = null; |
||||
try { |
||||
bopt = new BackupableDBOptions( |
||||
backupFolder.getRoot().getAbsolutePath()); |
||||
try (final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { |
||||
be.createNewBackup(db, true); |
||||
verifyNumberOfValidBackups(be, 1); |
||||
db.put("key1".getBytes(), "valueV2".getBytes()); |
||||
db.put("key2".getBytes(), "valueV2".getBytes()); |
||||
be.createNewBackup(db, true); |
||||
verifyNumberOfValidBackups(be, 2); |
||||
db.put("key1".getBytes(), "valueV3".getBytes()); |
||||
db.put("key2".getBytes(), "valueV3".getBytes()); |
||||
assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); |
||||
assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); |
||||
|
||||
//close the database
|
||||
db.close(); |
||||
|
||||
//restore the backup
|
||||
List<BackupInfo> backupInfo = verifyNumberOfValidBackups(be, 2); |
||||
// restore db from first backup
|
||||
be.restoreDbFromBackup(backupInfo.get(0).backupId(), |
||||
dbFolder.getRoot().getAbsolutePath(), |
||||
dbFolder.getRoot().getAbsolutePath(), |
||||
new RestoreOptions(false)); |
||||
// Open database again.
|
||||
db = RocksDB.open(opt, |
||||
dbFolder.getRoot().getAbsolutePath()); |
||||
// Values must have suffix V2 because of restoring latest backup.
|
||||
assertThat(new String(db.get("key1".getBytes()))).endsWith("V1"); |
||||
assertThat(new String(db.get("key2".getBytes()))).endsWith("V1"); |
||||
} |
||||
} finally { |
||||
if(bopt != null) { |
||||
bopt.dispose(); |
||||
} |
||||
} |
||||
} finally { |
||||
if (db != null) { |
||||
db.close(); |
||||
} |
||||
if (opt != null) { |
||||
opt.dispose(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Verify backups. |
||||
* |
||||
* @param be {@link BackupEngine} instance. |
||||
* @param expectedNumberOfBackups numerical value |
||||
* @throws RocksDBException thrown if an error occurs within the native |
||||
* part of the library. |
||||
*/ |
||||
private List<BackupInfo> verifyNumberOfValidBackups(final BackupEngine be, |
||||
final int expectedNumberOfBackups) throws RocksDBException { |
||||
// Verify that backups exist
|
||||
assertThat(be.getCorruptedBackups().length). |
||||
isEqualTo(0); |
||||
be.garbageCollect(); |
||||
final List<BackupInfo> backupInfo = be.getBackupInfo(); |
||||
assertThat(backupInfo.size()). |
||||
isEqualTo(expectedNumberOfBackups); |
||||
return backupInfo; |
||||
} |
||||
|
||||
/** |
||||
* Fill database with some test values. |
||||
* |
||||
* @param db {@link RocksDB} instance. |
||||
* @throws RocksDBException thrown if an error occurs within the native |
||||
* part of the library. |
||||
*/ |
||||
private void prepareDatabase(final RocksDB db) |
||||
throws RocksDBException { |
||||
db.put("key1".getBytes(), "valueV1".getBytes()); |
||||
db.put("key2".getBytes(), "valueV1".getBytes()); |
||||
} |
||||
} |
Loading…
Reference in new issue