RocksJava API - fix Transaction.multiGet() size limit, remove bogus EnsureLocalCapacity() calls (#10674)
Summary: Resolves see https://github.com/facebook/rocksdb/issues/9006 Fixes 2 related issues with JNI local references in the RocksJava API. 1. Some instances of RocksJava API JNI code appear to have misunderstood the reason for `JNIEnv->EnsureLocalCapacity()` and are carrying out bogus checks which happen to fail with some larger parameter values (many column families in a single call, very long key names or values). Remove these checks and add some regression tests for the previous failures. 2. The helper for Transaction multiGet operations (`multiGet()`, `multiGetForUpdate()`,...) is limited in the number of keys it can `get()` for because it requires a corresponding number of live local references. Refactor the helper slightly, copying out the key contents within a loop so that the references don't have to exist at the same time. Pull Request resolved: https://github.com/facebook/rocksdb/pull/10674 Reviewed By: ajkr Differential Revision: D40515361 Pulled By: jay-zhuang fbshipit-source-id: f1be0126181a698b3ad27c0945a39c54d950aa25main
parent
bf78380851
commit
17553bdd5e
@ -0,0 +1,140 @@ |
|||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test for changes made by |
||||||
|
* <a link="https://github.com/facebook/rocksdb/issues/9006">transactional multiGet problem</a> |
||||||
|
* the tests here were previously broken by the nonsense removed by that change. |
||||||
|
*/ |
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class MultiColumnRegressionTest { |
||||||
|
@Parameterized.Parameters |
||||||
|
public static List<Params> data() { |
||||||
|
return Arrays.asList(new Params(3, 100), new Params(3, 1000000)); |
||||||
|
} |
||||||
|
|
||||||
|
public static class Params { |
||||||
|
final int numColumns; |
||||||
|
final int keySize; |
||||||
|
|
||||||
|
public Params(final int numColumns, final int keySize) { |
||||||
|
this.numColumns = numColumns; |
||||||
|
this.keySize = keySize; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Rule public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
private final Params params; |
||||||
|
|
||||||
|
public MultiColumnRegressionTest(final Params params) { |
||||||
|
this.params = params; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void transactionDB() throws RocksDBException { |
||||||
|
final List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>(); |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
sb.append("cf" + i); |
||||||
|
for (int j = 0; j < params.keySize; j++) sb.append("_cf"); |
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor(sb.toString().getBytes())); |
||||||
|
} |
||||||
|
try (final Options opt = new Options().setCreateIfMissing(true); |
||||||
|
final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { |
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles = |
||||||
|
db.createColumnFamilies(columnFamilyDescriptors); |
||||||
|
} |
||||||
|
|
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); |
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>(); |
||||||
|
try (final TransactionDB tdb = TransactionDB.open(new DBOptions().setCreateIfMissing(true), |
||||||
|
new TransactionDBOptions(), dbFolder.getRoot().getAbsolutePath(), |
||||||
|
columnFamilyDescriptors, columnFamilyHandles)) { |
||||||
|
final WriteOptions writeOptions = new WriteOptions(); |
||||||
|
try (Transaction transaction = tdb.beginTransaction(writeOptions)) { |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
transaction.put( |
||||||
|
columnFamilyHandles.get(i), ("key" + i).getBytes(), ("value" + (i - 7)).getBytes()); |
||||||
|
} |
||||||
|
transaction.put("key".getBytes(), "value".getBytes()); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { |
||||||
|
columnFamilyHandle.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles2 = new ArrayList<>(); |
||||||
|
try (final TransactionDB tdb = TransactionDB.open(new DBOptions().setCreateIfMissing(true), |
||||||
|
new TransactionDBOptions(), dbFolder.getRoot().getAbsolutePath(), |
||||||
|
columnFamilyDescriptors, columnFamilyHandles2)) { |
||||||
|
try (Transaction transaction = tdb.beginTransaction(new WriteOptions())) { |
||||||
|
final ReadOptions readOptions = new ReadOptions(); |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
final byte[] value = |
||||||
|
transaction.get(columnFamilyHandles2.get(i), readOptions, ("key" + i).getBytes()); |
||||||
|
assertThat(value).isEqualTo(("value" + (i - 7)).getBytes()); |
||||||
|
} |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles2) { |
||||||
|
columnFamilyHandle.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void optimisticDB() throws RocksDBException { |
||||||
|
final List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>(); |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); |
||||||
|
} |
||||||
|
|
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); |
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>(); |
||||||
|
try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( |
||||||
|
new DBOptions().setCreateIfMissing(true), dbFolder.getRoot().getAbsolutePath(), |
||||||
|
columnFamilyDescriptors, columnFamilyHandles)) { |
||||||
|
try (Transaction transaction = otdb.beginTransaction(new WriteOptions())) { |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
transaction.put( |
||||||
|
columnFamilyHandles.get(i), ("key" + i).getBytes(), ("value" + (i - 7)).getBytes()); |
||||||
|
} |
||||||
|
transaction.put("key".getBytes(), "value".getBytes()); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { |
||||||
|
columnFamilyHandle.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles2 = new ArrayList<>(); |
||||||
|
try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( |
||||||
|
new DBOptions().setCreateIfMissing(true), dbFolder.getRoot().getAbsolutePath(), |
||||||
|
columnFamilyDescriptors, columnFamilyHandles2)) { |
||||||
|
try (Transaction transaction = otdb.beginTransaction(new WriteOptions())) { |
||||||
|
final ReadOptions readOptions = new ReadOptions(); |
||||||
|
for (int i = 0; i < params.numColumns; i++) { |
||||||
|
final byte[] value = |
||||||
|
transaction.get(columnFamilyHandles2.get(i), readOptions, ("key" + i).getBytes()); |
||||||
|
assertThat(value).isEqualTo(("value" + (i - 7)).getBytes()); |
||||||
|
} |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles2) { |
||||||
|
columnFamilyHandle.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
|
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class PutMultiplePartsTest { |
||||||
|
@Parameterized.Parameters |
||||||
|
public static List<Integer> data() { |
||||||
|
return Arrays.asList(2, 3, 250, 20000); |
||||||
|
} |
||||||
|
|
||||||
|
@Rule public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
private final int numParts; |
||||||
|
|
||||||
|
public PutMultiplePartsTest(final Integer numParts) { |
||||||
|
this.numParts = numParts; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void putUntracked() throws RocksDBException { |
||||||
|
try (final Options options = new Options().setCreateIfMissing(true); |
||||||
|
final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); |
||||||
|
final TransactionDB txnDB = |
||||||
|
TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { |
||||||
|
try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { |
||||||
|
final byte[][] keys = generateItems("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
transaction.putUntracked(keys, values); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
txnDB.syncWal(); |
||||||
|
} |
||||||
|
|
||||||
|
validateResults(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void put() throws RocksDBException { |
||||||
|
try (final Options options = new Options().setCreateIfMissing(true); |
||||||
|
final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); |
||||||
|
final TransactionDB txnDB = |
||||||
|
TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { |
||||||
|
try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { |
||||||
|
final byte[][] keys = generateItems("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
transaction.put(keys, values); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
txnDB.syncWal(); |
||||||
|
} |
||||||
|
|
||||||
|
validateResults(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void putUntrackedCF() throws RocksDBException { |
||||||
|
try (final Options options = new Options().setCreateIfMissing(true); |
||||||
|
final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); |
||||||
|
final TransactionDB txnDB = |
||||||
|
TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath()); |
||||||
|
final ColumnFamilyHandle columnFamilyHandle = |
||||||
|
txnDB.createColumnFamily(new ColumnFamilyDescriptor("cfTest".getBytes()))) { |
||||||
|
try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { |
||||||
|
final byte[][] keys = generateItems("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
transaction.putUntracked(columnFamilyHandle, keys, values); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
txnDB.syncWal(); |
||||||
|
} |
||||||
|
|
||||||
|
validateResultsCF(); |
||||||
|
} |
||||||
|
@Test |
||||||
|
public void putCF() throws RocksDBException { |
||||||
|
try (final Options options = new Options().setCreateIfMissing(true); |
||||||
|
final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); |
||||||
|
final TransactionDB txnDB = |
||||||
|
TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath()); |
||||||
|
final ColumnFamilyHandle columnFamilyHandle = |
||||||
|
txnDB.createColumnFamily(new ColumnFamilyDescriptor("cfTest".getBytes()))) { |
||||||
|
try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { |
||||||
|
final byte[][] keys = generateItems("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
transaction.put(columnFamilyHandle, keys, values); |
||||||
|
transaction.commit(); |
||||||
|
} |
||||||
|
txnDB.syncWal(); |
||||||
|
} |
||||||
|
|
||||||
|
validateResultsCF(); |
||||||
|
} |
||||||
|
|
||||||
|
private void validateResults() throws RocksDBException { |
||||||
|
try (final RocksDB db = RocksDB.open(new Options(), dbFolder.getRoot().getAbsolutePath())) { |
||||||
|
final List<byte[]> keys = generateItemsAsList("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
|
||||||
|
StringBuilder singleKey = new StringBuilder(); |
||||||
|
for (int i = 0; i < numParts; i++) { |
||||||
|
singleKey.append(new String(keys.get(i), StandardCharsets.UTF_8)); |
||||||
|
} |
||||||
|
final byte[] result = db.get(singleKey.toString().getBytes()); |
||||||
|
StringBuilder singleValue = new StringBuilder(); |
||||||
|
for (int i = 0; i < numParts; i++) { |
||||||
|
singleValue.append(new String(values[i], StandardCharsets.UTF_8)); |
||||||
|
} |
||||||
|
assertThat(result).isEqualTo(singleValue.toString().getBytes()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void validateResultsCF() throws RocksDBException { |
||||||
|
final List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>(); |
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor("cfTest".getBytes())); |
||||||
|
columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); |
||||||
|
final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>(); |
||||||
|
try (final RocksDB db = RocksDB.open(new DBOptions(), dbFolder.getRoot().getAbsolutePath(), |
||||||
|
columnFamilyDescriptors, columnFamilyHandles)) { |
||||||
|
final List<byte[]> keys = generateItemsAsList("key", ":", numParts); |
||||||
|
final byte[][] values = generateItems("value", "", numParts); |
||||||
|
|
||||||
|
StringBuilder singleKey = new StringBuilder(); |
||||||
|
for (int i = 0; i < numParts; i++) { |
||||||
|
singleKey.append(new String(keys.get(i), StandardCharsets.UTF_8)); |
||||||
|
} |
||||||
|
final byte[] result = db.get(columnFamilyHandles.get(0), singleKey.toString().getBytes()); |
||||||
|
StringBuilder singleValue = new StringBuilder(); |
||||||
|
for (int i = 0; i < numParts; i++) { |
||||||
|
singleValue.append(new String(values[i], StandardCharsets.UTF_8)); |
||||||
|
} |
||||||
|
assertThat(result).isEqualTo(singleValue.toString().getBytes()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private byte[][] generateItems(final String prefix, final String suffix, final int numItems) { |
||||||
|
return generateItemsAsList(prefix, suffix, numItems).toArray(new byte[0][0]); |
||||||
|
} |
||||||
|
|
||||||
|
private List<byte[]> generateItemsAsList( |
||||||
|
final String prefix, final String suffix, final int numItems) { |
||||||
|
final List<byte[]> items = new ArrayList<>(); |
||||||
|
for (int i = 0; i < numItems; i++) { |
||||||
|
items.add((prefix + i + suffix).getBytes()); |
||||||
|
} |
||||||
|
return items; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue