package voldemort.store.rocksdb; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksIterator; import voldemort.routing.RoutingStrategy; import voldemort.store.PersistenceFailureException; import voldemort.store.StoreBinaryFormat; import voldemort.store.StoreUtils; import voldemort.utils.ByteArray; import voldemort.utils.ClosableIterator; import voldemort.utils.Pair; import voldemort.versioning.Version; import voldemort.versioning.Versioned; public class PartitionPrefixedRocksDbStorageEngine extends RocksDbStorageEngine { private final RoutingStrategy routingStrategy; public PartitionPrefixedRocksDbStorageEngine(String storeName, RocksDB rdbStore, ColumnFamilyHandle storeHandle, ColumnFamilyOptions storeOptions, int lockStripes, RoutingStrategy routingStrategy, boolean enableReadLocks) { super(storeName, rdbStore, storeHandle, storeOptions, lockStripes, enableReadLocks); this.routingStrategy = routingStrategy; } @Override public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries(int partition) { return new RocksdbEntriesIterator(getRocksDbIterator(), partition); } @Override public ClosableIterator<ByteArray> keys(int partition) { return new RocksdbKeysIterator(getRocksDbIterator(), partition); } private ByteArray validateAndConstructKey(ByteArray key) { StoreUtils.assertValidKey(key); int partition = routingStrategy.getMasterPartition(key.get()); ByteArray prefixedKey = new ByteArray(StoreBinaryFormat.makePrefixedKey(key.get(), partition)); return prefixedKey; } @Override public List<Versioned<byte[]>> get(ByteArray key, byte[] transforms) throws PersistenceFailureException { return super.get(validateAndConstructKey(key), transforms); } @Override public void put(ByteArray key, Versioned<byte[]> value, byte[] transforms) throws PersistenceFailureException { super.put(validateAndConstructKey(key), value, transforms); } @Override public boolean delete(ByteArray key, Version version) throws PersistenceFailureException { return super.delete(validateAndConstructKey(key), version); } @Override public boolean isPartitionScanSupported() { return true; } @Override public List<Versioned<byte[]>> multiVersionPut(ByteArray key, List<Versioned<byte[]>> values) { return super.multiVersionPut(validateAndConstructKey(key), values); } private static class RocksdbEntriesIterator implements ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> { // TODO May need to identify non const methods in the inner Iterator adn // provide external synchronization on those if needed RocksIterator innerIterator; private List<Pair<ByteArray, Versioned<byte[]>>> cache; private final int partitionId; public RocksdbEntriesIterator(RocksIterator innerIterator, int partition) { this.innerIterator = innerIterator; this.partitionId = partition; // Caller of the RocksIterator should seek it before the first use. // seek to the prefix specified this.innerIterator.seek(StoreBinaryFormat.makePartitionKey(partition)); cache = new ArrayList<Pair<ByteArray, Versioned<byte[]>>>(); } @Override public boolean hasNext() { return cache.size() > 0 || makeMore(); } @Override public Pair<ByteArray, Versioned<byte[]>> next() { if(cache.size() == 0) { if(!makeMore()) { throw new NoSuchElementException("Iterated to end."); } } return cache.remove(cache.size() - 1); } protected boolean makeMore() { if(innerIterator.isValid()) { byte[] keyEntry = innerIterator.key(); // Check if the next key returned starts with the expected // prefix if(StoreBinaryFormat.extractPartition(keyEntry) != partitionId) { return false; } byte[] valueEntry = innerIterator.value(); innerIterator.next(); ByteArray key = new ByteArray(StoreBinaryFormat.extractKey(keyEntry)); for(Versioned<byte[]> val: StoreBinaryFormat.fromByteArray(valueEntry)) { cache.add(new Pair<ByteArray, Versioned<byte[]>>(key, val)); } return true; } return false; } @Override public void remove() { throw new UnsupportedOperationException("No removal"); } @Override public void close() { this.innerIterator.dispose(); } } private static class RocksdbKeysIterator implements ClosableIterator<ByteArray> { // TODO May need to identify non const methods in the inner Iterator adn // provide external synchronization on those if needed RocksIterator innerIterator; private ByteArray cache; private final int partitionId; public RocksdbKeysIterator(RocksIterator innerIterator, int partition) { this.innerIterator = innerIterator; this.partitionId = partition; // Caller of the RocksIterator should seek it to the prefix before // the first use. this.innerIterator.seek(StoreBinaryFormat.makePartitionKey(partition)); cache = null; } @Override public boolean hasNext() { return cache != null || fetchnextKey(); } private boolean fetchnextKey() { if(this.innerIterator.isValid()) { byte[] keyEntry = this.innerIterator.key(); // Check if the next key returned starts with the expected // prefix if(StoreBinaryFormat.extractPartition(keyEntry) != partitionId) { return false; } this.innerIterator.next(); cache = new ByteArray(StoreBinaryFormat.extractKey(keyEntry)); return true; } return false; } @Override public ByteArray next() { if(cache == null) { if(!fetchnextKey()) { throw new NoSuchElementException("Iterate to end"); } } ByteArray result = cache; cache = null; return result; } @Override public void remove() { throw new UnsupportedOperationException("No removal"); } @Override public void close() { this.innerIterator.dispose(); } } }