Allow storing metadata with backups for Java API (#4111)

Summary:
Exposes BackupEngine::CreateNewBackupWithMetadata and BackupInfo metadata to the Java API.

Full disclaimer, I'm not familiar with JNI stuff, so I might have forgotten something (hopefully no memory leaks!). I also tried to find contributing guidelines but didn't see any, but I hope the PR style is consistent with the rest of the code base.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/4111

Differential Revision: D8811180

Pulled By: ajkr

fbshipit-source-id: e38b3e396c7574328c2a1a0e55acc8d092b6a569
This commit is contained in:
Nicolas Pépin-Perreault 2018-07-11 15:44:52 -07:00 committed by Facebook Github Bot
parent 1c912196de
commit cfee7fb51a
5 changed files with 113 additions and 5 deletions

View File

@ -56,6 +56,35 @@ void Java_org_rocksdb_BackupEngine_createNewBackup(
rocksdb::RocksDBExceptionJni::ThrowNew(env, status); rocksdb::RocksDBExceptionJni::ThrowNew(env, status);
} }
/*
* Class: org_rocksdb_BackupEngine
* Method: createNewBackupWithMetadata
* Signature: (JJLjava/lang/String;Z)V
*/
void Java_org_rocksdb_BackupEngine_createNewBackupWithMetadata(
JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jlong db_handle,
jstring japp_metadata, jboolean jflush_before_backup) {
auto* db = reinterpret_cast<rocksdb::DB*>(db_handle);
auto* backup_engine = reinterpret_cast<rocksdb::BackupEngine*>(jbe_handle);
jboolean has_exception = JNI_FALSE;
std::string app_metadata = rocksdb::JniUtil::copyStdString(
env, japp_metadata, &has_exception);
if (has_exception == JNI_TRUE) {
rocksdb::RocksDBExceptionJni::ThrowNew(env, "Could not copy jstring to std::string");
return;
}
auto status = backup_engine->CreateNewBackupWithMetadata(
db, app_metadata, static_cast<bool>(jflush_before_backup));
if (status.ok()) {
return;
}
rocksdb::RocksDBExceptionJni::ThrowNew(env, status);
}
/* /*
* Class: org_rocksdb_BackupEngine * Class: org_rocksdb_BackupEngine
* Method: getBackupInfo * Method: getBackupInfo

View File

@ -2364,27 +2364,38 @@ class BackupInfoJni : public JavaClass {
* @param timestamp timestamp of the backup * @param timestamp timestamp of the backup
* @param size size of the backup * @param size size of the backup
* @param number_files number of files related to the backup * @param number_files number of files related to the backup
* @param app_metadata application specific metadata
* *
* @return A reference to a Java BackupInfo object, or a nullptr if an * @return A reference to a Java BackupInfo object, or a nullptr if an
* exception occurs * exception occurs
*/ */
static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp, static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp,
uint64_t size, uint32_t number_files) { uint64_t size, uint32_t number_files, const std::string& app_metadata) {
jclass jclazz = getJClass(env); jclass jclazz = getJClass(env);
if(jclazz == nullptr) { if(jclazz == nullptr) {
// exception occurred accessing class // exception occurred accessing class
return nullptr; return nullptr;
} }
static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(IJJI)V"); static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(IJJILjava/lang/String;)V");
if(mid == nullptr) { if(mid == nullptr) {
// exception occurred accessing method // exception occurred accessing method
return nullptr; return nullptr;
} }
jstring japp_metadata = nullptr;
if (app_metadata != nullptr) {
japp_metadata = env->NewStringUTF(app_metadata.c_str());
if (japp_metadata == nullptr) {
// exception occurred creating java string
return nullptr;
}
}
jobject jbackup_info = jobject jbackup_info =
env->NewObject(jclazz, mid, backup_id, timestamp, size, number_files); env->NewObject(jclazz, mid, backup_id, timestamp, size, number_files, japp_metadata);
if(env->ExceptionCheck()) { if(env->ExceptionCheck()) {
env->DeleteLocalRef(japp_metadata);
return nullptr; return nullptr;
} }
@ -2441,7 +2452,8 @@ class BackupInfoListJni {
backup_info.backup_id, backup_info.backup_id,
backup_info.timestamp, backup_info.timestamp,
backup_info.size, backup_info.size,
backup_info.number_files); backup_info.number_files,
backup_info.app_metadata);
if(env->ExceptionCheck()) { if(env->ExceptionCheck()) {
// exception occurred constructing object // exception occurred constructing object
if(obj != nullptr) { if(obj != nullptr) {

View File

@ -81,6 +81,36 @@ public class BackupEngine extends RocksObject implements AutoCloseable {
createNewBackup(nativeHandle_, db.nativeHandle_, flushBeforeBackup); createNewBackup(nativeHandle_, db.nativeHandle_, flushBeforeBackup);
} }
/**
* Captures the state of the database in the latest backup along with
* application specific metadata.
*
* @param db The database to backup
* @param metadata Application metadata
* @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
*
* @throws RocksDBException thrown if a new backup could not be created
*/
public void createNewBackupWithMetadata(
final RocksDB db, final String metadata, final boolean flushBeforeBackup)
throws RocksDBException {
assert (isOwningHandle());
createNewBackupWithMetadata(nativeHandle_, db.nativeHandle_, metadata, flushBeforeBackup);
}
/** /**
* Gets information about the available * Gets information about the available
* backups * backups
@ -197,6 +227,9 @@ public class BackupEngine extends RocksObject implements AutoCloseable {
private native void createNewBackup(final long handle, final long dbHandle, private native void createNewBackup(final long handle, final long dbHandle,
final boolean flushBeforeBackup) throws RocksDBException; final boolean flushBeforeBackup) throws RocksDBException;
private native void createNewBackupWithMetadata(final long handle, final long dbHandle,
final String metadata, final boolean flushBeforeBackup) throws RocksDBException;
private native List<BackupInfo> getBackupInfo(final long handle); private native List<BackupInfo> getBackupInfo(final long handle);
private native int[] getCorruptedBackups(final long handle); private native int[] getCorruptedBackups(final long handle);

View File

@ -20,11 +20,12 @@ public class BackupInfo {
* @param numberFiles number of files related to this backup. * @param numberFiles number of files related to this backup.
*/ */
BackupInfo(final int backupId, final long timestamp, final long size, BackupInfo(final int backupId, final long timestamp, final long size,
final int numberFiles) { final int numberFiles, final String app_metadata) {
backupId_ = backupId; backupId_ = backupId;
timestamp_ = timestamp; timestamp_ = timestamp;
size_ = size; size_ = size;
numberFiles_ = numberFiles; numberFiles_ = numberFiles;
app_metadata_ = app_metadata;
} }
/** /**
@ -59,8 +60,17 @@ public class BackupInfo {
return numberFiles_; return numberFiles_;
} }
/**
*
* @return the associated application metadata, or null
*/
public String appMetadata() {
return app_metadata_;
}
private int backupId_; private int backupId_;
private long timestamp_; private long timestamp_;
private long size_; private long size_;
private int numberFiles_; private int numberFiles_;
private String app_metadata_;
} }

View File

@ -11,6 +11,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -205,6 +206,29 @@ public class BackupEngineTest {
} }
} }
@Test
public void backupDbWithMetadata()
throws RocksDBException {
// Open empty database.
try(final Options opt = new Options().setCreateIfMissing(true);
final RocksDB db = RocksDB.open(opt,
dbFolder.getRoot().getAbsolutePath())) {
// Fill database with some test values
prepareDatabase(db);
// Create two backups
try(final BackupableDBOptions bopt = new BackupableDBOptions(
backupFolder.getRoot().getAbsolutePath());
final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
final String metadata = String.valueOf(ThreadLocalRandom.current().nextInt());
be.createNewBackupWithMetadata(db, metadata, true);
final List<BackupInfo> backupInfoList = verifyNumberOfValidBackups(be, 1);
assertThat(backupInfoList.get(0).appMetadata()).isEqualTo(metadata);
}
}
}
/** /**
* Verify backups. * Verify backups.
* *