From ce21afd205eca21fb75d0d3503d98d0d22f8b8ab Mon Sep 17 00:00:00 2001 From: Yueh-Hsuan Chiang Date: Tue, 4 Aug 2015 16:07:17 -0700 Subject: [PATCH] Expose the BackupEngine from the Java API 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/D42873 --- java/Makefile | 6 +- java/rocksjni/backupenginejni.cc | 216 +++++++++++++ java/rocksjni/portal.h | 9 + .../main/java/org/rocksdb/BackupEngine.java | 222 +++++++++++++ .../java/org/rocksdb/BackupEngineTest.java | 305 ++++++++++++++++++ src.mk | 7 +- 6 files changed, 760 insertions(+), 5 deletions(-) create mode 100644 java/rocksjni/backupenginejni.cc create mode 100644 java/src/main/java/org/rocksdb/BackupEngine.java create mode 100644 java/src/test/java/org/rocksdb/BackupEngineTest.java diff --git a/java/Makefile b/java/Makefile index 303c46382..8ef86b1e3 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1,5 +1,6 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractComparator\ org.rocksdb.AbstractSlice\ + org.rocksdb.BackupEngine\ org.rocksdb.BackupableDB\ org.rocksdb.BackupableDBOptions\ org.rocksdb.BlockBasedTableConfig\ @@ -58,6 +59,7 @@ ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx. endif JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ + org.rocksdb.BackupEngineTest\ org.rocksdb.BackupableDBTest\ org.rocksdb.BlockBasedTableConfigTest\ org.rocksdb.CheckPointTest\ @@ -74,11 +76,11 @@ JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ org.rocksdb.FlushTest\ org.rocksdb.InfoLogLevelTest\ org.rocksdb.KeyMayExistTest\ - org.rocksdb.LoggerTest\ + org.rocksdb.LoggerTest\ org.rocksdb.MemTableTest\ org.rocksdb.MergeTest\ org.rocksdb.MixedOptionsTest\ - org.rocksdb.NativeLibraryLoaderTest\ + org.rocksdb.NativeLibraryLoaderTest\ org.rocksdb.OptionsTest\ org.rocksdb.PlainTableConfigTest\ org.rocksdb.ReadOnlyTest\ diff --git a/java/rocksjni/backupenginejni.cc b/java/rocksjni/backupenginejni.cc new file mode 100644 index 000000000..750ab965a --- /dev/null +++ b/java/rocksjni/backupenginejni.cc @@ -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 +#include + +#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(env_handle); + auto* backupable_db_options = + reinterpret_cast( + 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(db_handle); + auto* backup_engine = reinterpret_cast(jbe_handle); + auto status = backup_engine->CreateNewBackup(db, + static_cast(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(jbe_handle); + std::vector 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(jbe_handle); + std::vector backup_ids; + backup_engine->GetCorruptedBackups(&backup_ids); + // store backupids in int array + const std::vector::size_type + kIdSize = backup_ids.size(); + int int_backup_ids[kIdSize]; + for (std::vector::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(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(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(jbe_handle); + auto status = + backup_engine-> + PurgeOldBackups(static_cast(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(jbe_handle); + auto status = + backup_engine->DeleteBackup(static_cast(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(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(jrestore_options_handle); + auto status = + backup_engine->RestoreDBFromBackup( + static_cast(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(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(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(jbe_handle); +} diff --git a/java/rocksjni/portal.h b/java/rocksjni/portal.h index daf8b3c6e..804bbc68a 100644 --- a/java/rocksjni/portal.h +++ b/java/rocksjni/portal.h @@ -296,6 +296,15 @@ class BackupableDBOptionsJni : public RocksDBNativeClass< } }; +class BackupEngineJni : public RocksDBNativeClass< + rocksdb::BackupEngine*, BackupEngineJni> { + public: + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/BackupEngine"); + } +}; + // The portal class for org.rocksdb.RocksIterator class IteratorJni : public RocksDBNativeClass< rocksdb::Iterator*, IteratorJni> { diff --git a/java/src/main/java/org/rocksdb/BackupEngine.java b/java/src/main/java/org/rocksdb/BackupEngine.java new file mode 100644 index 000000000..2f944e5fb --- /dev/null +++ b/java/src/main/java/org/rocksdb/BackupEngine.java @@ -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 getBackupInfo() { + assert (isInitialized()); + return getBackupInfo(nativeHandle_); + } + + /** + *

Returns a list of corrupted backup ids. If there + * is no corrupted backup the method will return an + * empty list.

+ * + * @return array of backup ids as int ids. + */ + public int[] getCorruptedBackups() { + assert(isInitialized()); + return getCorruptedBackups(nativeHandle_); + } + + /** + *

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.

+ * + * @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 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); +} diff --git a/java/src/test/java/org/rocksdb/BackupEngineTest.java b/java/src/test/java/org/rocksdb/BackupEngineTest.java new file mode 100644 index 000000000..48dff19e1 --- /dev/null +++ b/java/src/test/java/org/rocksdb/BackupEngineTest.java @@ -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 = + verifyNumberOfValidBackups(be, 2); + // Delete the first backup + be.deleteBackup(backupInfo.get(0).backupId()); + final List 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 = + verifyNumberOfValidBackups(be, 4); + // Delete everything except the latest backup + be.purgeOldBackups(1); + final List 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 = 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 verifyNumberOfValidBackups(final BackupEngine be, + final int expectedNumberOfBackups) throws RocksDBException { + // Verify that backups exist + assertThat(be.getCorruptedBackups().length). + isEqualTo(0); + be.garbageCollect(); + final List 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()); + } +} diff --git a/src.mk b/src.mk index 8da93881f..00b940d9a 100644 --- a/src.mk +++ b/src.mk @@ -32,7 +32,7 @@ LIB_SOURCES = \ db/merge_helper.cc \ db/merge_operator.cc \ db/repair.cc \ - db/slice.cc \ + db/slice.cc \ db/table_cache.cc \ db/table_properties_collector.cc \ db/transaction_log_impl.cc \ @@ -88,7 +88,7 @@ LIB_SOURCES = \ util/env_hdfs.cc \ util/env_posix.cc \ util/file_util.cc \ - util/file_reader_writer.cc \ + util/file_reader_writer.cc \ util/filter_policy.cc \ util/hash.cc \ util/hash_cuckoo_rep.cc \ @@ -166,7 +166,7 @@ TEST_BENCH_SOURCES = \ db/db_iter_test.cc \ db/db_test.cc \ db/db_compaction_filter_test.cc \ - db/db_compaction_test.cc \ + db/db_compaction_test.cc \ db/db_dynamic_level_test.cc \ db/db_inplace_update_test.cc \ db/db_log_iter_test.cc \ @@ -247,6 +247,7 @@ TEST_BENCH_SOURCES = \ util/thread_local_test.cc JNI_NATIVE_SOURCES = \ + java/rocksjni/backupenginejni.cc \ java/rocksjni/backupablejni.cc \ java/rocksjni/checkpoint.cc \ java/rocksjni/columnfamilyhandle.cc \