Add DB::OpenAsSecondary to RocksJava (#7047)
Summary: Closes https://github.com/facebook/rocksdb/issues/5852 Pull Request resolved: https://github.com/facebook/rocksdb/pull/7047 Reviewed By: cheng-chang Differential Revision: D22335162 Pulled By: pdillinger fbshipit-source-id: 75f3c524deccea7ebc0ad288da41f1ea81406c1c
This commit is contained in:
parent
bd77e34191
commit
899e59ecb7
@ -159,6 +159,7 @@ JAVA_TESTS = \
|
|||||||
org.rocksdb.RocksIteratorTest\
|
org.rocksdb.RocksIteratorTest\
|
||||||
org.rocksdb.RocksMemEnvTest\
|
org.rocksdb.RocksMemEnvTest\
|
||||||
org.rocksdb.util.SizeUnitTest\
|
org.rocksdb.util.SizeUnitTest\
|
||||||
|
org.rocksdb.SecondaryDBTest\
|
||||||
org.rocksdb.SliceTest\
|
org.rocksdb.SliceTest\
|
||||||
org.rocksdb.SnapshotTest\
|
org.rocksdb.SnapshotTest\
|
||||||
org.rocksdb.SstFileManagerTest\
|
org.rocksdb.SstFileManagerTest\
|
||||||
|
@ -208,6 +208,72 @@ jlongArray Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2_3_3B_3J(
|
|||||||
ROCKSDB_NAMESPACE::DB::Open);
|
ROCKSDB_NAMESPACE::DB::Open);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB
|
||||||
|
* Method: openAsSecondary
|
||||||
|
* Signature: (JLjava/lang/String;Ljava/lang/String;)J
|
||||||
|
*/
|
||||||
|
jlong Java_org_rocksdb_RocksDB_openAsSecondary__JLjava_lang_String_2Ljava_lang_String_2(
|
||||||
|
JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path,
|
||||||
|
jstring jsecondary_db_path) {
|
||||||
|
const char* secondary_db_path =
|
||||||
|
env->GetStringUTFChars(jsecondary_db_path, nullptr);
|
||||||
|
if (secondary_db_path == nullptr) {
|
||||||
|
// exception thrown: OutOfMemoryError
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
jlong db_handle = rocksdb_open_helper(
|
||||||
|
env, jopt_handle, jdb_path,
|
||||||
|
[secondary_db_path](const ROCKSDB_NAMESPACE::Options& options,
|
||||||
|
const std::string& db_path,
|
||||||
|
ROCKSDB_NAMESPACE::DB** db) {
|
||||||
|
return ROCKSDB_NAMESPACE::DB::OpenAsSecondary(options, db_path,
|
||||||
|
secondary_db_path, db);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we have now finished with secondary_db_path
|
||||||
|
env->ReleaseStringUTFChars(jsecondary_db_path, secondary_db_path);
|
||||||
|
|
||||||
|
return db_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB
|
||||||
|
* Method: openAsSecondary
|
||||||
|
* Signature: (JLjava/lang/String;Ljava/lang/String;[[B[J)[J
|
||||||
|
*/
|
||||||
|
jlongArray
|
||||||
|
Java_org_rocksdb_RocksDB_openAsSecondary__JLjava_lang_String_2Ljava_lang_String_2_3_3B_3J(
|
||||||
|
JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path,
|
||||||
|
jstring jsecondary_db_path, jobjectArray jcolumn_names,
|
||||||
|
jlongArray jcolumn_options) {
|
||||||
|
const char* secondary_db_path =
|
||||||
|
env->GetStringUTFChars(jsecondary_db_path, nullptr);
|
||||||
|
if (secondary_db_path == nullptr) {
|
||||||
|
// exception thrown: OutOfMemoryError
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
jlongArray jhandles = rocksdb_open_helper(
|
||||||
|
env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options,
|
||||||
|
[secondary_db_path](
|
||||||
|
const ROCKSDB_NAMESPACE::DBOptions& options,
|
||||||
|
const std::string& db_path,
|
||||||
|
const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor>&
|
||||||
|
column_families,
|
||||||
|
std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>* handles,
|
||||||
|
ROCKSDB_NAMESPACE::DB** db) {
|
||||||
|
return ROCKSDB_NAMESPACE::DB::OpenAsSecondary(
|
||||||
|
options, db_path, secondary_db_path, column_families, handles, db);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we have now finished with secondary_db_path
|
||||||
|
env->ReleaseStringUTFChars(jsecondary_db_path, secondary_db_path);
|
||||||
|
|
||||||
|
return jhandles;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: org_rocksdb_RocksDB
|
* Class: org_rocksdb_RocksDB
|
||||||
* Method: disposeInternal
|
* Method: disposeInternal
|
||||||
@ -3320,6 +3386,20 @@ void Java_org_rocksdb_RocksDB_endTrace(JNIEnv* env, jobject, jlong jdb_handle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB
|
||||||
|
* Method: tryCatchUpWithPrimary
|
||||||
|
* Signature: (J)V
|
||||||
|
*/
|
||||||
|
void Java_org_rocksdb_RocksDB_tryCatchUpWithPrimary(JNIEnv* env, jobject,
|
||||||
|
jlong jdb_handle) {
|
||||||
|
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||||
|
auto s = db->TryCatchUpWithPrimary();
|
||||||
|
if (!s.ok()) {
|
||||||
|
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: org_rocksdb_RocksDB
|
* Class: org_rocksdb_RocksDB
|
||||||
* Method: destroyDB
|
* Method: destroyDB
|
||||||
|
@ -437,6 +437,99 @@ public class RocksDB extends RocksObject {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open DB as secondary instance with only the default column family.
|
||||||
|
*
|
||||||
|
* The secondary instance can dynamically tail the MANIFEST of
|
||||||
|
* a primary that must have already been created. User can call
|
||||||
|
* {@link #tryCatchUpWithPrimary()} to make the secondary instance catch up
|
||||||
|
* with primary (WAL tailing is NOT supported now) whenever the user feels
|
||||||
|
* necessary. Column families created by the primary after the secondary
|
||||||
|
* instance starts are currently ignored by the secondary instance.
|
||||||
|
* Column families opened by secondary and dropped by the primary will be
|
||||||
|
* dropped by secondary as well. However the user of the secondary instance
|
||||||
|
* can still access the data of such dropped column family as long as they
|
||||||
|
* do not destroy the corresponding column family handle.
|
||||||
|
* WAL tailing is not supported at present, but will arrive soon.
|
||||||
|
*
|
||||||
|
* @param options the options to open the secondary instance.
|
||||||
|
* @param path the path to the primary RocksDB instance.
|
||||||
|
* @param secondaryPath points to a directory where the secondary instance
|
||||||
|
* stores its info log
|
||||||
|
*
|
||||||
|
* @return a {@link RocksDB} instance on success, null if the specified
|
||||||
|
* {@link RocksDB} can not be opened.
|
||||||
|
*
|
||||||
|
* @throws RocksDBException thrown if error happens in underlying
|
||||||
|
* native library.
|
||||||
|
*/
|
||||||
|
public static RocksDB openAsSecondary(final Options options, final String path,
|
||||||
|
final String secondaryPath) throws RocksDBException {
|
||||||
|
// when non-default Options is used, keeping an Options reference
|
||||||
|
// in RocksDB can prevent Java to GC during the life-time of
|
||||||
|
// the currently-created RocksDB.
|
||||||
|
final RocksDB db = new RocksDB(openAsSecondary(options.nativeHandle_, path, secondaryPath));
|
||||||
|
db.storeOptionsInstance(options);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open DB as secondary instance with column families.
|
||||||
|
* You can open a subset of column families in secondary mode.
|
||||||
|
*
|
||||||
|
* The secondary instance can dynamically tail the MANIFEST of
|
||||||
|
* a primary that must have already been created. User can call
|
||||||
|
* {@link #tryCatchUpWithPrimary()} to make the secondary instance catch up
|
||||||
|
* with primary (WAL tailing is NOT supported now) whenever the user feels
|
||||||
|
* necessary. Column families created by the primary after the secondary
|
||||||
|
* instance starts are currently ignored by the secondary instance.
|
||||||
|
* Column families opened by secondary and dropped by the primary will be
|
||||||
|
* dropped by secondary as well. However the user of the secondary instance
|
||||||
|
* can still access the data of such dropped column family as long as they
|
||||||
|
* do not destroy the corresponding column family handle.
|
||||||
|
* WAL tailing is not supported at present, but will arrive soon.
|
||||||
|
*
|
||||||
|
* @param options the options to open the secondary instance.
|
||||||
|
* @param path the path to the primary RocksDB instance.
|
||||||
|
* @param secondaryPath points to a directory where the secondary instance
|
||||||
|
* stores its info log.
|
||||||
|
* @param columnFamilyDescriptors list of column family descriptors
|
||||||
|
* @param columnFamilyHandles will be filled with ColumnFamilyHandle instances
|
||||||
|
* on open.
|
||||||
|
*
|
||||||
|
* @return a {@link RocksDB} instance on success, null if the specified
|
||||||
|
* {@link RocksDB} can not be opened.
|
||||||
|
*
|
||||||
|
* @throws RocksDBException thrown if error happens in underlying
|
||||||
|
* native library.
|
||||||
|
*/
|
||||||
|
public static RocksDB openAsSecondary(final DBOptions options, final String path,
|
||||||
|
final String secondaryPath, final List<ColumnFamilyDescriptor> columnFamilyDescriptors,
|
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles) throws RocksDBException {
|
||||||
|
// when non-default Options is used, keeping an Options reference
|
||||||
|
// in RocksDB can prevent Java to GC during the life-time of
|
||||||
|
// the currently-created RocksDB.
|
||||||
|
|
||||||
|
final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][];
|
||||||
|
final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()];
|
||||||
|
for (int i = 0; i < columnFamilyDescriptors.size(); i++) {
|
||||||
|
final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors.get(i);
|
||||||
|
cfNames[i] = cfDescriptor.getName();
|
||||||
|
cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long[] handles =
|
||||||
|
openAsSecondary(options.nativeHandle_, path, secondaryPath, cfNames, cfOptionHandles);
|
||||||
|
final RocksDB db = new RocksDB(handles[0]);
|
||||||
|
db.storeOptionsInstance(options);
|
||||||
|
|
||||||
|
for (int i = 1; i < handles.length; i++) {
|
||||||
|
columnFamilyHandles.add(new ColumnFamilyHandle(db, handles[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is similar to {@link #close()} except that it
|
* This is similar to {@link #close()} except that it
|
||||||
* throws an exception if any error occurs.
|
* throws an exception if any error occurs.
|
||||||
@ -4166,6 +4259,25 @@ public class RocksDB extends RocksObject {
|
|||||||
endTrace(nativeHandle_);
|
endTrace(nativeHandle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the secondary instance catch up with the primary by tailing and
|
||||||
|
* replaying the MANIFEST and WAL of the primary.
|
||||||
|
* Column families created by the primary after the secondary instance starts
|
||||||
|
* will be ignored unless the secondary instance closes and restarts with the
|
||||||
|
* newly created column families.
|
||||||
|
* Column families that exist before secondary instance starts and dropped by
|
||||||
|
* the primary afterwards will be marked as dropped. However, as long as the
|
||||||
|
* secondary instance does not delete the corresponding column family
|
||||||
|
* handles, the data of the column family is still accessible to the
|
||||||
|
* secondary.
|
||||||
|
*
|
||||||
|
* @throws RocksDBException thrown if error happens in underlying
|
||||||
|
* native library.
|
||||||
|
*/
|
||||||
|
public void tryCatchUpWithPrimary() throws RocksDBException {
|
||||||
|
tryCatchUpWithPrimary(nativeHandle_);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete files in multiple ranges at once.
|
* Delete files in multiple ranges at once.
|
||||||
* Delete files in a lot of ranges one at a time can be slow, use this API for
|
* Delete files in a lot of ranges one at a time can be slow, use this API for
|
||||||
@ -4289,6 +4401,13 @@ public class RocksDB extends RocksObject {
|
|||||||
final long[] columnFamilyOptions
|
final long[] columnFamilyOptions
|
||||||
) throws RocksDBException;
|
) throws RocksDBException;
|
||||||
|
|
||||||
|
private native static long openAsSecondary(final long optionsHandle, final String path,
|
||||||
|
final String secondaryPath) throws RocksDBException;
|
||||||
|
|
||||||
|
private native static long[] openAsSecondary(final long optionsHandle, final String path,
|
||||||
|
final String secondaryPath, final byte[][] columnFamilyNames,
|
||||||
|
final long[] columnFamilyOptions) throws RocksDBException;
|
||||||
|
|
||||||
@Override protected native void disposeInternal(final long handle);
|
@Override protected native void disposeInternal(final long handle);
|
||||||
|
|
||||||
private native static void closeDatabase(final long handle)
|
private native static void closeDatabase(final long handle)
|
||||||
@ -4535,6 +4654,7 @@ public class RocksDB extends RocksObject {
|
|||||||
private native void startTrace(final long handle, final long maxTraceFileSize,
|
private native void startTrace(final long handle, final long maxTraceFileSize,
|
||||||
final long traceWriterHandle) throws RocksDBException;
|
final long traceWriterHandle) throws RocksDBException;
|
||||||
private native void endTrace(final long handle) throws RocksDBException;
|
private native void endTrace(final long handle) throws RocksDBException;
|
||||||
|
private native void tryCatchUpWithPrimary(final long handle) throws RocksDBException;
|
||||||
private native void deleteFilesInRanges(long handle, long cfHandle, final byte[][] ranges,
|
private native void deleteFilesInRanges(long handle, long cfHandle, final byte[][] ranges,
|
||||||
boolean include_end) throws RocksDBException;
|
boolean include_end) throws RocksDBException;
|
||||||
|
|
||||||
|
135
java/src/test/java/org/rocksdb/SecondaryDBTest.java
Normal file
135
java/src/test/java/org/rocksdb/SecondaryDBTest.java
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
package org.rocksdb;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
public class SecondaryDBTest {
|
||||||
|
@ClassRule
|
||||||
|
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
|
||||||
|
new RocksNativeLibraryResource();
|
||||||
|
|
||||||
|
@Rule public TemporaryFolder dbFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Rule public TemporaryFolder secondaryDbFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAsSecondary() throws RocksDBException {
|
||||||
|
try (final Options options = new Options().setCreateIfMissing(true);
|
||||||
|
final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) {
|
||||||
|
db.put("key1".getBytes(), "value1".getBytes());
|
||||||
|
db.put("key2".getBytes(), "value2".getBytes());
|
||||||
|
db.put("key3".getBytes(), "value3".getBytes());
|
||||||
|
|
||||||
|
// open secondary
|
||||||
|
try (final Options secondaryOptions = new Options();
|
||||||
|
final RocksDB secondaryDb =
|
||||||
|
RocksDB.openAsSecondary(secondaryOptions, dbFolder.getRoot().getAbsolutePath(),
|
||||||
|
secondaryDbFolder.getRoot().getAbsolutePath())) {
|
||||||
|
assertThat(secondaryDb.get("key1".getBytes())).isEqualTo("value1".getBytes());
|
||||||
|
assertThat(secondaryDb.get("key2".getBytes())).isEqualTo("value2".getBytes());
|
||||||
|
assertThat(secondaryDb.get("key3".getBytes())).isEqualTo("value3".getBytes());
|
||||||
|
|
||||||
|
// write to primary
|
||||||
|
db.put("key4".getBytes(), "value4".getBytes());
|
||||||
|
db.put("key5".getBytes(), "value5".getBytes());
|
||||||
|
db.put("key6".getBytes(), "value6".getBytes());
|
||||||
|
|
||||||
|
// tell secondary to catch up
|
||||||
|
secondaryDb.tryCatchUpWithPrimary();
|
||||||
|
|
||||||
|
db.put("key7".getBytes(), "value7".getBytes());
|
||||||
|
|
||||||
|
// check secondary
|
||||||
|
assertThat(secondaryDb.get("key4".getBytes())).isEqualTo("value4".getBytes());
|
||||||
|
assertThat(secondaryDb.get("key5".getBytes())).isEqualTo("value5".getBytes());
|
||||||
|
assertThat(secondaryDb.get("key6".getBytes())).isEqualTo("value6".getBytes());
|
||||||
|
|
||||||
|
assertThat(secondaryDb.get("key7".getBytes())).isNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openAsSecondaryColumnFamilies() throws RocksDBException {
|
||||||
|
try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) {
|
||||||
|
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
|
||||||
|
cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts));
|
||||||
|
cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes(), cfOpts));
|
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
|
||||||
|
|
||||||
|
try (final DBOptions options =
|
||||||
|
new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true);
|
||||||
|
final RocksDB db = RocksDB.open(
|
||||||
|
options, dbFolder.getRoot().getAbsolutePath(), cfDescriptors, cfHandles)) {
|
||||||
|
try {
|
||||||
|
final ColumnFamilyHandle cf1 = cfHandles.get(1);
|
||||||
|
|
||||||
|
db.put(cf1, "key1".getBytes(), "value1".getBytes());
|
||||||
|
db.put(cf1, "key2".getBytes(), "value2".getBytes());
|
||||||
|
db.put(cf1, "key3".getBytes(), "value3".getBytes());
|
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> secondaryCfHandles = new ArrayList<>();
|
||||||
|
|
||||||
|
// open secondary
|
||||||
|
try (final DBOptions secondaryOptions = new DBOptions();
|
||||||
|
final RocksDB secondaryDb =
|
||||||
|
RocksDB.openAsSecondary(secondaryOptions, dbFolder.getRoot().getAbsolutePath(),
|
||||||
|
secondaryDbFolder.getRoot().getAbsolutePath(), cfDescriptors,
|
||||||
|
secondaryCfHandles)) {
|
||||||
|
try {
|
||||||
|
final ColumnFamilyHandle secondaryCf1 = secondaryCfHandles.get(1);
|
||||||
|
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key1".getBytes()))
|
||||||
|
.isEqualTo("value1".getBytes());
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key2".getBytes()))
|
||||||
|
.isEqualTo("value2".getBytes());
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key3".getBytes()))
|
||||||
|
.isEqualTo("value3".getBytes());
|
||||||
|
|
||||||
|
// write to primary
|
||||||
|
db.put(cf1, "key4".getBytes(), "value4".getBytes());
|
||||||
|
db.put(cf1, "key5".getBytes(), "value5".getBytes());
|
||||||
|
db.put(cf1, "key6".getBytes(), "value6".getBytes());
|
||||||
|
|
||||||
|
// tell secondary to catch up
|
||||||
|
secondaryDb.tryCatchUpWithPrimary();
|
||||||
|
|
||||||
|
db.put(cf1, "key7".getBytes(), "value7".getBytes());
|
||||||
|
|
||||||
|
// check secondary
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key4".getBytes()))
|
||||||
|
.isEqualTo("value4".getBytes());
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key5".getBytes()))
|
||||||
|
.isEqualTo("value5".getBytes());
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key6".getBytes()))
|
||||||
|
.isEqualTo("value6".getBytes());
|
||||||
|
|
||||||
|
assertThat(secondaryDb.get(secondaryCf1, "key7".getBytes())).isNull();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
for (final ColumnFamilyHandle secondaryCfHandle : secondaryCfHandles) {
|
||||||
|
secondaryCfHandle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) {
|
||||||
|
cfHandle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user