From f4f46a49834e2ec02224f73737acfac0a4f1b358 Mon Sep 17 00:00:00 2001 From: Mike McMahon Date: Thu, 11 Apr 2024 10:30:14 -0700 Subject: [PATCH 1/2] Add a way of scanning records in a KeyRange instead of a TupleRange. --- .../provider/foundationdb/FDBRecordStore.java | 44 +++++++++++++++---- .../foundationdb/FDBRecordStoreBase.java | 15 +++++++ .../foundationdb/FDBTypedRecordStore.java | 7 +++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java index cb8d05c41f..26d580dc90 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java @@ -46,6 +46,7 @@ import com.apple.foundationdb.record.IndexScanType; import com.apple.foundationdb.record.IndexState; import com.apple.foundationdb.record.IsolationLevel; +import com.apple.foundationdb.record.KeyRange; import com.apple.foundationdb.record.MutableRecordStoreState; import com.apple.foundationdb.record.PipelineOperation; import com.apple.foundationdb.record.PlanHashable; @@ -136,6 +137,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -1192,32 +1194,56 @@ public RecordCursor> scanRecords(@Nullable final Tuple } @Nonnull - @SuppressWarnings("PMD.CloseResource") public RecordCursor> scanTypedRecords(@Nonnull RecordSerializer typedSerializer, @Nullable final Tuple low, @Nullable final Tuple high, @Nonnull final EndpointType lowEndpoint, @Nonnull final EndpointType highEndpoint, @Nullable byte[] continuation, @Nonnull ScanProperties scanProperties) { + return scanTypedRecordsInternal(typedSerializer, + builder -> builder.setLow(low, lowEndpoint).setHigh(high, highEndpoint), + continuation, scanProperties); + } + + @Nonnull + @Override + public RecordCursor> scanRecordsKeyRange(@Nonnull final KeyRange range, @Nullable final byte[] continuation, @Nonnull final ScanProperties scanProperties) { + return scanTypedRecordsKeyRange(serializer, range, continuation, scanProperties); + } + + @Nonnull + public RecordCursor> scanTypedRecordsKeyRange(@Nonnull RecordSerializer typedSerializer, + @Nonnull final KeyRange range, + @Nullable byte[] continuation, + @Nonnull ScanProperties scanProperties) { + return scanTypedRecordsInternal(typedSerializer, + builder -> builder.setRange(range), + continuation, scanProperties); + } + + @Nonnull + @SuppressWarnings("PMD.CloseResource") + private RecordCursor> scanTypedRecordsInternal(@Nonnull RecordSerializer typedSerializer, + @Nonnull Consumer setRange, + @Nullable byte[] continuation, + @Nonnull ScanProperties scanProperties) { final RecordMetaData metaData = metaDataProvider.getRecordMetaData(); final Subspace recordsSubspace = recordsSubspace(); final SplitHelper.SizeInfo sizeInfo = new SplitHelper.SizeInfo(); final RecordCursor rawRecords; if (metaData.isSplitLongRecords()) { - RecordCursor keyValues = KeyValueCursor.Builder.withSubspace(recordsSubspace) + KeyValueCursor.Builder keyValuesBuilder = KeyValueCursor.Builder.withSubspace(recordsSubspace) .setContext(context).setContinuation(continuation) - .setLow(low, lowEndpoint) - .setHigh(high, highEndpoint) - .setScanProperties(scanProperties.with(ExecuteProperties::clearRowAndTimeLimits).with(ExecuteProperties::clearState)) - .build(); + .setScanProperties(scanProperties.with(ExecuteProperties::clearRowAndTimeLimits).with(ExecuteProperties::clearState)); + setRange.accept(keyValuesBuilder); + RecordCursor keyValues = keyValuesBuilder.build(); rawRecords = new SplitHelper.KeyValueUnsplitter(context, recordsSubspace, keyValues, useOldVersionFormat(), sizeInfo, scanProperties.isReverse(), new CursorLimitManager(context, scanProperties.with(ExecuteProperties::clearReturnedRowLimit))) .skip(scanProperties.getExecuteProperties().getSkip()) .limitRowsTo(scanProperties.getExecuteProperties().getReturnedRowLimit()); } else { KeyValueCursor.Builder keyValuesBuilder = KeyValueCursor.Builder.withSubspace(recordsSubspace) - .setContext(context).setContinuation(continuation) - .setLow(low, lowEndpoint) - .setHigh(high, highEndpoint); + .setContext(context).setContinuation(continuation); + setRange.accept(keyValuesBuilder); if (omitUnsplitRecordSuffix) { rawRecords = keyValuesBuilder.setScanProperties(scanProperties).build().map(kv -> { sizeInfo.set(kv); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java index bcedcc2d11..a932cde1cc 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java @@ -30,6 +30,7 @@ import com.apple.foundationdb.record.IndexScanType; import com.apple.foundationdb.record.IndexState; import com.apple.foundationdb.record.IsolationLevel; +import com.apple.foundationdb.record.KeyRange; import com.apple.foundationdb.record.PipelineOperation; import com.apple.foundationdb.record.RecordCoreArgumentException; import com.apple.foundationdb.record.RecordCoreException; @@ -866,6 +867,20 @@ RecordCursor> scanRecords(@Nullable Tuple low, @Nullable Tupl @Nullable byte[] continuation, @Nonnull ScanProperties scanProperties); + /** + * Scan the records in the database in a key range. + * + * @param range key range + * @param continuation any continuation from a previous scan + * @param scanProperties skip, limit and other scan properties + * + * @return a cursor that will scan everything in the range, picking up at continuation, and honoring the given scan properties + */ + @Nonnull + RecordCursor> scanRecordsKeyRange(@Nonnull KeyRange range, + @Nullable byte[] continuation, + @Nonnull ScanProperties scanProperties); + /** * Count the number of records in the database in a range. * diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java index e8d63d62ab..69cb6bf0d5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.record.ExecuteState; import com.apple.foundationdb.record.IndexEntry; import com.apple.foundationdb.record.IsolationLevel; +import com.apple.foundationdb.record.KeyRange; import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.RecordCursor; import com.apple.foundationdb.record.RecordIndexUniquenessViolation; @@ -183,6 +184,12 @@ public RecordCursor> scanRecords(@Nullable Tuple low, @Nullab return untypedStore.scanTypedRecords(typedSerializer, low, high, lowEndpoint, highEndpoint, continuation, scanProperties); } + @Nonnull + @Override + public RecordCursor> scanRecordsKeyRange(@Nonnull final KeyRange range, @Nullable final byte[] continuation, @Nonnull final ScanProperties scanProperties) { + return untypedStore.scanTypedRecordsKeyRange(typedSerializer, range, continuation, scanProperties); + } + @Nonnull @Override public CompletableFuture countRecords(@Nullable Tuple low, @Nullable Tuple high, @Nonnull EndpointType lowEndpoint, @Nonnull EndpointType highEndpoint, @Nullable byte[] continuation, @Nonnull ScanProperties scanProperties) { From 954b481f8d5a1eb737572c2f94a9f125e2934461 Mon Sep 17 00:00:00 2001 From: Mike McMahon Date: Thu, 11 Apr 2024 10:41:44 -0700 Subject: [PATCH 2/2] Use that for indexing by record instead of trying to recover TupleRange's from unbuilt Range's. --- .../IndexingMultiTargetByRecords.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/IndexingMultiTargetByRecords.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/IndexingMultiTargetByRecords.java index 53de3fdb66..e7e72fe47f 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/IndexingMultiTargetByRecords.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/IndexingMultiTargetByRecords.java @@ -23,10 +23,10 @@ import com.apple.foundationdb.Range; import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.async.AsyncUtil; -import com.apple.foundationdb.async.RangeSet; import com.apple.foundationdb.record.ExecuteProperties; import com.apple.foundationdb.record.IndexBuildProto; import com.apple.foundationdb.record.IsolationLevel; +import com.apple.foundationdb.record.KeyRange; import com.apple.foundationdb.record.RecordCursor; import com.apple.foundationdb.record.RecordCursorResult; import com.apple.foundationdb.record.ScanProperties; @@ -125,9 +125,7 @@ private CompletableFuture buildMultiTargetIndex(@Nonnull SubspaceProvider } else { final Range range = tupleRange.toRange(); rangeStart = range.begin; - // tupleRange has an inclusive high endpoint, so end isn't a valid tuple. - // But buildRangeOnly needs to convert missing Ranges back to TupleRanges, so round up. - rangeEnd = ByteArrayUtil.strinc(range.end); + rangeEnd = range.end; } final CompletableFuture maybePresetRangeFuture = @@ -172,12 +170,13 @@ private CompletableFuture buildRangeOnly(@Nonnull FDBRecordStore store, if (range == null) { return AsyncUtil.READY_FALSE; // no more missing ranges - all done } - final Tuple rangeStart = RangeSet.isFirstKey(range.begin) ? null : Tuple.fromBytes(range.begin); - final Tuple rangeEnd = RangeSet.isFinalKey(range.end) ? null : Tuple.fromBytes(range.end); - final TupleRange tupleRange = TupleRange.between(rangeStart, rangeEnd); + final byte[] keyPrefix = store.recordsSubspace().pack(); + final byte[] rangeStart = ByteArrayUtil.join(keyPrefix, range.begin); + final byte[] rangeEnd = ByteArrayUtil.join(keyPrefix, range.end); + final KeyRange keyRange = new KeyRange(rangeStart, rangeEnd); RecordCursor> cursor = - store.scanRecords(tupleRange, null, scanProperties); + store.scanRecordsKeyRange(keyRange, null, scanProperties); final AtomicReference>> lastResult = new AtomicReference<>(RecordCursorResult.exhausted()); final AtomicBoolean hasMore = new AtomicBoolean(true); @@ -189,20 +188,20 @@ private CompletableFuture buildRangeOnly(@Nonnull FDBRecordStore store, this::getRecordIfTypeMatch, lastResult, hasMore, recordsScanned, isIdempotent) .thenCompose(ignore -> postIterateRangeOnly(targetRangeSets, hasMore.get(), lastResult, - rangeStart, rangeEnd, scanProperties.isReverse())); + range.begin, range.end, scanProperties.isReverse())); }); } private CompletableFuture postIterateRangeOnly(List targetRangeSets, boolean hasMore, AtomicReference>> lastResult, - Tuple rangeStart, Tuple rangeEnd, boolean isReverse) { + byte[] rangeStart, byte[] rangeEnd, boolean isReverse) { if (isReverse) { - Tuple continuation = hasMore ? lastResult.get().get().getPrimaryKey() : rangeStart; - return insertRanges(targetRangeSets, packOrNull(continuation), packOrNull(rangeEnd)) + byte[] continuation = hasMore ? packOrNull(lastResult.get().get().getPrimaryKey()) : rangeStart; + return insertRanges(targetRangeSets, continuation, rangeEnd) .thenApply(ignore -> hasMore || rangeStart != null); } else { - Tuple continuation = hasMore ? lastResult.get().get().getPrimaryKey() : rangeEnd; - return insertRanges(targetRangeSets, packOrNull(rangeStart), packOrNull(continuation)) + byte[] continuation = hasMore ? packOrNull(lastResult.get().get().getPrimaryKey()) : rangeEnd; + return insertRanges(targetRangeSets, rangeStart, continuation) .thenApply(ignore -> hasMore || rangeEnd != null); } }