diff --git a/java/jmh/README.md b/java/jmh/README.md index d9ba1e60ba5..53476be049a 100644 --- a/java/jmh/README.md +++ b/java/jmh/README.md @@ -6,10 +6,10 @@ These are micro-benchmarks for RocksJava functionality, using [JMH (Java Microbe **Note**: This uses a specific build of RocksDB that is set in the `` element of the `dependencies` section of the `pom.xml` file. If you are testing local changes you should build and install a SNAPSHOT version of rocksdbjni, and update the `pom.xml` of rocksdbjni-jmh file to test with this. -For instance, this is how to install the OSX jar you just built for 8.11.0 +For instance, this is how to install the OSX jar you just built for 9.8.0 ```bash -$ mvn install:install-file -Dfile=./java/target/rocksdbjni-8.11.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=8.11.0-SNAPSHOT -Dpackaging=jar +$ mvn install:install-file -Dfile=./java/target/rocksdbjni-9.8.0-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=9.8.0-SNAPSHOT -Dpackaging=jar ``` ```bash @@ -22,3 +22,22 @@ $ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar ``` NOTE: you can append `-help` to the command above to see all of the JMH runtime options. + +### Performance bug in Java `get()` + +See this [issue](https://github.com/facebook/rocksdb/issues/13023) + +#### Before fix (single) + +Benchmark (columnFamilyTestType) (keyCount) (keySize) (nthMissingKey) (valueSize) Mode Cnt Score Error Units +GetNotFoundBenchmarks.getNotFoundEven no_column_family 100000 12 2 16 thrpt 15 289245.405 ± 1727.615 ops/s +GetNotFoundBenchmarks.getNotFoundOdd no_column_family 100000 12 2 16 thrpt 15 717300.753 ± 7040.178 ops/s + +#### After fix (single) + +Benchmark (columnFamilyTestType) (keyCount) (keySize) (nthMissingKey) (valueSize) Mode Cnt Score Error Units +GetNotFoundBenchmarks.getNotFoundEven no_column_family 100000 12 2 16 thrpt 15 856149.231 ± 10159.858 ops/s +GetNotFoundBenchmarks.getNotFoundOdd no_column_family 100000 12 2 16 thrpt 15 726021.153 ± 4047.159 ops/s + + + diff --git a/java/jmh/pom.xml b/java/jmh/pom.xml index 8400e1e67ee..bc557c66a1e 100644 --- a/java/jmh/pom.xml +++ b/java/jmh/pom.xml @@ -50,7 +50,7 @@ org.rocksdb rocksdbjni - 9.0.0 + 9.8.0-SNAPSHOT diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/GetNotFoundBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/GetNotFoundBenchmarks.java new file mode 100644 index 00000000000..8422ee2355e --- /dev/null +++ b/java/jmh/src/main/java/org/rocksdb/jmh/GetNotFoundBenchmarks.java @@ -0,0 +1,191 @@ +/** + * 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.jmh; + +import static org.rocksdb.util.KVUtils.ba; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.rocksdb.*; +import org.rocksdb.util.FileUtils; + +@State(Scope.Benchmark) +public class GetNotFoundBenchmarks { + @Param({"no_column_family", "20_column_families"}) String columnFamilyTestType; + + @Param({"1000", "100000"}) int keyCount; + private final static int extraKeyCount = + 16; // some slack to deal with odd/even tests rounding up key values + + @Param({"12"}) int keySize; + + @Param({"16"}) int valueSize; + + /** + * Don't create every n-th key + * Usually, just use "2" for making the half-present array + * 0 means no missing keys + */ + @Param({"2", "16", "0"}) int nthMissingKey; + + Path dbDir; + DBOptions options; + ReadOptions readOptions; + int cfs = 0; // number of column families + private AtomicInteger cfHandlesIdx; + ColumnFamilyHandle[] cfHandles; + RocksDB db; + private final AtomicInteger keyIndex = new AtomicInteger(); + private ByteBuffer keyBuf; + private ByteBuffer valueBuf; + private byte[] keyArr; + private byte[] valueArr; + + @Setup(Level.Trial) + public void setup() throws IOException, RocksDBException { + RocksDB.loadLibrary(); + + dbDir = Files.createTempDirectory("rocksjava-get-benchmarks"); + + options = new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); + readOptions = new ReadOptions(); + + final List cfDescriptors = new ArrayList<>(); + cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); + + if ("20_column_families".equals(columnFamilyTestType)) { + cfs = 20; + } + + if (cfs > 0) { + cfHandlesIdx = new AtomicInteger(1); + for (int i = 1; i <= cfs; i++) { + cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i))); + } + } + + final List cfHandlesList = new ArrayList<>(cfDescriptors.size()); + db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList); + cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]); + + // store initial data for retrieving via get + keyArr = new byte[keySize]; + valueArr = new byte[valueSize]; + Arrays.fill(keyArr, (byte) 0x30); + Arrays.fill(valueArr, (byte) 0x30); + for (int i = 0; i <= cfs; i++) { + for (int j = 0; j < keyCount; j++) { + if (nthMissingKey <= 0 || j % nthMissingKey != 0) { + final byte[] keyPrefix = ba("key" + j); + final byte[] valuePrefix = ba("value" + j); + System.arraycopy(keyPrefix, 0, keyArr, 0, keyPrefix.length); + System.arraycopy(valuePrefix, 0, valueArr, 0, valuePrefix.length); + db.put(cfHandles[i], keyArr, valueArr); + } + } + } + + try (final FlushOptions flushOptions = new FlushOptions().setWaitForFlush(true)) { + db.flush(flushOptions); + } + + keyBuf = ByteBuffer.allocateDirect(keySize); + valueBuf = ByteBuffer.allocateDirect(valueSize); + Arrays.fill(keyArr, (byte) 0x30); + Arrays.fill(valueArr, (byte) 0x30); + keyBuf.put(keyArr); + keyBuf.flip(); + valueBuf.put(valueArr); + valueBuf.flip(); + } + + @TearDown(Level.Trial) + public void cleanup() throws IOException { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + db.close(); + options.close(); + readOptions.close(); + FileUtils.delete(dbDir); + } + + private ColumnFamilyHandle getColumnFamily() { + if (cfs == 0) { + return cfHandles[0]; + } else if (cfs == 1) { + return cfHandles[1]; + } else { + int idx = cfHandlesIdx.getAndIncrement(); + if (idx > cfs) { + cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok + idx = 0; + } + return cfHandles[idx]; + } + } + + /** + * Takes the next position in the index. + */ + private int next(final int increment) { + int idx; + int nextIdx; + while (true) { + idx = keyIndex.get(); + nextIdx = idx + increment; + if (nextIdx >= keyCount) { + nextIdx = nextIdx % keyCount; + } + if (keyIndex.compareAndSet(idx, nextIdx)) { + break; + } + } + + return idx; + } + + private int next() { + return next(1); + } + + // String -> byte[] + private byte[] getKeyArr(final int keyOffset, final int increment) { + final int MAX_LEN = 9; // key100000 + final int keyIdx = next(increment); + final byte[] keyPrefix = ba("key" + (keyIdx + keyOffset)); + System.arraycopy(keyPrefix, 0, keyArr, 0, keyPrefix.length); + Arrays.fill(keyArr, keyPrefix.length, MAX_LEN, (byte) 0x30); + return keyArr; + } + + /** + * Get even values, these should not be present + * @throws RocksDBException + */ + @Benchmark + public void getNotFoundEven(Blackhole blackhole) throws RocksDBException { + blackhole.consume(db.get(getColumnFamily(), getKeyArr(0, 2))); + } + + /** + * Get odd values 1, 3, 5, these should be present + * @throws RocksDBException + */ + @Benchmark + public void getNotFoundOdd(Blackhole blackhole) throws RocksDBException { + blackhole.consume(db.get(getColumnFamily(), getKeyArr(1, 2))); + } +} \ No newline at end of file diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/MultiNotFoundBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/MultiNotFoundBenchmarks.java new file mode 100644 index 00000000000..c579ce07330 --- /dev/null +++ b/java/jmh/src/main/java/org/rocksdb/jmh/MultiNotFoundBenchmarks.java @@ -0,0 +1,276 @@ +package org.rocksdb.jmh; + +import static org.rocksdb.util.KVUtils.ba; +import static org.rocksdb.util.KVUtils.keys; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.rocksdb.*; +import org.rocksdb.util.FileUtils; + +@State(Scope.Thread) +public class MultiNotFoundBenchmarks { + @Param({"no_column_family", "20_column_families"}) String columnFamilyTestType; + + @Param({"100000"}) int keyCount; + + /** + * Don't create every n-th key + * Usually, just use "2" for making the + */ + @Param({"2", "16"}) int nthMissingKey; + + @Param({ + "10", + "100", + "1000", + "10000", + }) + int multiGetSize; + + @Param({"16"}) int valueSize; + + @Param({"16"}) int keySize; // big enough + + Path dbDir; + DBOptions options; + int cfs = 0; // number of column families + private AtomicInteger cfHandlesIdx; + ColumnFamilyHandle[] cfHandles; + RocksDB db; + private final AtomicInteger keyIndex = new AtomicInteger(); + + private List defaultCFHandles = new ArrayList<>(); + + @Setup(Level.Trial) + public void setup() throws IOException, RocksDBException { + RocksDB.loadLibrary(); + + dbDir = Files.createTempDirectory("rocksjava-multiget-benchmarks"); + + options = new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); + + final List cfDescriptors = new ArrayList<>(); + cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); + + if ("20_column_families".equals(columnFamilyTestType)) { + cfs = 20; + } + + if (cfs > 0) { + cfHandlesIdx = new AtomicInteger(1); + for (int i = 1; i <= cfs; i++) { + cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i))); + } + } + + final List cfHandlesList = new ArrayList<>(cfDescriptors.size()); + db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList); + cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]); + + // store initial data for retrieving via get + for (int j = 0; j < keyCount; j++) { + if (j % nthMissingKey != 0) { + final byte[] paddedValue = Arrays.copyOf(ba("value" + j), valueSize); + db.put(ba("key" + j), paddedValue); + } + } + + // store initial data for retrieving via get - column families + for (int i = 0; i < cfs; i++) { + for (int j = 0; j < keyCount; j++) { + if (j % nthMissingKey != 0) { + final byte[] paddedValue = Arrays.copyOf(ba("value" + j), valueSize); + db.put(cfHandles[i], ba("key" + j), paddedValue); + } + } + } + + // build a big list of default column families for efficient passing + final ColumnFamilyHandle defaultCFH = db.getDefaultColumnFamily(); + for (int i = 0; i < keyCount; i++) { + defaultCFHandles.add(defaultCFH); + } + + // list of random cfs + try (final FlushOptions flushOptions = new FlushOptions().setWaitForFlush(true)) { + db.flush(flushOptions); + } + } + + @TearDown(Level.Trial) + public void cleanup() throws IOException { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + db.close(); + options.close(); + FileUtils.delete(dbDir); + } + + private int next(final int inc, final int limit) { + int idx; + int nextIdx; + while (true) { + idx = keyIndex.get(); + nextIdx = idx + inc; + if (nextIdx >= limit) { + nextIdx = inc; + } + + if (keyIndex.compareAndSet(idx, nextIdx)) { + break; + } + } + + if (nextIdx >= limit) { + return -1; + } else { + return idx; + } + } + + ByteBuffer keysBuffer; + ByteBuffer valuesBuffer; + + List valueBuffersList; + List keyBuffersList; + + @Setup + public void allocateSliceBuffers() { + keysBuffer = ByteBuffer.allocateDirect(keyCount * keySize); + valuesBuffer = ByteBuffer.allocateDirect(keyCount * valueSize); + valueBuffersList = new ArrayList<>(); + keyBuffersList = new ArrayList<>(); + for (int i = 0; i < keyCount; i++) { + valueBuffersList.add(valuesBuffer.slice(i * valueSize, valueSize)); + keyBuffersList.add(keysBuffer.slice(i * keySize, keySize)); + } + } + + @TearDown + public void freeSliceBuffers() { + valueBuffersList.clear(); + } + + private List filter(List before, final int initial, final int step) { + final List after = new ArrayList<>(before.size()); + for (int i = initial; i < before.size(); i += step) { + after.add(before.get(i)); + } + return after; + } + + private List filterByteBuffers( + List before, final int initial, final int step) { + final List after = new ArrayList<>(before.size()); + for (int i = initial; i < before.size(); i += step) { + after.add(before.get(i)); + } + return after; + } + + /** + * Perform get on the even-numbered keys, which should not exist + * This should be faster (certainly not slower) than the even-numbered keys, + * but has at times been found to be slower due to implementation issues in the not-found path. + * + * @throws RocksDBException + */ + @Benchmark + public void multiNotFoundListEvens() throws RocksDBException { + final int fromKeyIdx = next(2 * multiGetSize, keyCount); + if (fromKeyIdx >= 0) { + final List keys = filter(keys(fromKeyIdx, fromKeyIdx + 2 * multiGetSize), 0, 2); + final List valueResults = db.multiGetAsList(keys); + for (final byte[] result : valueResults) { + if (result != null) + throw new RuntimeException("Test wrongly returned a value"); + } + } + } + + /** + * Perform get on the odd-numbered keys, which should exist + * This is for reference comparison w/ non-existent keys, see above + * + * @throws RocksDBException + */ + @Benchmark + public void multiNotFoundListOdds() throws RocksDBException { + final int fromKeyIdx = next(2 * multiGetSize, keyCount); + if (fromKeyIdx >= 0) { + final List keys = filter(keys(fromKeyIdx, fromKeyIdx + 2 * multiGetSize), 1, 2); + final List valueResults = db.multiGetAsList(keys); + for (final byte[] result : valueResults) { + if (result.length != valueSize) + throw new RuntimeException("Test valueSize assumption wrong"); + } + } + } + + @Benchmark + public List multiNotFoundBBEvens() throws RocksDBException { + final int fromKeyIdx = next(2 * multiGetSize, keyCount); + if (fromKeyIdx >= 0) { + final List keys = + filterByteBuffers(keys(keyBuffersList, fromKeyIdx, fromKeyIdx + 2 * multiGetSize), 0, 2); + final List values = + valueBuffersList.subList(fromKeyIdx, fromKeyIdx + multiGetSize); + final List statusResults = db.multiGetByteBuffers(keys, values); + for (final ByteBufferGetStatus result : statusResults) { + if (result.status.getCode() != Status.Code.NotFound) + throw new RuntimeException("Test status should be NotFound, was: " + result.status); + } + } + return new ArrayList<>(); + } + + @Benchmark + public List multiNotFoundBBOdds() throws RocksDBException { + final int fromKeyIdx = next(2 * multiGetSize, keyCount); + if (fromKeyIdx >= 0) { + final List keys = + filterByteBuffers(keys(keyBuffersList, fromKeyIdx, fromKeyIdx + 2 * multiGetSize), 1, 2); + final List values = + valueBuffersList.subList(fromKeyIdx, fromKeyIdx + multiGetSize); + final List statusResults = db.multiGetByteBuffers(keys, values); + for (final ByteBufferGetStatus result : statusResults) { + if (result.status.getCode() != Status.Code.Ok) + throw new RuntimeException("Test status not OK: " + result.status); + if (result.value.limit() != valueSize) + throw new RuntimeException("Test valueSize assumption wrong"); + } + } + return new ArrayList<>(); + } + + public static void main(final String[] args) throws RunnerException { + final org.openjdk.jmh.runner.options.Options opt = + new OptionsBuilder() + .include(MultiNotFoundBenchmarks.class.getSimpleName()) + .forks(1) + .jvmArgs("-ea") + .warmupIterations(1) + .measurementIterations(2) + .forks(2) + .param("columnFamilyTestType=", "no_column_family") + .param("multiGetSize=", "1000") + .param("keyCount=", "10000") + .param("nthMissingKey=", "2") + .output("jmh_output") + .build(); + + new Runner(opt).run(); + } +} diff --git a/java/rocksjni/kv_helper.h b/java/rocksjni/kv_helper.h index 5f0a8ffc57e..e1eecf3ceb6 100644 --- a/java/rocksjni/kv_helper.h +++ b/java/rocksjni/kv_helper.h @@ -34,25 +34,32 @@ class KVException : public std::exception { public: // These values are expected on Java API calls to represent the result of a // Get() which has failed; a negative length is returned to indicate an error. - static const int kNotFound = -1; // the key was not found in RocksDB - static const int kStatusError = - -2; // there was some other error fetching the value for the key + + // the key was not found in RocksDB + // The return of NotFound is usually not implemented through this exception + // mechanism, but rather it is tested directly from a RocksDB Status. This + // because it can be considered an expected result (e.g. for get() methods), + // and the performance of C++ exceptions is "horrible when thrown". This is + // still the most sensible place to define the value. + static const int kNotFound = -1; + + // there was some other error fetching the value for the key + // Almost certainly this will mean that the JNI Env contains an exception, + // which will trigger a Java exception when the JNI call returns to Java. + static const int kStatusError = -2; /** - * @brief Throw a KVException (and potentially a Java exception) if the + * @brief Throw a KVException and a Java exception if the * RocksDB status is "bad" * + * + * * @param env JNI environment needed to create a Java exception * @param status RocksDB status to examine */ - static void ThrowOnError(JNIEnv* env, const Status& status) { - if (status.ok()) { - return; - } - if (status.IsNotFound()) { - // IsNotFound does not generate a Java Exception, any other bad status - // does.. - throw KVException(kNotFound); + static inline const Status ThrowOnError(JNIEnv* env, const Status& status) { + if (status.ok() || status.IsNotFound()) { + return status; } ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); throw KVException(kStatusError); @@ -209,7 +216,8 @@ class JByteArrayPinnableSlice { /** * @brief create a new Java buffer and copy the result into it * - * @return jbyteArray the java buffer holding the result + * @return jbyteArray the java buffer holding the result, nullptr if a buffer + * could not be allocated */ jbyteArray NewByteArray() { const jint pinnable_len = static_cast(pinnable_slice_.size()); diff --git a/java/rocksjni/rocksjni.cc b/java/rocksjni/rocksjni.cc index abd2d58c8fd..478bc9fa496 100644 --- a/java/rocksjni/rocksjni.cc +++ b/java/rocksjni/rocksjni.cc @@ -1210,7 +1210,8 @@ jint Java_org_rocksdb_RocksDB_getDirect(JNIEnv* env, jclass /*jdb*/, db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice()); } - ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s); + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); @@ -1453,10 +1454,11 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII(JNIEnv* env, jclass, try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { @@ -1484,9 +1486,10 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ(JNIEnv* env, jclass, try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { @@ -1509,11 +1512,12 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII(JNIEnv* env, jclass, try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get( *reinterpret_cast(jropt_handle), db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { return nullptr; @@ -1538,10 +1542,11 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ( try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(*reinterpret_cast( jropt_handle), cf_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { return nullptr; @@ -1563,10 +1568,11 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII(JNIEnv* env, jclass, ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { @@ -1595,9 +1601,10 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ(JNIEnv* env, jclass, ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { @@ -1621,11 +1628,12 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII(JNIEnv* env, jclass, ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get( *reinterpret_cast(jropt_handle), db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { @@ -1652,10 +1660,11 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ( ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, db->Get(*reinterpret_cast( jropt_handle), cf_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { diff --git a/java/rocksjni/transaction.cc b/java/rocksjni/transaction.cc index e211ebe5d6d..c97764b5005 100644 --- a/java/rocksjni/transaction.cc +++ b/java/rocksjni/transaction.cc @@ -177,9 +177,10 @@ jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BIIJ( try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->Get(*read_options, column_family_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { return nullptr; @@ -200,8 +201,9 @@ jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BII( try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->Get(*read_options, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { return nullptr; @@ -227,9 +229,10 @@ jint Java_org_rocksdb_Transaction_get__JJ_3BII_3BIIJ( ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_part_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->Get(*read_options, column_family_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); @@ -259,9 +262,10 @@ jint Java_org_rocksdb_Transaction_getDirect(JNIEnv* env, jclass, jlong jhandle, jkey_part_len); ROCKSDB_NAMESPACE::JDirectBufferPinnableSlice value(env, jval_bb, jval_off, jval_part_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->Get(*read_options, column_family_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (const ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); @@ -359,10 +363,11 @@ jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIIJZZ( try { ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->GetForUpdate(*read_options, column_family_handle, key.slice(), &value.pinnable_slice(), jexclusive, jdo_validate)); + if (status.IsNotFound()) return nullptr; return value.NewByteArray(); } catch (ROCKSDB_NAMESPACE::KVException&) { return nullptr; @@ -389,10 +394,11 @@ jint Java_org_rocksdb_Transaction_getForUpdate__JJ_3BII_3BIIJZZ( ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len); ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->GetForUpdate(*read_options, column_family_handle, key.slice(), &value.pinnable_slice(), jexclusive, jdo_validate)); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); @@ -420,9 +426,10 @@ jint Java_org_rocksdb_Transaction_getDirect( jkey_part_len); ROCKSDB_NAMESPACE::JDirectBufferPinnableSlice value(env, jval_bb, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->Get(*read_options, column_family_handle, key.slice(), &value.pinnable_slice())); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (const ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); @@ -451,10 +458,11 @@ jint Java_org_rocksdb_Transaction_getDirectForUpdate( jkey_part_len); ROCKSDB_NAMESPACE::JDirectBufferPinnableSlice value(env, jval_bb, jval_off, jval_len); - ROCKSDB_NAMESPACE::KVException::ThrowOnError( + auto status = ROCKSDB_NAMESPACE::KVException::ThrowOnError( env, txn->GetForUpdate(*read_options, column_family_handle, key.slice(), &value.pinnable_slice(), jexclusive, jdo_validate)); + if (status.IsNotFound()) return ROCKSDB_NAMESPACE::KVException::kNotFound; return value.Fetch(); } catch (const ROCKSDB_NAMESPACE::KVException& e) { return e.Code(); diff --git a/java/src/test/java/org/rocksdb/MergeTest.java b/java/src/test/java/org/rocksdb/MergeTest.java index 10ffeb7788b..d553dd1a88a 100644 --- a/java/src/test/java/org/rocksdb/MergeTest.java +++ b/java/src/test/java/org/rocksdb/MergeTest.java @@ -111,6 +111,13 @@ public void cFStringOption() final byte[] value = db.get(columnFamilyHandleList.get(1), "cfkey".getBytes()); final String strValue = new String(value); assertThat(strValue).isEqualTo("aa,bb"); + + // check merge of bb under key2, with no preceding value + db.merge(columnFamilyHandleList.get(1), "cfkey2".getBytes(), "bb2".getBytes()); + + final byte[] value2 = db.get(columnFamilyHandleList.get(1), "cfkey2".getBytes()); + final String strValue2 = new String(value2); + assertThat(strValue2).isEqualTo("bb2"); } finally { for (final ColumnFamilyHandle handle : columnFamilyHandleList) { handle.close(); diff --git a/java/src/test/java/org/rocksdb/RocksDBTest.java b/java/src/test/java/org/rocksdb/RocksDBTest.java index 42bb4ab3cbe..bc4e16efa80 100644 --- a/java/src/test/java/org/rocksdb/RocksDBTest.java +++ b/java/src/test/java/org/rocksdb/RocksDBTest.java @@ -625,6 +625,57 @@ public void deleteRange() throws RocksDBException { } } + @Test + public void notFound() throws RocksDBException { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { + assertThat(db.get("key1".getBytes())).isNull(); + assertThat(db.get("key2".getBytes())).isNull(); + + db.put("key1".getBytes(), "value".getBytes()); + db.put("key2".getBytes(), "12345678".getBytes()); + + assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); + assertThat(db.get("key2".getBytes())).isEqualTo("12345678".getBytes()); + + db.delete("key1".getBytes()); + db.delete("key2".getBytes()); + + assertThat(db.get("key1".getBytes())).isNull(); + assertThat(db.get("key2".getBytes())).isNull(); + } + } + + @Test + public void notFoundBB() throws RocksDBException { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final ReadOptions readOptions = new ReadOptions()) { + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(256); + final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(256); + + keyBuffer.clear(); + keyBuffer.put("key1".getBytes()).flip(); + + valueBuffer.clear(); + assertThat(db.get(readOptions, keyBuffer, valueBuffer)).isEqualTo(RocksDB.NOT_FOUND); + + db.put("key1".getBytes(), "value".getBytes()); + + keyBuffer.clear(); + keyBuffer.put("key1".getBytes()).flip(); + valueBuffer.clear(); + assertThat(db.get(readOptions, keyBuffer, valueBuffer)).isEqualTo("value".getBytes().length); + + db.delete("key1".getBytes()); + assertThat(db.get(readOptions, keyBuffer, valueBuffer)).isEqualTo(RocksDB.NOT_FOUND); + + db.put("key1".getBytes(), "".getBytes()); + keyBuffer.clear(); + keyBuffer.put("key1".getBytes()).flip(); + valueBuffer.clear(); + assertThat(db.get(readOptions, keyBuffer, valueBuffer)).isEqualTo(0); + } + } + @Test public void clipColumnFamily() throws RocksDBException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath())) {