[RocksJava] TTL Support

This commit is contained in:
fyrz 2014-11-21 23:38:17 +01:00
parent ca47da9e63
commit 5ff8aec4db
3 changed files with 213 additions and 24 deletions

View File

@ -7,20 +7,85 @@ package org.rocksdb;
import java.util.List;
/**
* Database with TTL support.
*
* <p><strong>Use case</strong></p>
* <p>This API should be used to open the db when key-values inserted are
* meant to be removed from the db in a non-strict 'ttl' amount of time
* Therefore, this guarantees that key-values inserted will remain in the
* db for &gt;= ttl amount of time and the db will make efforts to remove the
* key-values as soon as possible after ttl seconds of their insertion.
* </p>
*
* <p><strong>Behaviour</strong></p>
* <p>TTL is accepted in seconds
* (int32_t)Timestamp(creation) is suffixed to values in Put internally
* Expired TTL values deleted in compaction only:(Timestamp+ttl&lt;time_now)
* Get/Iterator may return expired entries(compaction not run on them yet)
* Different TTL may be used during different Opens
* </p>
*
* <p><strong>Example</strong></p>
* <ul>
* <li>Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2</li>
* <li>Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t&gt;=5</li>
* </ul>
*
* <p>
* read_only=true opens in the usual read-only mode. Compactions will not be
* triggered(neither manual nor automatic), so no expired entries removed
* </p>
*
* <p><strong>Constraints</strong></p>
* <p>Not specifying/passing or non-positive TTL behaves
* like TTL = infinity</p>
*
* <p><strong>!!!WARNING!!!</strong></p>
* <p>Calling DB::Open directly to re-open a db created by this API will get
* corrupt values(timestamp suffixed) and no ttl effect will be there
* during the second Open, so use this API consistently to open the db
* Be careful when passing ttl with a small positive value because the
* whole database may be deleted in a small amount of time.</p>
*/
public class TtlDB extends RocksDB {
//static Status Open(const Options& options, const std::string& dbname,
// DBWithTTL** dbptr, int32_t ttl = 0,
// bool read_only = false);
/**
* <p>Opens a TtlDB.</p>
*
* <p>Database is opened in read-write mode without default TTL.</p>
*
* @param options {@link org.rocksdb.Options} instance.
* @param db_path path to database.
*
* @return TtlDB instance.
*
* @throws RocksDBException thrown if an error occurs within the native
* part of the library.
*/
public static TtlDB open(Options options, String db_path)
throws RocksDBException {
return open(options, db_path, 0, false);
}
/**
* <p>Opens a TtlDB.</p>
*
* @param options {@link org.rocksdb.Options} instance.
* @param db_path path to database.
* @param ttl time to live for new entries.
* @param readOnly boolean value indicating if database if db is
* opened read-only.
*
* @return TtlDB instance.
*
* @throws RocksDBException thrown if an error occurs within the native
* part of the library.
*/
public static TtlDB open(Options options, String db_path, int ttl,
boolean readOnly) throws RocksDBException {
TtlDB ttldb = new TtlDB();
ttldb.open(options.nativeHandle_, db_path, ttl, readOnly);
// Prevent the RocksDB object from attempting to delete
// the underly C++ DB object.
//ttldb.disOwnNativeHandle();
return ttldb;
}
@ -38,17 +103,39 @@ public class TtlDB extends RocksDB {
return null;
}
/**
* <p>Creates a new ttl based column family with a name defined
* in given ColumnFamilyDescriptor and allocates a
* ColumnFamilyHandle within an internal structure.</p>
*
* <p>The ColumnFamilyHandle is automatically disposed with DB
* disposal.</p>
*
* @param columnFamilyDescriptor column family to be created.
* @param ttl TTL to set for this column family.
*
* @return {@link org.rocksdb.ColumnFamilyHandle} instance.
*
* @throws RocksDBException thrown if error happens in underlying
* native library.
*/
public ColumnFamilyHandle createColumnFamilyWithTtl(
ColumnFamilyDescriptor columnFamilyDescriptor, int ttl) {
return null;
ColumnFamilyDescriptor columnFamilyDescriptor, int ttl)
throws RocksDBException {
assert(isInitialized());
return new ColumnFamilyHandle(this,
createColumnFamilyWithTtl(nativeHandle_,
columnFamilyDescriptor, ttl));
}
/**
* Close the TtlDB instance and release resource.
* <p>Close the TtlDB instance and release resource.</p>
*
* Internally, TtlDB owns the {@code rocksdb::DB} pointer to its associated
* {@link org.rocksdb.RocksDB}. The release of that RocksDB pointer is handled in the destructor
* of the c++ {@code rocksdb::TtlDB} and should be transparent to Java developers.
* <p>Internally, TtlDB owns the {@code rocksdb::DB} pointer
* to its associated {@link org.rocksdb.RocksDB}. The release
* of that RocksDB pointer is handled in the destructor of the
* c++ {@code rocksdb::TtlDB} and should be transparent to
* Java developers.</p>
*/
@Override public synchronized void close() {
if (isInitialized()) {
@ -57,9 +144,13 @@ public class TtlDB extends RocksDB {
}
/**
* A protected construction that will be used in the static factory
* method {@link #open(DBOptions, String, java.util.List, java.util.List)} and
* {@link #open(DBOptions, String, java.util.List, java.util.List, java.util.List, boolean)}.
* <p>A protected constructor that will be used in the static
* factory method
* {@link #open(DBOptions, String, java.util.List, java.util.List)}
* and
* {@link #open(DBOptions, String, java.util.List,
* java.util.List, java.util.List, boolean)}.
* </p>
*/
protected TtlDB() {
super();
@ -72,4 +163,7 @@ public class TtlDB extends RocksDB {
private native void open(long optionsHandle, String db_path, int ttl,
boolean readOnly) throws RocksDBException;
private native long createColumnFamilyWithTtl(long handle,
ColumnFamilyDescriptor columnFamilyDescriptor, int ttl)
throws RocksDBException;
}

View File

@ -9,9 +9,9 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.Options;
import org.rocksdb.RocksDBException;
import org.rocksdb.TtlDB;
import org.rocksdb.*;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@ -25,19 +25,49 @@ public class TtlDBTest {
public TemporaryFolder dbFolder = new TemporaryFolder();
@Test
public void ttlDBOpen() throws RocksDBException, InterruptedException {
public void ttlDBOpen() throws RocksDBException,
InterruptedException {
Options options = null;
TtlDB ttlDB = null;
try {
options = new Options().setCreateIfMissing(true);
options = new Options().
setCreateIfMissing(true).
setMaxGrandparentOverlapFactor(0).
setMaxMemCompactionLevel(0);
ttlDB = TtlDB.open(options,
dbFolder.getRoot().getAbsolutePath());
ttlDB.put("key".getBytes(), "value".getBytes());
assertThat(ttlDB.get("key".getBytes())).
isEqualTo("value".getBytes());
assertThat(ttlDB.get("key".getBytes())).isNotNull();
} finally {
if (ttlDB != null) {
ttlDB.close();
}
if (options != null) {
options.dispose();
}
}
}
@Test
public void ttlDBOpenWithTtl() throws RocksDBException,
InterruptedException {
Options options = null;
TtlDB ttlDB = null;
try {
options = new Options().
setCreateIfMissing(true).
setMaxGrandparentOverlapFactor(0).
setMaxMemCompactionLevel(0);
ttlDB = TtlDB.open(options, dbFolder.getRoot().getAbsolutePath(),
1, false);
ttlDB.put("key".getBytes(), "value".getBytes());
assertThat(ttlDB.get("key".getBytes())).
isEqualTo("value".getBytes());
Thread.sleep(1250);
ttlDB.compactRange();
TimeUnit.SECONDS.sleep(2);
ttlDB.compactRange();
assertThat(ttlDB.get("key".getBytes())).isNull();
} finally {
if (ttlDB != null) {
@ -48,4 +78,36 @@ public class TtlDBTest {
}
}
}
@Test
public void createTtlColumnFamily() throws RocksDBException,
InterruptedException {
Options options = null;
TtlDB ttlDB = null;
ColumnFamilyHandle columnFamilyHandle = null;
try {
options = new Options().setCreateIfMissing(true);
ttlDB = TtlDB.open(options,
dbFolder.getRoot().getAbsolutePath());
columnFamilyHandle = ttlDB.createColumnFamilyWithTtl(
new ColumnFamilyDescriptor("new_cf"), 1);
ttlDB.put(columnFamilyHandle, "key".getBytes(),
"value".getBytes());
assertThat(ttlDB.get(columnFamilyHandle, "key".getBytes())).
isEqualTo("value".getBytes());
Thread.sleep(2500);
ttlDB.compactRange(columnFamilyHandle);
assertThat(ttlDB.get(columnFamilyHandle, "key".getBytes())).isNull();
} finally {
if (columnFamilyHandle != null) {
columnFamilyHandle.dispose();
}
if (ttlDB != null) {
ttlDB.close();
}
if (options != null) {
options.dispose();
}
}
}
}

View File

@ -40,3 +40,36 @@ void Java_org_rocksdb_TtlDB_open(JNIEnv* env, jobject jttldb,
}
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
}
/*
* Class: org_rocksdb_TtlDB
* Method: createColumnFamilyWithTtl
* Signature: (JLorg/rocksdb/ColumnFamilyDescriptor;I)J;
*/
jlong Java_org_rocksdb_TtlDB_createColumnFamilyWithTtl(
JNIEnv* env, jobject jobj, jlong jdb_handle,
jobject jcf_descriptor, jint jttl) {
rocksdb::ColumnFamilyHandle* handle;
auto db_handle = reinterpret_cast<rocksdb::DBWithTTL*>(jdb_handle);
jstring jstr = (jstring) env->CallObjectMethod(jcf_descriptor,
rocksdb::ColumnFamilyDescriptorJni::getColumnFamilyNameMethod(
env));
// get CF Options
jobject jcf_opt_obj = env->CallObjectMethod(jcf_descriptor,
rocksdb::ColumnFamilyDescriptorJni::getColumnFamilyOptionsMethod(
env));
rocksdb::ColumnFamilyOptions* cfOptions =
rocksdb::ColumnFamilyOptionsJni::getHandle(env, jcf_opt_obj);
const char* cfname = env->GetStringUTFChars(jstr, 0);
rocksdb::Status s = db_handle->CreateColumnFamilyWithTtl(
*cfOptions, cfname, &handle, jttl);
env->ReleaseStringUTFChars(jstr, cfname);
if (s.ok()) {
return reinterpret_cast<jlong>(handle);
}
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
return 0;
}