Improve RocksJava Comparator (#6252)
Summary: This is a redesign of the API for RocksJava comparators with the aim of improving performance. It also simplifies the class hierarchy. **NOTE**: This breaks backwards compatibility for existing 3rd party Comparators implemented in Java... so we need to consider carefully which release branches this goes into. Previously when implementing a comparator in Java the developer had a choice of subclassing either `DirectComparator` or `Comparator` which would use direct and non-direct byte-buffers resepectively (via `DirectSlice` and `Slice`). In this redesign there we have eliminated the overhead of using the Java Slice classes, and just use `ByteBuffer`s. The `ComparatorOptions` supplied when constructing a Comparator allow you to choose between direct and non-direct byte buffers by setting `useDirect`. In addition, the `ComparatorOptions` now allow you to choose whether a ByteBuffer is reused over multiple comparator calls, by setting `maxReusedBufferSize > 0`. When buffers are reused, ComparatorOptions provides a choice of mutex type by setting `useAdaptiveMutex`. --- [JMH benchmarks previously indicated](https://github.com/facebook/rocksdb/pull/6241#issue-356398306) that the difference between C++ and Java for implementing a comparator was ~7x slowdown in Java. With these changes, when reusing buffers and guarding access to them via mutexes the slowdown is approximately the same. However, these changes offer a new facility to not reuse mutextes, which reduces the slowdown to ~5.5x in Java. We also offer a `thread_local` mechanism for reusing buffers, which reduces slowdown to ~5.2x in Java (closes https://github.com/facebook/rocksdb/pull/4425). These changes also form a good base for further optimisation work such as further JNI lookup caching, and JNI critical. --- These numbers were captured without jemalloc. With jemalloc, the performance improves for all tests, and the Java slowdown reduces to between 4.8x and 5.x. ``` ComparatorBenchmarks.put native_bytewise thrpt 25 124483.795 ± 2032.443 ops/s ComparatorBenchmarks.put native_reverse_bytewise thrpt 25 114414.536 ± 3486.156 ops/s ComparatorBenchmarks.put java_bytewise_non-direct_reused-64_adaptive-mutex thrpt 25 17228.250 ± 1288.546 ops/s ComparatorBenchmarks.put java_bytewise_non-direct_reused-64_non-adaptive-mutex thrpt 25 16035.865 ± 1248.099 ops/s ComparatorBenchmarks.put java_bytewise_non-direct_reused-64_thread-local thrpt 25 21571.500 ± 871.521 ops/s ComparatorBenchmarks.put java_bytewise_direct_reused-64_adaptive-mutex thrpt 25 23613.773 ± 8465.660 ops/s ComparatorBenchmarks.put java_bytewise_direct_reused-64_non-adaptive-mutex thrpt 25 16768.172 ± 5618.489 ops/s ComparatorBenchmarks.put java_bytewise_direct_reused-64_thread-local thrpt 25 23921.164 ± 8734.742 ops/s ComparatorBenchmarks.put java_bytewise_non-direct_no-reuse thrpt 25 17899.684 ± 839.679 ops/s ComparatorBenchmarks.put java_bytewise_direct_no-reuse thrpt 25 22148.316 ± 1215.527 ops/s ComparatorBenchmarks.put java_reverse_bytewise_non-direct_reused-64_adaptive-mutex thrpt 25 11311.126 ± 820.602 ops/s ComparatorBenchmarks.put java_reverse_bytewise_non-direct_reused-64_non-adaptive-mutex thrpt 25 11421.311 ± 807.210 ops/s ComparatorBenchmarks.put java_reverse_bytewise_non-direct_reused-64_thread-local thrpt 25 11554.005 ± 960.556 ops/s ComparatorBenchmarks.put java_reverse_bytewise_direct_reused-64_adaptive-mutex thrpt 25 22960.523 ± 1673.421 ops/s ComparatorBenchmarks.put java_reverse_bytewise_direct_reused-64_non-adaptive-mutex thrpt 25 18293.317 ± 1434.601 ops/s ComparatorBenchmarks.put java_reverse_bytewise_direct_reused-64_thread-local thrpt 25 24479.361 ± 2157.306 ops/s ComparatorBenchmarks.put java_reverse_bytewise_non-direct_no-reuse thrpt 25 7942.286 ± 626.170 ops/s ComparatorBenchmarks.put java_reverse_bytewise_direct_no-reuse thrpt 25 11781.955 ± 1019.843 ops/s ``` Pull Request resolved: https://github.com/facebook/rocksdb/pull/6252 Differential Revision: D19331064 Pulled By: pdillinger fbshipit-source-id: 1f3b794e6a14162b2c3ffb943e8c0e64a0c03738main
parent
800d24ddc5
commit
7242dae7fe
@ -0,0 +1,125 @@ |
|||||||
|
// 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 java.nio.ByteBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class is intentionally private, |
||||||
|
* it holds methods which are called |
||||||
|
* from C++ to interact with a Comparator |
||||||
|
* written in Java. |
||||||
|
* |
||||||
|
* Placing these bridge methods in this |
||||||
|
* class keeps the API of the |
||||||
|
* {@link org.rocksdb.AbstractComparator} clean. |
||||||
|
*/ |
||||||
|
class AbstractComparatorJniBridge { |
||||||
|
|
||||||
|
/** |
||||||
|
* Only called from JNI. |
||||||
|
* |
||||||
|
* Simply a bridge to calling |
||||||
|
* {@link AbstractComparator#compare(ByteBuffer, ByteBuffer)}, |
||||||
|
* which ensures that the byte buffer lengths are correct |
||||||
|
* before and after the call. |
||||||
|
* |
||||||
|
* @param comparator the comparator object on which to |
||||||
|
* call {@link AbstractComparator#compare(ByteBuffer, ByteBuffer)} |
||||||
|
* @param a buffer access to first key |
||||||
|
* @param aLen the length of the a key, |
||||||
|
* may be smaller than the buffer {@code a} |
||||||
|
* @param b buffer access to second key |
||||||
|
* @param bLen the length of the b key, |
||||||
|
* may be smaller than the buffer {@code b} |
||||||
|
* |
||||||
|
* @return the result of the comparison |
||||||
|
*/ |
||||||
|
private static int compareInternal( |
||||||
|
final AbstractComparator comparator, |
||||||
|
final ByteBuffer a, final int aLen, |
||||||
|
final ByteBuffer b, final int bLen) { |
||||||
|
if (aLen != -1) { |
||||||
|
a.mark(); |
||||||
|
a.limit(aLen); |
||||||
|
} |
||||||
|
if (bLen != -1) { |
||||||
|
b.mark(); |
||||||
|
b.limit(bLen); |
||||||
|
} |
||||||
|
|
||||||
|
final int c = comparator.compare(a, b); |
||||||
|
|
||||||
|
if (aLen != -1) { |
||||||
|
a.reset(); |
||||||
|
} |
||||||
|
if (bLen != -1) { |
||||||
|
b.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Only called from JNI. |
||||||
|
* |
||||||
|
* Simply a bridge to calling |
||||||
|
* {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)}, |
||||||
|
* which ensures that the byte buffer lengths are correct |
||||||
|
* before the call. |
||||||
|
* |
||||||
|
* @param comparator the comparator object on which to |
||||||
|
* call {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)} |
||||||
|
* @param start buffer access to the start key |
||||||
|
* @param startLen the length of the start key, |
||||||
|
* may be smaller than the buffer {@code start} |
||||||
|
* @param limit buffer access to the limit key |
||||||
|
* @param limitLen the length of the limit key, |
||||||
|
* may be smaller than the buffer {@code limit} |
||||||
|
* |
||||||
|
* @return either {@code startLen} if the start key is unchanged, otherwise |
||||||
|
* the new length of the start key |
||||||
|
*/ |
||||||
|
private static int findShortestSeparatorInternal( |
||||||
|
final AbstractComparator comparator, |
||||||
|
final ByteBuffer start, final int startLen, |
||||||
|
final ByteBuffer limit, final int limitLen) { |
||||||
|
if (startLen != -1) { |
||||||
|
start.limit(startLen); |
||||||
|
} |
||||||
|
if (limitLen != -1) { |
||||||
|
limit.limit(limitLen); |
||||||
|
} |
||||||
|
comparator.findShortestSeparator(start, limit); |
||||||
|
return start.remaining(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Only called from JNI. |
||||||
|
* |
||||||
|
* Simply a bridge to calling |
||||||
|
* {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)}, |
||||||
|
* which ensures that the byte buffer length is correct |
||||||
|
* before the call. |
||||||
|
* |
||||||
|
* @param comparator the comparator object on which to |
||||||
|
* call {@link AbstractComparator#findShortSuccessor(ByteBuffer)} |
||||||
|
* @param key buffer access to the key |
||||||
|
* @param keyLen the length of the key, |
||||||
|
* may be smaller than the buffer {@code key} |
||||||
|
* |
||||||
|
* @return either keyLen if the key is unchanged, otherwise the new length of the key |
||||||
|
*/ |
||||||
|
private static int findShortSuccessorInternal( |
||||||
|
final AbstractComparator comparator, |
||||||
|
final ByteBuffer key, final int keyLen) { |
||||||
|
if (keyLen != -1) { |
||||||
|
key.limit(keyLen); |
||||||
|
} |
||||||
|
comparator.findShortSuccessor(key); |
||||||
|
return key.remaining(); |
||||||
|
} |
||||||
|
} |
@ -1,34 +0,0 @@ |
|||||||
// 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; |
|
||||||
|
|
||||||
/** |
|
||||||
* Base class for comparators which will receive |
|
||||||
* byte[] based access via org.rocksdb.Slice in their |
|
||||||
* compare method implementation. |
|
||||||
* |
|
||||||
* byte[] based slices perform better when small keys |
|
||||||
* are involved. When using larger keys consider |
|
||||||
* using @see org.rocksdb.DirectComparator |
|
||||||
*/ |
|
||||||
public abstract class Comparator extends AbstractComparator<Slice> { |
|
||||||
|
|
||||||
public Comparator(final ComparatorOptions copt) { |
|
||||||
super(copt); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected long initializeNative(final long... nativeParameterHandles) { |
|
||||||
return createNewComparator0(nativeParameterHandles[0]); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
final ComparatorType getComparatorType() { |
|
||||||
return ComparatorType.JAVA_COMPARATOR; |
|
||||||
} |
|
||||||
|
|
||||||
private native long createNewComparator0(final long comparatorOptionsHandle); |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
// 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; |
|
||||||
|
|
||||||
/** |
|
||||||
* Base class for comparators which will receive |
|
||||||
* ByteBuffer based access via org.rocksdb.DirectSlice |
|
||||||
* in their compare method implementation. |
|
||||||
* |
|
||||||
* ByteBuffer based slices perform better when large keys |
|
||||||
* are involved. When using smaller keys consider |
|
||||||
* using @see org.rocksdb.Comparator |
|
||||||
*/ |
|
||||||
public abstract class DirectComparator extends AbstractComparator<DirectSlice> { |
|
||||||
|
|
||||||
public DirectComparator(final ComparatorOptions copt) { |
|
||||||
super(copt); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected long initializeNative(final long... nativeParameterHandles) { |
|
||||||
return createNewDirectComparator0(nativeParameterHandles[0]); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
final ComparatorType getComparatorType() { |
|
||||||
return ComparatorType.JAVA_DIRECT_COMPARATOR; |
|
||||||
} |
|
||||||
|
|
||||||
private native long createNewDirectComparator0( |
|
||||||
final long comparatorOptionsHandle); |
|
||||||
} |
|
@ -0,0 +1,65 @@ |
|||||||
|
// 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; |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines the type of synchronisation primitive used |
||||||
|
* in native code. |
||||||
|
*/ |
||||||
|
public enum ReusedSynchronisationType { |
||||||
|
/** |
||||||
|
* Standard mutex. |
||||||
|
*/ |
||||||
|
MUTEX((byte)0x0), |
||||||
|
|
||||||
|
/** |
||||||
|
* Use adaptive mutex, which spins in the user space before resorting |
||||||
|
* to kernel. This could reduce context switch when the mutex is not |
||||||
|
* heavily contended. However, if the mutex is hot, we could end up |
||||||
|
* wasting spin time. |
||||||
|
*/ |
||||||
|
ADAPTIVE_MUTEX((byte)0x1), |
||||||
|
|
||||||
|
/** |
||||||
|
* There is a reused buffer per-thread. |
||||||
|
*/ |
||||||
|
THREAD_LOCAL((byte)0x2); |
||||||
|
|
||||||
|
private final byte value; |
||||||
|
|
||||||
|
ReusedSynchronisationType(final byte value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the byte value of the enumerations value |
||||||
|
* |
||||||
|
* @return byte representation |
||||||
|
*/ |
||||||
|
public byte getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get ReusedSynchronisationType by byte value. |
||||||
|
* |
||||||
|
* @param value byte representation of ReusedSynchronisationType. |
||||||
|
* |
||||||
|
* @return {@link org.rocksdb.ReusedSynchronisationType} instance. |
||||||
|
* @throws java.lang.IllegalArgumentException if an invalid |
||||||
|
* value is provided. |
||||||
|
*/ |
||||||
|
public static ReusedSynchronisationType getReusedSynchronisationType( |
||||||
|
final byte value) { |
||||||
|
for (final ReusedSynchronisationType reusedSynchronisationType |
||||||
|
: ReusedSynchronisationType.values()) { |
||||||
|
if (reusedSynchronisationType.getValue() == value) { |
||||||
|
return reusedSynchronisationType; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Illegal value provided for ReusedSynchronisationType."); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package org.rocksdb.util; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
|
||||||
|
public class ByteUtil { |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert a String to a UTF-8 byte array. |
||||||
|
* |
||||||
|
* @param str the string |
||||||
|
* |
||||||
|
* @return the byte array. |
||||||
|
*/ |
||||||
|
public static byte[] bytes(final String str) { |
||||||
|
return str.getBytes(UTF_8); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compares the first {@code count} bytes of two areas of memory. Returns |
||||||
|
* zero if they are the same, a value less than zero if {@code x} is |
||||||
|
* lexically less than {@code y}, or a value greater than zero if {@code x} |
||||||
|
* is lexically greater than {@code y}. Note that lexical order is determined |
||||||
|
* as if comparing unsigned char arrays. |
||||||
|
* |
||||||
|
* Similar to <a href="https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c">memcmp.c</a>. |
||||||
|
* |
||||||
|
* @param x the first value to compare with |
||||||
|
* @param y the second value to compare against |
||||||
|
* @param count the number of bytes to compare |
||||||
|
* |
||||||
|
* @return the result of the comparison |
||||||
|
*/ |
||||||
|
public static int memcmp(final ByteBuffer x, final ByteBuffer y, |
||||||
|
final int count) { |
||||||
|
for (int idx = 0; idx < count; idx++) { |
||||||
|
final int aa = x.get(idx) & 0xff; |
||||||
|
final int bb = y.get(idx) & 0xff; |
||||||
|
if (aa != bb) { |
||||||
|
return aa - bb; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
@ -1,88 +0,0 @@ |
|||||||
// 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.util; |
|
||||||
|
|
||||||
import org.rocksdb.ComparatorOptions; |
|
||||||
import org.rocksdb.DirectComparator; |
|
||||||
import org.rocksdb.DirectSlice; |
|
||||||
|
|
||||||
import java.nio.ByteBuffer; |
|
||||||
|
|
||||||
/** |
|
||||||
* This is a Java Native implementation of the C++ |
|
||||||
* equivalent BytewiseComparatorImpl using {@link DirectSlice} |
|
||||||
* |
|
||||||
* The performance of Comparators implemented in Java is always |
|
||||||
* less than their C++ counterparts due to the bridging overhead, |
|
||||||
* as such you likely don't want to use this apart from benchmarking |
|
||||||
* and you most likely instead wanted |
|
||||||
* {@link org.rocksdb.BuiltinComparator#BYTEWISE_COMPARATOR} |
|
||||||
*/ |
|
||||||
public class DirectBytewiseComparator extends DirectComparator { |
|
||||||
|
|
||||||
public DirectBytewiseComparator(final ComparatorOptions copt) { |
|
||||||
super(copt); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String name() { |
|
||||||
return "rocksdb.java.DirectBytewiseComparator"; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int compare(final DirectSlice a, final DirectSlice b) { |
|
||||||
return a.data().compareTo(b.data()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String findShortestSeparator(final String start, |
|
||||||
final DirectSlice limit) { |
|
||||||
final byte[] startBytes = start.getBytes(); |
|
||||||
|
|
||||||
// Find length of common prefix
|
|
||||||
final int min_length = Math.min(startBytes.length, limit.size()); |
|
||||||
int diff_index = 0; |
|
||||||
while ((diff_index < min_length) && |
|
||||||
(startBytes[diff_index] == limit.get(diff_index))) { |
|
||||||
diff_index++; |
|
||||||
} |
|
||||||
|
|
||||||
if (diff_index >= min_length) { |
|
||||||
// Do not shorten if one string is a prefix of the other
|
|
||||||
} else { |
|
||||||
final byte diff_byte = startBytes[diff_index]; |
|
||||||
if(diff_byte < 0xff && diff_byte + 1 < limit.get(diff_index)) { |
|
||||||
final byte shortest[] = new byte[diff_index + 1]; |
|
||||||
System.arraycopy(startBytes, 0, shortest, 0, diff_index + 1); |
|
||||||
shortest[diff_index]++; |
|
||||||
assert(ByteBuffer.wrap(shortest).compareTo(limit.data()) < 0); |
|
||||||
return new String(shortest); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String findShortSuccessor(final String key) { |
|
||||||
final byte[] keyBytes = key.getBytes(); |
|
||||||
|
|
||||||
// Find first character that can be incremented
|
|
||||||
final int n = keyBytes.length; |
|
||||||
for (int i = 0; i < n; i++) { |
|
||||||
final byte byt = keyBytes[i]; |
|
||||||
if (byt != 0xff) { |
|
||||||
final byte shortSuccessor[] = new byte[i + 1]; |
|
||||||
System.arraycopy(keyBytes, 0, shortSuccessor, 0, i + 1); |
|
||||||
shortSuccessor[i]++; |
|
||||||
return new String(shortSuccessor); |
|
||||||
} |
|
||||||
} |
|
||||||
// *key is a run of 0xffs. Leave it alone.
|
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,67 @@ |
|||||||
|
// 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.util; |
||||||
|
|
||||||
|
import org.rocksdb.AbstractComparator; |
||||||
|
import org.rocksdb.ComparatorOptions; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This is a Java implementation of a Comparator for Java int |
||||||
|
* keys. |
||||||
|
* |
||||||
|
* This comparator assumes keys are (at least) four bytes, so |
||||||
|
* the caller must guarantee that in accessing other APIs in |
||||||
|
* combination with this comparator. |
||||||
|
* |
||||||
|
* The performance of Comparators implemented in Java is always |
||||||
|
* less than their C++ counterparts due to the bridging overhead, |
||||||
|
* as such you likely don't want to use this apart from benchmarking |
||||||
|
* or testing. |
||||||
|
*/ |
||||||
|
public final class IntComparator extends AbstractComparator { |
||||||
|
|
||||||
|
public IntComparator(final ComparatorOptions copt) { |
||||||
|
super(copt); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "rocksdb.java.IntComparator"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compare(final ByteBuffer a, final ByteBuffer b) { |
||||||
|
return compareIntKeys(a, b); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compares integer keys |
||||||
|
* so that they are in ascending order |
||||||
|
* |
||||||
|
* @param a 4-bytes representing an integer key |
||||||
|
* @param b 4-bytes representing an integer key |
||||||
|
* |
||||||
|
* @return negative if a < b, 0 if a == b, positive otherwise |
||||||
|
*/ |
||||||
|
private final int compareIntKeys(final ByteBuffer a, final ByteBuffer b) { |
||||||
|
final int iA = a.getInt(); |
||||||
|
final int iB = b.getInt(); |
||||||
|
|
||||||
|
// protect against int key calculation overflow
|
||||||
|
final long diff = (long)iA - iB; |
||||||
|
final int result; |
||||||
|
if (diff < Integer.MIN_VALUE) { |
||||||
|
result = Integer.MIN_VALUE; |
||||||
|
} else if(diff > Integer.MAX_VALUE) { |
||||||
|
result = Integer.MAX_VALUE; |
||||||
|
} else { |
||||||
|
result = (int)diff; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -1,199 +0,0 @@ |
|||||||
// 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 java.io.IOException; |
|
||||||
import java.nio.file.*; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Random; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
import static org.rocksdb.Types.byteToInt; |
|
||||||
import static org.rocksdb.Types.intToByte; |
|
||||||
|
|
||||||
/** |
|
||||||
* Abstract tests for both Comparator and DirectComparator |
|
||||||
*/ |
|
||||||
public abstract class AbstractComparatorTest<T extends AbstractSlice<?>> { |
|
||||||
|
|
||||||
/** |
|
||||||
* Get a comparator which will expect Integer keys |
|
||||||
* and determine an ascending order |
|
||||||
* |
|
||||||
* @return An integer ascending order key comparator |
|
||||||
*/ |
|
||||||
public abstract AbstractComparator<T> getAscendingIntKeyComparator(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Test which stores random keys into the database |
|
||||||
* using an @see getAscendingIntKeyComparator |
|
||||||
* it then checks that these keys are read back in |
|
||||||
* ascending order |
|
||||||
* |
|
||||||
* @param db_path A path where we can store database |
|
||||||
* files temporarily |
|
||||||
* |
|
||||||
* @throws java.io.IOException if IO error happens. |
|
||||||
*/ |
|
||||||
public void testRoundtrip(final Path db_path) throws IOException, |
|
||||||
RocksDBException { |
|
||||||
try (final AbstractComparator<T> comparator = getAscendingIntKeyComparator(); |
|
||||||
final Options opt = new Options() |
|
||||||
.setCreateIfMissing(true) |
|
||||||
.setComparator(comparator)) { |
|
||||||
|
|
||||||
// store 10,000 random integer keys
|
|
||||||
final int ITERATIONS = 10000; |
|
||||||
try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { |
|
||||||
final Random random = new Random(); |
|
||||||
for (int i = 0; i < ITERATIONS; i++) { |
|
||||||
final byte[] key = intToByte(random.nextInt()); |
|
||||||
// does key already exist (avoid duplicates)
|
|
||||||
if (i > 0 && db.get(key) != null) { |
|
||||||
i--; // generate a different key
|
|
||||||
} else { |
|
||||||
db.put(key, "value".getBytes()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// re-open db and read from start to end
|
|
||||||
// integer keys should be in ascending
|
|
||||||
// order as defined by SimpleIntComparator
|
|
||||||
try (final RocksDB db = RocksDB.open(opt, db_path.toString()); |
|
||||||
final RocksIterator it = db.newIterator()) { |
|
||||||
it.seekToFirst(); |
|
||||||
int lastKey = Integer.MIN_VALUE; |
|
||||||
int count = 0; |
|
||||||
for (it.seekToFirst(); it.isValid(); it.next()) { |
|
||||||
final int thisKey = byteToInt(it.key()); |
|
||||||
assertThat(thisKey).isGreaterThan(lastKey); |
|
||||||
lastKey = thisKey; |
|
||||||
count++; |
|
||||||
} |
|
||||||
assertThat(count).isEqualTo(ITERATIONS); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Test which stores random keys into a column family |
|
||||||
* in the database |
|
||||||
* using an @see getAscendingIntKeyComparator |
|
||||||
* it then checks that these keys are read back in |
|
||||||
* ascending order |
|
||||||
* |
|
||||||
* @param db_path A path where we can store database |
|
||||||
* files temporarily |
|
||||||
* |
|
||||||
* @throws java.io.IOException if IO error happens. |
|
||||||
*/ |
|
||||||
public void testRoundtripCf(final Path db_path) throws IOException, |
|
||||||
RocksDBException { |
|
||||||
|
|
||||||
try(final AbstractComparator<T> comparator = getAscendingIntKeyComparator()) { |
|
||||||
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList( |
|
||||||
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), |
|
||||||
new ColumnFamilyDescriptor("new_cf".getBytes(), |
|
||||||
new ColumnFamilyOptions().setComparator(comparator)) |
|
||||||
); |
|
||||||
|
|
||||||
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>(); |
|
||||||
|
|
||||||
try (final DBOptions opt = new DBOptions(). |
|
||||||
setCreateIfMissing(true). |
|
||||||
setCreateMissingColumnFamilies(true)) { |
|
||||||
|
|
||||||
// store 10,000 random integer keys
|
|
||||||
final int ITERATIONS = 10000; |
|
||||||
|
|
||||||
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
|
||||||
cfDescriptors, cfHandles)) { |
|
||||||
try { |
|
||||||
assertThat(cfDescriptors.size()).isEqualTo(2); |
|
||||||
assertThat(cfHandles.size()).isEqualTo(2); |
|
||||||
|
|
||||||
final Random random = new Random(); |
|
||||||
for (int i = 0; i < ITERATIONS; i++) { |
|
||||||
final byte key[] = intToByte(random.nextInt()); |
|
||||||
if (i > 0 && db.get(cfHandles.get(1), key) != null) { |
|
||||||
// does key already exist (avoid duplicates)
|
|
||||||
i--; // generate a different key
|
|
||||||
} else { |
|
||||||
db.put(cfHandles.get(1), key, "value".getBytes()); |
|
||||||
} |
|
||||||
} |
|
||||||
} finally { |
|
||||||
for (final ColumnFamilyHandle handle : cfHandles) { |
|
||||||
handle.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
cfHandles.clear(); |
|
||||||
} |
|
||||||
|
|
||||||
// re-open db and read from start to end
|
|
||||||
// integer keys should be in ascending
|
|
||||||
// order as defined by SimpleIntComparator
|
|
||||||
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
|
||||||
cfDescriptors, cfHandles); |
|
||||||
final RocksIterator it = db.newIterator(cfHandles.get(1))) { |
|
||||||
try { |
|
||||||
assertThat(cfDescriptors.size()).isEqualTo(2); |
|
||||||
assertThat(cfHandles.size()).isEqualTo(2); |
|
||||||
|
|
||||||
it.seekToFirst(); |
|
||||||
int lastKey = Integer.MIN_VALUE; |
|
||||||
int count = 0; |
|
||||||
for (it.seekToFirst(); it.isValid(); it.next()) { |
|
||||||
final int thisKey = byteToInt(it.key()); |
|
||||||
assertThat(thisKey).isGreaterThan(lastKey); |
|
||||||
lastKey = thisKey; |
|
||||||
count++; |
|
||||||
} |
|
||||||
|
|
||||||
assertThat(count).isEqualTo(ITERATIONS); |
|
||||||
|
|
||||||
} finally { |
|
||||||
for (final ColumnFamilyHandle handle : cfHandles) { |
|
||||||
handle.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
cfHandles.clear(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Compares integer keys |
|
||||||
* so that they are in ascending order |
|
||||||
* |
|
||||||
* @param a 4-bytes representing an integer key |
|
||||||
* @param b 4-bytes representing an integer key |
|
||||||
* |
|
||||||
* @return negative if a < b, 0 if a == b, positive otherwise |
|
||||||
*/ |
|
||||||
protected final int compareIntKeys(final byte[] a, final byte[] b) { |
|
||||||
|
|
||||||
final int iA = byteToInt(a); |
|
||||||
final int iB = byteToInt(b); |
|
||||||
|
|
||||||
// protect against int key calculation overflow
|
|
||||||
final double diff = (double)iA - iB; |
|
||||||
final int result; |
|
||||||
if (diff < Integer.MIN_VALUE) { |
|
||||||
result = Integer.MIN_VALUE; |
|
||||||
} else if(diff > Integer.MAX_VALUE) { |
|
||||||
result = Integer.MAX_VALUE; |
|
||||||
} else { |
|
||||||
result = (int)diff; |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
// 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 org.junit.ClassRule; |
|
||||||
import org.junit.Rule; |
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.rules.TemporaryFolder; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.nio.file.FileSystems; |
|
||||||
|
|
||||||
public class DirectComparatorTest { |
|
||||||
@ClassRule |
|
||||||
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = |
|
||||||
new RocksNativeLibraryResource(); |
|
||||||
|
|
||||||
@Rule |
|
||||||
public TemporaryFolder dbFolder = new TemporaryFolder(); |
|
||||||
|
|
||||||
@Test |
|
||||||
public void directComparator() throws IOException, RocksDBException { |
|
||||||
|
|
||||||
final AbstractComparatorTest<DirectSlice> comparatorTest = new AbstractComparatorTest<DirectSlice>() { |
|
||||||
@Override |
|
||||||
public AbstractComparator<DirectSlice> getAscendingIntKeyComparator() { |
|
||||||
return new DirectComparator(new ComparatorOptions()) { |
|
||||||
|
|
||||||
@Override |
|
||||||
public String name() { |
|
||||||
return "test.AscendingIntKeyDirectComparator"; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int compare(final DirectSlice a, final DirectSlice b) { |
|
||||||
final byte ax[] = new byte[4], bx[] = new byte[4]; |
|
||||||
a.data().get(ax); |
|
||||||
b.data().get(bx); |
|
||||||
return compareIntKeys(ax, bx); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// test the round-tripability of keys written and read with the DirectComparator
|
|
||||||
comparatorTest.testRoundtrip(FileSystems.getDefault().getPath( |
|
||||||
dbFolder.getRoot().getAbsolutePath())); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,267 @@ |
|||||||
|
// 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.util; |
||||||
|
|
||||||
|
import org.junit.BeforeClass; |
||||||
|
import org.junit.ClassRule; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
import org.rocksdb.*; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.file.FileSystems; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Random; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Similar to {@link IntComparatorTest}, but uses {@link BytewiseComparator} |
||||||
|
* which ensures the correct ordering of positive integers. |
||||||
|
*/ |
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class BytewiseComparatorIntTest { |
||||||
|
|
||||||
|
// test with 500 random positive integer keys
|
||||||
|
private static final int TOTAL_KEYS = 500; |
||||||
|
private static final byte[][] keys = new byte[TOTAL_KEYS][4]; |
||||||
|
|
||||||
|
@BeforeClass |
||||||
|
public static void prepareKeys() { |
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
final Random random = new Random(); |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
final int ri = random.nextInt() & Integer.MAX_VALUE; // the & ensures positive integer
|
||||||
|
buf.putInt(ri); |
||||||
|
buf.flip(); |
||||||
|
final byte[] key = buf.array(); |
||||||
|
|
||||||
|
// does key already exist (avoid duplicates)
|
||||||
|
if (keyExists(key, i)) { |
||||||
|
i--; // loop round and generate a different key
|
||||||
|
} else { |
||||||
|
System.arraycopy(key, 0, keys[i], 0, 4); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean keyExists(final byte[] key, final int limit) { |
||||||
|
for (int j = 0; j < limit; j++) { |
||||||
|
if (Arrays.equals(key, keys[j])) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Parameters(name = "{0}") |
||||||
|
public static Iterable<Object[]> parameters() { |
||||||
|
return Arrays.asList(new Object[][] { |
||||||
|
{ "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "direct_reused64_mutex", true, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "non-direct_reused64_thread-local", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "direct_reused64_thread-local", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "non-direct_noreuse", false, -1, null }, |
||||||
|
{ "direct_noreuse", true, -1, null } |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Parameter(0) |
||||||
|
public String name; |
||||||
|
|
||||||
|
@Parameter(1) |
||||||
|
public boolean useDirectBuffer; |
||||||
|
|
||||||
|
@Parameter(2) |
||||||
|
public int maxReusedBufferSize; |
||||||
|
|
||||||
|
@Parameter(3) |
||||||
|
public ReusedSynchronisationType reusedSynchronisationType; |
||||||
|
|
||||||
|
@ClassRule |
||||||
|
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = |
||||||
|
new RocksNativeLibraryResource(); |
||||||
|
|
||||||
|
@Rule |
||||||
|
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorDefaultCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final BytewiseComparator comparator = new BytewiseComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtrip(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorNamedCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final BytewiseComparator comparator = new BytewiseComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtripCf(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtrip(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
try (final Options opt = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator)) { |
||||||
|
|
||||||
|
// store TOTAL_KEYS into the db
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in ascending
|
||||||
|
// order as defined by IntComparator
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString()); |
||||||
|
final RocksIterator it = db.newIterator()) { |
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MIN_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isGreaterThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into a column family |
||||||
|
* in the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtripCf(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
|
||||||
|
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList( |
||||||
|
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), |
||||||
|
new ColumnFamilyDescriptor("new_cf".getBytes(), |
||||||
|
new ColumnFamilyOptions() |
||||||
|
.setComparator(comparator)) |
||||||
|
); |
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>(); |
||||||
|
|
||||||
|
try (final DBOptions opt = new DBOptions() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setCreateMissingColumnFamilies(true)) { |
||||||
|
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles)) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in ascending
|
||||||
|
// order as defined by SimpleIntComparator
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles); |
||||||
|
final RocksIterator it = db.newIterator(cfHandles.get(1))) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MIN_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isGreaterThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
|
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
|
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { |
||||||
|
cfDescriptor.getOptions().close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,266 @@ |
|||||||
|
// 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.util; |
||||||
|
|
||||||
|
import org.junit.BeforeClass; |
||||||
|
import org.junit.ClassRule; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
import org.rocksdb.*; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.file.*; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Random; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for IntComparator, but more generally |
||||||
|
* also for rocksdb::ComparatorJniCallback implementation. |
||||||
|
*/ |
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class IntComparatorTest { |
||||||
|
|
||||||
|
// test with 500 random integer keys
|
||||||
|
private static final int TOTAL_KEYS = 500; |
||||||
|
private static final byte[][] keys = new byte[TOTAL_KEYS][4]; |
||||||
|
|
||||||
|
@BeforeClass |
||||||
|
public static void prepareKeys() { |
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
final Random random = new Random(); |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
final int ri = random.nextInt(); |
||||||
|
buf.putInt(ri); |
||||||
|
buf.flip(); |
||||||
|
final byte[] key = buf.array(); |
||||||
|
|
||||||
|
// does key already exist (avoid duplicates)
|
||||||
|
if (keyExists(key, i)) { |
||||||
|
i--; // loop round and generate a different key
|
||||||
|
} else { |
||||||
|
System.arraycopy(key, 0, keys[i], 0, 4); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean keyExists(final byte[] key, final int limit) { |
||||||
|
for (int j = 0; j < limit; j++) { |
||||||
|
if (Arrays.equals(key, keys[j])) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Parameters(name = "{0}") |
||||||
|
public static Iterable<Object[]> parameters() { |
||||||
|
return Arrays.asList(new Object[][] { |
||||||
|
{ "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "direct_reused64_mutex", true, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "non-direct_reused64_thread-local", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "direct_reused64_thread-local", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "non-direct_noreuse", false, -1, null }, |
||||||
|
{ "direct_noreuse", true, -1, null } |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Parameter(0) |
||||||
|
public String name; |
||||||
|
|
||||||
|
@Parameter(1) |
||||||
|
public boolean useDirectBuffer; |
||||||
|
|
||||||
|
@Parameter(2) |
||||||
|
public int maxReusedBufferSize; |
||||||
|
|
||||||
|
@Parameter(3) |
||||||
|
public ReusedSynchronisationType reusedSynchronisationType; |
||||||
|
|
||||||
|
@ClassRule |
||||||
|
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = |
||||||
|
new RocksNativeLibraryResource(); |
||||||
|
|
||||||
|
@Rule |
||||||
|
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorDefaultCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final IntComparator comparator = new IntComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtrip(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorNamedCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final IntComparator comparator = new IntComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtripCf(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtrip(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
try (final Options opt = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator)) { |
||||||
|
|
||||||
|
// store TOTAL_KEYS into the db
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in ascending
|
||||||
|
// order as defined by IntComparator
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString()); |
||||||
|
final RocksIterator it = db.newIterator()) { |
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MIN_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isGreaterThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into a column family |
||||||
|
* in the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtripCf(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
|
||||||
|
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList( |
||||||
|
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), |
||||||
|
new ColumnFamilyDescriptor("new_cf".getBytes(), |
||||||
|
new ColumnFamilyOptions() |
||||||
|
.setComparator(comparator)) |
||||||
|
); |
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>(); |
||||||
|
|
||||||
|
try (final DBOptions opt = new DBOptions() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setCreateMissingColumnFamilies(true)) { |
||||||
|
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles)) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in ascending
|
||||||
|
// order as defined by SimpleIntComparator
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles); |
||||||
|
final RocksIterator it = db.newIterator(cfHandles.get(1))) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MIN_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isGreaterThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
|
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
|
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { |
||||||
|
cfDescriptor.getOptions().close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,174 @@ |
|||||||
|
package org.rocksdb.util; |
||||||
|
|
||||||
|
import org.junit.ClassRule; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
import org.rocksdb.*; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.file.*; |
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class JNIComparatorTest { |
||||||
|
|
||||||
|
@Parameters(name = "{0}") |
||||||
|
public static Iterable<Object[]> parameters() { |
||||||
|
return Arrays.asList(new Object[][] { |
||||||
|
{ "bytewise_non-direct", BuiltinComparator.BYTEWISE_COMPARATOR, false }, |
||||||
|
{ "bytewise_direct", BuiltinComparator.BYTEWISE_COMPARATOR, true }, |
||||||
|
{ "reverse-bytewise_non-direct", BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR, false }, |
||||||
|
{ "reverse-bytewise_direct", BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR, true }, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Parameter(0) |
||||||
|
public String name; |
||||||
|
|
||||||
|
@Parameter(1) |
||||||
|
public BuiltinComparator builtinComparator; |
||||||
|
|
||||||
|
@Parameter(2) |
||||||
|
public boolean useDirectBuffer; |
||||||
|
|
||||||
|
@ClassRule |
||||||
|
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = |
||||||
|
new RocksNativeLibraryResource(); |
||||||
|
|
||||||
|
@Rule |
||||||
|
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
private static final int MIN = Short.MIN_VALUE - 1; |
||||||
|
private static final int MAX = Short.MAX_VALUE + 1; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void java_comparator_equals_cpp_comparator() throws RocksDBException, IOException { |
||||||
|
final int[] javaKeys; |
||||||
|
try (final ComparatorOptions comparatorOptions = new ComparatorOptions(); |
||||||
|
final AbstractComparator comparator = builtinComparator == BuiltinComparator.BYTEWISE_COMPARATOR |
||||||
|
? new BytewiseComparator(comparatorOptions) |
||||||
|
: new ReverseBytewiseComparator(comparatorOptions)) { |
||||||
|
final Path javaDbDir = |
||||||
|
FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); |
||||||
|
storeWithJavaComparator(javaDbDir, comparator); |
||||||
|
javaKeys = readAllWithJavaComparator(javaDbDir, comparator); |
||||||
|
} |
||||||
|
|
||||||
|
final Path cppDbDir = |
||||||
|
FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); |
||||||
|
storeWithCppComparator(cppDbDir, builtinComparator); |
||||||
|
final int[] cppKeys = |
||||||
|
readAllWithCppComparator(cppDbDir, builtinComparator); |
||||||
|
|
||||||
|
assertThat(javaKeys).isEqualTo(cppKeys); |
||||||
|
} |
||||||
|
|
||||||
|
private void storeWithJavaComparator(final Path dir, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
try (final Options options = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator); |
||||||
|
final RocksDB db = |
||||||
|
RocksDB.open(options, dir.toAbsolutePath().toString())) { |
||||||
|
for (int i = MIN; i < MAX; i++) { |
||||||
|
buf.putInt(i); |
||||||
|
buf.flip(); |
||||||
|
|
||||||
|
db.put(buf.array(), buf.array()); |
||||||
|
|
||||||
|
buf.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void storeWithCppComparator(final Path dir, |
||||||
|
final BuiltinComparator builtinComparator) throws RocksDBException { |
||||||
|
try (final Options options = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(builtinComparator); |
||||||
|
final RocksDB db = |
||||||
|
RocksDB.open(options, dir.toAbsolutePath().toString())) { |
||||||
|
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
for (int i = MIN; i < MAX; i++) { |
||||||
|
buf.putInt(i); |
||||||
|
buf.flip(); |
||||||
|
|
||||||
|
db.put(buf.array(), buf.array()); |
||||||
|
|
||||||
|
buf.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private int[] readAllWithJavaComparator(final Path dir, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
try (final Options options = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator); |
||||||
|
final RocksDB db = |
||||||
|
RocksDB.open(options, dir.toAbsolutePath().toString())) { |
||||||
|
|
||||||
|
try (final RocksIterator it = db.newIterator()) { |
||||||
|
it.seekToFirst(); |
||||||
|
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
final int[] keys = new int[MAX - MIN]; |
||||||
|
int idx = 0; |
||||||
|
while (it.isValid()) { |
||||||
|
buf.put(it.key()); |
||||||
|
buf.flip(); |
||||||
|
|
||||||
|
final int thisKey = buf.getInt(); |
||||||
|
keys[idx++] = thisKey; |
||||||
|
|
||||||
|
buf.clear(); |
||||||
|
|
||||||
|
it.next(); |
||||||
|
} |
||||||
|
|
||||||
|
return keys; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private int[] readAllWithCppComparator(final Path dir, |
||||||
|
final BuiltinComparator comparator) throws RocksDBException { |
||||||
|
try (final Options options = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator); |
||||||
|
final RocksDB db = |
||||||
|
RocksDB.open(options, dir.toAbsolutePath().toString())) { |
||||||
|
|
||||||
|
try (final RocksIterator it = db.newIterator()) { |
||||||
|
it.seekToFirst(); |
||||||
|
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
final int[] keys = new int[MAX - MIN]; |
||||||
|
int idx = 0; |
||||||
|
while (it.isValid()) { |
||||||
|
buf.put(it.key()); |
||||||
|
buf.flip(); |
||||||
|
|
||||||
|
final int thisKey = buf.getInt(); |
||||||
|
keys[idx++] = thisKey; |
||||||
|
|
||||||
|
buf.clear(); |
||||||
|
|
||||||
|
it.next(); |
||||||
|
} |
||||||
|
|
||||||
|
return keys; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,270 @@ |
|||||||
|
// 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.util; |
||||||
|
|
||||||
|
import org.junit.BeforeClass; |
||||||
|
import org.junit.ClassRule; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
import org.rocksdb.*; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.file.FileSystems; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Random; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Similar to {@link IntComparatorTest}, but uses |
||||||
|
* {@link ReverseBytewiseComparator} which ensures the correct reverse |
||||||
|
* ordering of positive integers. |
||||||
|
*/ |
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class ReverseBytewiseComparatorIntTest { |
||||||
|
|
||||||
|
// test with 500 random positive integer keys
|
||||||
|
private static final int TOTAL_KEYS = 500; |
||||||
|
private static final byte[][] keys = new byte[TOTAL_KEYS][4]; |
||||||
|
|
||||||
|
@BeforeClass |
||||||
|
public static void prepareKeys() { |
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(4); |
||||||
|
final Random random = new Random(); |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
final int ri = random.nextInt() & Integer.MAX_VALUE; // the & ensures positive integer
|
||||||
|
buf.putInt(ri); |
||||||
|
buf.flip(); |
||||||
|
final byte[] key = buf.array(); |
||||||
|
|
||||||
|
// does key already exist (avoid duplicates)
|
||||||
|
if (keyExists(key, i)) { |
||||||
|
i--; // loop round and generate a different key
|
||||||
|
} else { |
||||||
|
System.arraycopy(key, 0, keys[i], 0, 4); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean keyExists(final byte[] key, final int limit) { |
||||||
|
for (int j = 0; j < limit; j++) { |
||||||
|
if (Arrays.equals(key, keys[j])) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Parameters(name = "{0}") |
||||||
|
public static Iterable<Object[]> parameters() { |
||||||
|
return Arrays.asList(new Object[][] { |
||||||
|
{ "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.MUTEX }, |
||||||
|
{ "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, |
||||||
|
{ "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, |
||||||
|
{ "non-direct_noreuse", false, -1, null }, |
||||||
|
{ "direct_noreuse", true, -1, null } |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Parameter(0) |
||||||
|
public String name; |
||||||
|
|
||||||
|
@Parameter(1) |
||||||
|
public boolean useDirectBuffer; |
||||||
|
|
||||||
|
@Parameter(2) |
||||||
|
public int maxReusedBufferSize; |
||||||
|
|
||||||
|
@Parameter(3) |
||||||
|
public ReusedSynchronisationType reusedSynchronisationType; |
||||||
|
|
||||||
|
@ClassRule |
||||||
|
public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = |
||||||
|
new RocksNativeLibraryResource(); |
||||||
|
|
||||||
|
@Rule |
||||||
|
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorDefaultCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final ReverseBytewiseComparator comparator = |
||||||
|
new ReverseBytewiseComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtrip(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void javaComparatorNamedCf() throws RocksDBException { |
||||||
|
try (final ComparatorOptions options = new ComparatorOptions() |
||||||
|
.setUseDirectBuffer(useDirectBuffer) |
||||||
|
.setMaxReusedBufferSize(maxReusedBufferSize) |
||||||
|
// if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
|
||||||
|
.setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); |
||||||
|
final ReverseBytewiseComparator comparator |
||||||
|
= new ReverseBytewiseComparator(options)) { |
||||||
|
|
||||||
|
// test the round-tripability of keys written and read with the Comparator
|
||||||
|
testRoundtripCf(FileSystems.getDefault().getPath( |
||||||
|
dbFolder.getRoot().getAbsolutePath()), comparator); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtrip(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
try (final Options opt = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator)) { |
||||||
|
|
||||||
|
// store TOTAL_KEYS into the db
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { |
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in descending
|
||||||
|
// order
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString()); |
||||||
|
final RocksIterator it = db.newIterator()) { |
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MAX_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isLessThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test which stores random keys into a column family |
||||||
|
* in the database |
||||||
|
* using an {@link IntComparator} |
||||||
|
* it then checks that these keys are read back in |
||||||
|
* ascending order |
||||||
|
* |
||||||
|
* @param db_path A path where we can store database |
||||||
|
* files temporarily |
||||||
|
* |
||||||
|
* @param comparator the comparator |
||||||
|
* |
||||||
|
* @throws RocksDBException if a database error happens. |
||||||
|
*/ |
||||||
|
private void testRoundtripCf(final Path db_path, |
||||||
|
final AbstractComparator comparator) throws RocksDBException { |
||||||
|
|
||||||
|
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList( |
||||||
|
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), |
||||||
|
new ColumnFamilyDescriptor("new_cf".getBytes(), |
||||||
|
new ColumnFamilyOptions() |
||||||
|
.setComparator(comparator)) |
||||||
|
); |
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>(); |
||||||
|
|
||||||
|
try (final DBOptions opt = new DBOptions() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setCreateMissingColumnFamilies(true)) { |
||||||
|
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles)) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
for (int i = 0; i < TOTAL_KEYS; i++) { |
||||||
|
db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// integer keys should be in descending
|
||||||
|
// order
|
||||||
|
final ByteBuffer key = ByteBuffer.allocate(4); |
||||||
|
try (final RocksDB db = RocksDB.open(opt, db_path.toString(), |
||||||
|
cfDescriptors, cfHandles); |
||||||
|
final RocksIterator it = db.newIterator(cfHandles.get(1))) { |
||||||
|
try { |
||||||
|
assertThat(cfDescriptors.size()).isEqualTo(2); |
||||||
|
assertThat(cfHandles.size()).isEqualTo(2); |
||||||
|
|
||||||
|
it.seekToFirst(); |
||||||
|
int lastKey = Integer.MAX_VALUE; |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
key.put(it.key()); |
||||||
|
key.flip(); |
||||||
|
final int thisKey = key.getInt(); |
||||||
|
key.clear(); |
||||||
|
assertThat(thisKey).isLessThan(lastKey); |
||||||
|
lastKey = thisKey; |
||||||
|
count++; |
||||||
|
} |
||||||
|
|
||||||
|
assertThat(count).isEqualTo(TOTAL_KEYS); |
||||||
|
|
||||||
|
} finally { |
||||||
|
for (final ColumnFamilyHandle cfHandle : cfHandles) { |
||||||
|
cfHandle.close(); |
||||||
|
} |
||||||
|
cfHandles.clear(); |
||||||
|
for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { |
||||||
|
cfDescriptor.getOptions().close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue