use of com.apple.foundationdb.ReadTransaction in project fdb-record-layer by FoundationDB.
the class RangeSet method insertRange.
/**
* Inserts a range into the set. The range inserted will begin at <code>begin</code> (inclusive) and end at
* <code>end</code> (exclusive). If the <code>requireEmpty</code> is set, then this will only actually change the
* database in the case that the range being added is not yet included in the set. If this flag is set to
* <code>false</code>, then this will "fill in the gaps" between ranges present so that the whole range is
* present following this transactions operation. The return value will (when ready) be equal to <code>true</code>
* if and only if there are changes (i.e., writes) to the database that need to be made, i.e., the range was not
* already included in the set. If the initial end point is less than the begin point, then this will
* throw an {@link IllegalArgumentException} indicating that one has passed an inverted range. If <code>begin</code>
* and <code>end</code> are equal, then this will immediately return a future that is set to <code>false</code>
* (corresponding to adding an empty range). If <code>null</code> is set for either endpoint, this will insert
* a range all the way to the end of the total range.
*
* <p>
* In terms of isolation, this method will add both read- and write-conflict ranges. It adds a read-conflict range
* corresponding to the range being added, i.e., for the keys within the range from <code>begin</code> to <code>end</code>.
* This is so that if this range is modified concurrently by another writer, this transaction will fail (as the exact
* writes done depend on these keys not being modified.) It will also a write-conflict ranges corresponding
* to all of the individual ranges added to the database. That means that if the range is initially empty,
* a write-conflict range corresponding to the keys from <code>begin</code> to <code>end</code>. This is done
* so that if another transaction checks to see if a key in the range we are writing is within the range set
* and finds that it is not, this write will then cause that transaction to fail if it is committed after this
* one. If the range is not empty initially, write conflict ranges are added for all of the "gaps" that have
* to be added. (So, if the range is already full, then no write conflict ranges are added at all.)
* </p>
*
* @param tc the transaction or database in which to operate
* @param begin the (inclusive) beginning of the range to add
* @param end the (exclusive) end of the range to add
* @param requireEmpty whether this should only be added if this range is initially empty
* @return a future that is <code>true</code> if there were any modifications to the database and <code>false</code> otherwise
*/
@Nonnull
public CompletableFuture<Boolean> insertRange(@Nonnull TransactionContext tc, @Nullable byte[] begin, @Nullable byte[] end, boolean requireEmpty) {
byte[] beginNonNull = (begin == null) ? FIRST_KEY : begin;
byte[] endNonNull = (end == null) ? FINAL_KEY : end;
checkKey(beginNonNull);
checkRange(beginNonNull, endNonNull);
if (ByteArrayUtil.compareUnsigned(beginNonNull, endNonNull) == 0) {
return AsyncUtil.READY_FALSE;
}
return tc.runAsync(tr -> {
// Add a read range for the keys corresponding to the bounds of this range.
byte[] frobnicatedBegin = subspace.pack(beginNonNull);
byte[] frobnicatedEnd = subspace.pack(endNonNull);
tr.addReadConflictRange(frobnicatedBegin, frobnicatedEnd);
// Look to see what is already in this database to see what of this range is already present.
// Note: the two range reads are done in parallel, which essentially means we get the before read
// "for free".
byte[] keyAfterBegin = keyAfter(frobnicatedBegin);
ReadTransaction snapshot = tr.snapshot();
AsyncIterator<KeyValue> beforeIterator = snapshot.getRange(subspace.range().begin, keyAfterBegin, 1, true).iterator();
AsyncIterator<KeyValue> afterIterator = snapshot.getRange(keyAfterBegin, frobnicatedEnd, (requireEmpty ? 1 : ReadTransaction.ROW_LIMIT_UNLIMITED), false).iterator();
return beforeIterator.onHasNext().thenCompose(hasBefore -> {
AtomicReference<byte[]> lastSeen = new AtomicReference<>(frobnicatedBegin);
KeyValue before = hasBefore ? beforeIterator.next() : null;
// end of that range.
if (hasBefore) {
byte[] beforeEnd = before.getValue();
if (ByteArrayUtil.compareUnsigned(beginNonNull, beforeEnd) < 0) {
if (requireEmpty) {
return AsyncUtil.READY_FALSE;
} else {
lastSeen.set(subspace.pack(beforeEnd));
}
}
}
if (requireEmpty) {
// If we will only add on the empty case, then the after iterator has to be empty.
return afterIterator.onHasNext().thenApply(hasNext -> {
if (hasNext) {
return false;
} else {
if (before != null && ByteArrayUtil.compareUnsigned(beginNonNull, before.getValue()) == 0) {
// This consolidation is done to make the simple case of a single writer
// going forward more space compact.
tr.addReadConflictKey(before.getKey());
tr.set(before.getKey(), endNonNull);
} else {
tr.set(frobnicatedBegin, endNonNull);
}
tr.addWriteConflictRange(frobnicatedBegin, frobnicatedEnd);
return true;
}
});
} else {
AtomicBoolean changed = new AtomicBoolean(false);
// If we are allowing non-empty ranges, then we just need to fill in the gaps.
return AsyncUtil.whileTrue(() -> {
byte[] lastSeenBytes = lastSeen.get();
if (MoreAsyncUtil.isCompletedNormally(afterIterator.onHasNext()) && afterIterator.hasNext()) {
KeyValue kv = afterIterator.next();
if (ByteArrayUtil.compareUnsigned(lastSeenBytes, kv.getKey()) < 0) {
tr.set(lastSeenBytes, subspace.unpack(kv.getKey()).getBytes(0));
tr.addWriteConflictRange(lastSeenBytes, kv.getKey());
changed.set(true);
}
lastSeen.set(subspace.pack(kv.getValue()));
}
return afterIterator.onHasNext();
}, tc.getExecutor()).thenApply(vignore -> {
byte[] lastSeenBytes = lastSeen.get();
// Get from lastSeen to the end (the last gap).
if (ByteArrayUtil.compareUnsigned(lastSeenBytes, frobnicatedEnd) < 0) {
tr.set(lastSeenBytes, endNonNull);
tr.addWriteConflictRange(lastSeenBytes, frobnicatedEnd);
changed.set(true);
}
return changed.get();
});
}
});
});
}
use of com.apple.foundationdb.ReadTransaction in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method getSnapshotRecordCount.
@Override
public CompletableFuture<Long> getSnapshotRecordCount(@Nonnull KeyExpression key, @Nonnull Key.Evaluated value, @Nonnull IndexQueryabilityFilter indexQueryabilityFilter) {
if (getRecordMetaData().getRecordCountKey() != null) {
if (key.getColumnSize() != value.size()) {
throw recordCoreException("key and value are not the same size");
}
final ReadTransaction tr = context.readTransaction(true);
final Tuple subkey = Tuple.from(RECORD_COUNT_KEY).addAll(value.toTupleAppropriateList());
if (getRecordMetaData().getRecordCountKey().equals(key)) {
return tr.get(getSubspace().pack(subkey)).thenApply(FDBRecordStore::decodeRecordCount);
} else if (key.isPrefixKey(getRecordMetaData().getRecordCountKey())) {
AsyncIterable<KeyValue> kvs = tr.getRange(getSubspace().range(Tuple.from(RECORD_COUNT_KEY)));
return MoreAsyncUtil.reduce(getExecutor(), kvs.iterator(), 0L, (count, kv) -> count + decodeRecordCount(kv.getValue()));
}
}
return evaluateAggregateFunction(Collections.emptyList(), IndexFunctionHelper.count(key), TupleRange.allOf(value.toTuple()), IsolationLevel.SNAPSHOT, indexQueryabilityFilter).thenApply(tuple -> tuple.getLong(0));
}
use of com.apple.foundationdb.ReadTransaction in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method loadRecordVersionAsync.
/**
* Async version of {@link #loadRecordVersion(Tuple, boolean)}.
* @param primaryKey the primary key of the record
* @param snapshot whether to snapshot read
* @return a future that completes with the version of the record of {@code Optional.empty()} if versions are not enabled for this store
*/
@Nonnull
public Optional<CompletableFuture<FDBRecordVersion>> loadRecordVersionAsync(@Nonnull final Tuple primaryKey, final boolean snapshot) {
final RecordMetaData metaData = metaDataProvider.getRecordMetaData();
if (useOldVersionFormat() && !metaData.isStoreRecordVersions()) {
// a priori that this will return an empty optional, so we return it without doing any I/O.
return Optional.empty();
} else {
byte[] versionKey = getSubspace().pack(recordVersionKey(primaryKey));
Optional<CompletableFuture<FDBRecordVersion>> cachedOptional = context.getLocalVersion(versionKey).map(localVersion -> CompletableFuture.completedFuture(FDBRecordVersion.incomplete(localVersion)));
if (cachedOptional.isPresent()) {
return cachedOptional;
}
final ReadTransaction tr = snapshot ? ensureContextActive().snapshot() : ensureContextActive();
return Optional.of(tr.get(versionKey).thenApply(valueBytes -> {
if (valueBytes == null) {
return null;
} else if (useOldVersionFormat()) {
return FDBRecordVersion.complete(valueBytes, false);
} else {
return SplitHelper.unpackVersion(valueBytes);
}
}));
}
}
use of com.apple.foundationdb.ReadTransaction in project fdb-record-layer by FoundationDB.
the class FDBStoreTimerTest method testLowLevelIoMetrics.
@Test
public void testLowLevelIoMetrics() {
final FDBStoreTimer timer = new FDBStoreTimer();
try (FDBRecordContext context = fdb.openContext(null, timer)) {
Transaction tr = context.ensureActive();
tr.clear(subspace.range());
tr.commit().join();
}
assertThat(timer.getCount(FDBStoreTimer.Counts.DELETES), equalTo(1));
assertThat(timer.getCount(FDBStoreTimer.Events.COMMITS), equalTo(1));
timer.reset();
int writeBytes = 0;
try (FDBRecordContext context = fdb.openContext(null, timer)) {
Transaction tr = context.ensureActive();
for (int i = 0; i < 5; i++) {
byte[] key = subspace.pack(Tuple.from(i));
byte[] value = subspace.pack(Tuple.from("foo", i));
tr.set(key, value);
writeBytes += (key.length + value.length);
}
ReadTransaction rtr = tr.snapshot();
List<KeyValue> values = rtr.getRange(subspace.range()).asList().join();
assertThat(values.size(), equalTo(5));
tr.commit().join();
}
assertThat(timer.getCount(FDBStoreTimer.Counts.WRITES), equalTo(5));
assertThat(timer.getCount(FDBStoreTimer.Counts.BYTES_WRITTEN), equalTo(writeBytes));
assertThat(timer.getCount(FDBStoreTimer.Counts.READS), equalTo(1));
assertThat(timer.getCount(FDBStoreTimer.Counts.BYTES_READ), equalTo(writeBytes));
assertThat(timer.getCount(FDBStoreTimer.Events.COMMITS), equalTo(1));
}
use of com.apple.foundationdb.ReadTransaction in project fdb-record-layer by FoundationDB.
the class SplitHelperTest method loadWithSplit.
@Nullable
private FDBRawRecord loadWithSplit(@Nonnull FDBRecordContext context, @Nonnull Tuple key, boolean splitLongRecords, boolean omitUnsplitSuffix, @Nullable FDBStoredSizes expectedSizes, @Nullable byte[] expectedContents, @Nullable FDBRecordVersion expectedVersion) {
final ReadTransaction tr = context.ensureActive();
SplitHelper.SizeInfo sizeInfo = new SplitHelper.SizeInfo();
FDBRawRecord rawRecord;
try {
rawRecord = SplitHelper.loadWithSplit(tr, context, subspace, key, splitLongRecords, omitUnsplitSuffix, sizeInfo).get();
} catch (InterruptedException | ExecutionException e) {
throw FDBExceptions.wrapException(e);
}
if (expectedSizes == null || expectedContents == null) {
assertNull(rawRecord);
} else {
assertNotNull(rawRecord);
assertArrayEquals(expectedContents, rawRecord.getRawRecord());
int valueSize = expectedContents.length;
if (expectedVersion != null) {
valueSize += 1 + FDBRecordVersion.VERSION_LENGTH;
}
assertEquals(valueSize, rawRecord.getValueSize());
if (!splitLongRecords) {
assertThat(rawRecord.isSplit(), is(false));
}
if (omitUnsplitSuffix) {
assertThat(rawRecord.isVersionedInline(), is(false));
}
boolean isSplit = rawRecord.getKeyCount() - (expectedVersion != null ? 1 : 0) != 1;
assertEquals(isSplit, rawRecord.isSplit());
assertEquals(key, rawRecord.getPrimaryKey());
if (expectedVersion != null) {
assertThat(rawRecord.isVersionedInline(), is(true));
assertEquals(expectedVersion, rawRecord.getVersion());
} else {
assertThat(rawRecord.isVersionedInline(), is(false));
assertNull(rawRecord.getVersion());
}
// Verify that the expected sizes are the same as the ones retrieved
assertEquals(expectedSizes.getKeyCount(), rawRecord.getKeyCount());
assertEquals(expectedSizes.getKeySize(), rawRecord.getKeySize());
assertEquals(expectedSizes.getValueSize(), rawRecord.getValueSize());
assertEquals(expectedSizes.isSplit(), rawRecord.isSplit());
assertEquals(expectedSizes.isVersionedInline(), rawRecord.isVersionedInline());
// Verify using sizeInfo and using the raw record get the same size information
assertEquals(rawRecord.getKeyCount(), sizeInfo.getKeyCount());
assertEquals(rawRecord.getKeySize(), sizeInfo.getKeySize());
assertEquals(rawRecord.getValueSize(), sizeInfo.getValueSize());
assertEquals(rawRecord.isSplit(), sizeInfo.isSplit());
assertEquals(rawRecord.isVersionedInline(), sizeInfo.isVersionedInline());
}
return rawRecord;
}
Aggregations