use of com.apple.foundationdb.TransactionContext in project fdb-record-layer by FoundationDB.
the class BunchedMap method compact.
/**
* Compact the values within the map into as few keys as possible. This will scan through and re-write
* the keys to be optimal. This feature is experimental at the moment, but it should be used to better
* pack entries if needed.
*
* @param tcx database or transaction to use when compacting data
* @param subspace subspace within which the map's data are located
* @param keyLimit maximum number of database keys to read in a single transaction
* @param continuation the continuation returned from a previous call or <code>null</code>
* to start from the beginning of the subspace
* @return future that will complete with a continuation that can be used to complete
* the compaction across multiple transactions (<code>null</code> if finished)
*/
@Nonnull
protected CompletableFuture<byte[]> compact(@Nonnull TransactionContext tcx, @Nonnull Subspace subspace, int keyLimit, @Nullable byte[] continuation) {
return tcx.runAsync(tr -> {
byte[] subspaceKey = subspace.getKey();
byte[] begin = (continuation == null) ? subspaceKey : continuation;
byte[] end = subspace.range().end;
final AsyncIterable<KeyValue> iterable = tr.snapshot().getRange(begin, end, keyLimit);
List<Map.Entry<K, V>> currentEntryList = new ArrayList<>(bunchSize);
// The estimated size can be off (and will be off for many implementations of BunchedSerializer),
// but it is just a heuristic to know when to split, so that's fine (I claim).
AtomicInteger currentEntrySize = new AtomicInteger(0);
AtomicInteger readKeys = new AtomicInteger(0);
AtomicReference<byte[]> lastReadKeyBytes = new AtomicReference<>(null);
AtomicReference<K> lastKey = new AtomicReference<>(null);
return AsyncUtil.forEach(iterable, kv -> {
final K boundaryKey = serializer.deserializeKey(kv.getKey(), subspaceKey.length);
final List<Map.Entry<K, V>> entriesFromKey = serializer.deserializeEntries(boundaryKey, kv.getValue());
readKeys.incrementAndGet();
if (entriesFromKey.size() >= bunchSize && currentEntryList.isEmpty()) {
// Nothing can be done. Just move on.
lastReadKeyBytes.set(null);
return;
}
if (lastReadKeyBytes.get() == null) {
lastReadKeyBytes.set(kv.getKey());
}
final byte[] endKeyBytes = ByteArrayUtil.join(subspaceKey, serializer.serializeKey(entriesFromKey.get(entriesFromKey.size() - 1).getKey()), ZERO_ARRAY);
tr.addReadConflictRange(lastReadKeyBytes.get(), endKeyBytes);
tr.addWriteConflictRange(lastReadKeyBytes.get(), kv.getKey());
lastReadKeyBytes.set(endKeyBytes);
tr.clear(kv.getKey());
instrumentDelete(kv.getKey(), kv.getValue());
for (Map.Entry<K, V> entry : entriesFromKey) {
byte[] serializedEntry = serializer.serializeEntry(entry);
if (currentEntrySize.get() + serializedEntry.length > MAX_VALUE_SIZE && !currentEntryList.isEmpty()) {
flushEntryList(tr, subspaceKey, currentEntryList, lastKey);
currentEntrySize.set(0);
}
currentEntryList.add(entry);
currentEntrySize.addAndGet(serializedEntry.length);
if (currentEntryList.size() == bunchSize) {
flushEntryList(tr, subspaceKey, currentEntryList, lastKey);
currentEntrySize.set(0);
}
}
}, tr.getExecutor()).thenApply(vignore -> {
if (!currentEntryList.isEmpty()) {
flushEntryList(tr, subspaceKey, currentEntryList, lastKey);
}
// Return a valid continuation if there might be more keys
if (lastKey.get() != null && keyLimit != ReadTransaction.ROW_LIMIT_UNLIMITED && readKeys.get() == keyLimit) {
return ByteArrayUtil.join(subspaceKey, serializer.serializeKey(lastKey.get()), ZERO_ARRAY);
} else {
return null;
}
});
});
}
Aggregations