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