use of com.apple.foundationdb.Transaction in project fdb-record-layer by FoundationDB.
the class BunchedMapTest method concurrentLegalUpdates.
@Test
public void concurrentLegalUpdates() throws ExecutionException, InterruptedException {
final Tuple value = Tuple.from((Object) null);
// From initial database, essentially any two updates will cause each one
// to get its own key.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(1066L), value).join();
map.put(tr2, bmSubspace, Tuple.from(1415L), value).join();
}, true, Arrays.asList(Tuple.from(1066L), Tuple.from(1415L)));
try (Transaction tr = db.createTransaction()) {
tr.clear(bmSubspace.range());
tr.commit().get();
}
final List<Tuple> tuples = LongStream.range(100L, 115L).boxed().map(Tuple::from).collect(Collectors.toList());
db.run(tr -> {
tuples.forEach(t -> map.put(tr, bmSubspace, t, value).join());
return null;
});
// Case 1: Transaction reads the same key as another, but it
// doesn't actually need the part that is different.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(116L), value).join();
assertEquals(value, map.get(tr2, bmSubspace, Tuple.from(112L)).join().get());
}, true, Arrays.asList(Tuple.from(100L), Tuple.from(110L)));
// Case 2: Transaction reads the same key in a way while
// another transaction writes the same value into the key.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(105L), value).join();
assertEquals(value, map.get(tr2, bmSubspace, Tuple.from(105L)).join().get());
}, true, Arrays.asList(Tuple.from(100L), Tuple.from(110L)));
// Case 3: Transaction ranges read will overlap
runWithTwoTrs((tr1, tr2) -> {
// As the first one is full, the logic chooses to put (109L, null)
// as the first key of the second set of things.
map.put(tr1, bmSubspace, Tuple.from(109L, null), value).join();
// As the split is in the middle, it will choose to put
// (107L, null) in the first group of transactions.
map.put(tr2, bmSubspace, Tuple.from(107L, null), value).join();
}, true, Arrays.asList(Tuple.from(100L), Tuple.from(105L), Tuple.from(109L, null)));
try (Transaction tr = db.createTransaction()) {
map.verifyIntegrity(tr, bmSubspace).get();
// Fill up the (100L,) to (105L,) range.
LongStream.range(0L, 5L).boxed().map(l -> Tuple.from(104L, l)).forEach(t -> map.put(tr, bmSubspace, t, value).join());
tr.commit().get();
}
// Case 4: Read a value that is rewritten to the same value when appending
// to the beginning.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(104L, 100L), value).join();
assertEquals(value, map.get(tr2, bmSubspace, Tuple.from(107L)).join().get());
}, true, Arrays.asList(Tuple.from(100L), Tuple.from(104L, 100L), Tuple.from(109L, null)));
try (Transaction tr = db.createTransaction()) {
// Fill up (104L, 100L) to (109, null).
LongStream.range(101L, 104L).boxed().map(l -> Tuple.from(104L, l)).forEach(t -> map.put(tr, bmSubspace, t, value).join());
tr.commit().get();
}
// Case 5: Two things going in the middle of two filled ranges.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(104L, 42L), value).join();
map.put(tr2, bmSubspace, Tuple.from(104L, 43L), value).join();
}, true, Arrays.asList(Tuple.from(100L), Tuple.from(104L, 42L), Tuple.from(104L, 43L), Tuple.from(104L, 100L), Tuple.from(109L, null)));
// Case 6: Two keys before all filled ranges.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(42L), value).join();
map.put(tr2, bmSubspace, Tuple.from(43L), value).join();
}, true, Arrays.asList(Tuple.from(42L), Tuple.from(43L), Tuple.from(100L), Tuple.from(104L, 42L), Tuple.from(104L, 43L), Tuple.from(104L, 100L), Tuple.from(109L, null)));
try (Transaction tr = db.createTransaction()) {
// Fill up the last range.
LongStream.range(117L, 120L).boxed().map(Tuple::from).forEach(t -> map.put(tr, bmSubspace, t, value).join());
tr.commit().get();
}
// Case 7: Two keys after filled ranges.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(120L), value).join();
map.put(tr2, bmSubspace, Tuple.from(121L), value).join();
}, true, Arrays.asList(Tuple.from(42L), Tuple.from(43L), Tuple.from(100L), Tuple.from(104L, 42L), Tuple.from(104L, 43L), Tuple.from(104L, 100L), Tuple.from(109L, null), Tuple.from(120L), Tuple.from(121L)));
// Case 8: Adding to a full range while simultaneously adding something after the range.
runWithTwoTrs((tr1, tr2) -> {
map.put(tr1, bmSubspace, Tuple.from(102L, 0L), value).join();
map.put(tr2, bmSubspace, Tuple.from(104L, 41L), value).join();
}, true, Arrays.asList(Tuple.from(42L), Tuple.from(43L), Tuple.from(100L), Tuple.from(104L), Tuple.from(104L, 41L), Tuple.from(104L, 43L), Tuple.from(104L, 100L), Tuple.from(109L, null), Tuple.from(120L), Tuple.from(121L)));
// Compact the data to a minimal number of keys.
try (Transaction tr = db.createTransaction()) {
assertNull(map.compact(tr, bmSubspace, 0, null).get());
map.verifyIntegrity(tr, bmSubspace).get();
tr.commit().get();
}
verifyBoundaryKeys(Arrays.asList(Tuple.from(42L), Tuple.from(104L, 2L), Tuple.from(105L), Tuple.from(113L)));
}
use of com.apple.foundationdb.Transaction in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method repairRecordKeyIfNecessary.
private void repairRecordKeyIfNecessary(@Nonnull FDBRecordContext context, @Nonnull Subspace recordSubspace, @Nonnull KeyValue keyValue, final boolean isDryRun) {
final RecordMetaData metaData = metaDataProvider.getRecordMetaData();
final Tuple recordKey = recordSubspace.unpack(keyValue.getKey());
// Ignore version key
if (metaData.isStoreRecordVersions() && isMaybeVersion(recordKey)) {
return;
}
final Message protoRecord = serializer.deserialize(metaData, recordKey, keyValue.getValue(), getTimer());
final RecordType recordType = metaData.getRecordTypeForDescriptor(protoRecord.getDescriptorForType());
final KeyExpression primaryKeyExpression = recordType.getPrimaryKey();
if (recordKey.size() == primaryKeyExpression.getColumnSize()) {
context.increment(FDBStoreTimer.Counts.REPAIR_RECORD_KEY);
final Tuple newPrimaryKey = recordKey.add(SplitHelper.UNSPLIT_RECORD);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(KeyValueLogMessage.of("Repairing primary key", LogMessageKeys.RECORD_TYPE, recordType.getName(), subspaceProvider.logKey(), subspaceProvider.toString(context), "dry_run", isDryRun, "orig_primary_key", recordKey, "new_primary_key", newPrimaryKey));
}
if (!isDryRun) {
final Transaction tr = context.ensureActive();
tr.clear(keyValue.getKey());
tr.set(recordSubspace.pack(newPrimaryKey), keyValue.getValue());
}
} else if (recordKey.size() == primaryKeyExpression.getColumnSize() + 1) {
Object suffix = recordKey.get(recordKey.size() - 1);
if (!(suffix instanceof Long) || !(((Long) suffix) == SplitHelper.UNSPLIT_RECORD)) {
context.increment(FDBStoreTimer.Counts.INVALID_SPLIT_SUFFIX);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(KeyValueLogMessage.of("Invalid split suffix", subspaceProvider.logKey(), subspaceProvider.toString(context), LogMessageKeys.RECORD_TYPE, recordType.getName(), LogMessageKeys.PRIMARY_KEY, recordKey));
}
}
} else {
context.increment(FDBStoreTimer.Counts.INVALID_KEY_LENGTH);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(KeyValueLogMessage.of("Invalid key length", subspaceProvider.logKey(), subspaceProvider.toString(context), LogMessageKeys.RECORD_TYPE, recordType.getName(), LogMessageKeys.PRIMARY_KEY, recordKey));
}
}
}
use of com.apple.foundationdb.Transaction in project fdb-record-layer by FoundationDB.
the class FDBReverseDirectoryCache method putToSubspace.
private CompletableFuture<Void> putToSubspace(@Nonnull FDBRecordContext context, @Nonnull Subspace reverseCacheSubspace, @Nonnull ScopedValue<String> scopedPathString, @Nonnull Long pathValue) {
String pathString = scopedPathString.getData();
Transaction transaction = context.ensureActive();
return transaction.snapshot().get(reverseCacheSubspace.pack(pathValue)).thenApply(valueBytes -> {
if (valueBytes != null) {
String readValue = Tuple.fromBytes(valueBytes).getString(0);
if (!readValue.equals(pathString)) {
throw new RecordCoreException("Provided value for path key does not match existing value in reverse directory layer cache").addLogInfo(LogMessageKeys.RESOLVER, scopedPathString.getScope()).addLogInfo(LogMessageKeys.RESOLVER_PATH, pathString).addLogInfo(LogMessageKeys.RESOLVER_KEY, pathValue).addLogInfo(LogMessageKeys.CACHED_KEY, readValue);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Put unnecessary, found path '" + readValue + "'' in reverse lookup for value '" + pathValue + "'");
}
persistentCacheHitCount.incrementAndGet();
logStatsToStoreTimer(context, FDBStoreTimer.Counts.REVERSE_DIR_PERSISTENT_CACHE_HIT_COUNT);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Adding '" + pathValue + "' to reverse lookup with key " + pathString);
}
// Take care NOT to place the value in our cache. We don't own the calling context/transaction
// so it is possible it could fail/rollback leaving our cache inconsistent.
transaction.set(reverseCacheSubspace.pack(pathValue), Tuple.from(pathString).pack());
persistentCacheMissCount.incrementAndGet();
logStatsToStoreTimer(context, FDBStoreTimer.Counts.REVERSE_DIR_PERSISTENT_CACHE_MISS_COUNT);
}
return null;
});
}
use of com.apple.foundationdb.Transaction in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method uncheckedMarkIndexReadable.
/**
* Marks the index with the given name as readable without checking to see if it is
* ready. This is dangerous to do if one has not first verified that the
* index is ready to be readable as it can cause half-built indexes to be
* used within queries and can thus produce inconsistent results.
* @param indexName the name of the index to mark readable
* @return a future that will contain <code>true</code> if the store was modified
* and <code>false</code> otherwise
*/
@Nonnull
public CompletableFuture<Boolean> uncheckedMarkIndexReadable(@Nonnull String indexName) {
if (recordStoreStateRef.get() == null) {
return preloadRecordStoreStateAsync().thenCompose(vignore -> uncheckedMarkIndexReadable(indexName));
}
addIndexStateReadConflict(indexName);
beginRecordStoreStateWrite();
boolean haveFuture = false;
try {
Transaction tr = ensureContextActive();
byte[] indexKey = indexStateSubspace().pack(indexName);
CompletableFuture<Boolean> future = tr.get(indexKey).thenApply(previous -> {
if (previous != null) {
updateIndexState(indexName, indexKey, IndexState.READABLE);
return true;
} else {
return false;
}
}).whenComplete((b, t) -> endRecordStoreStateWrite()).thenApply(this::addRemoveReplacedIndexesCommitCheckIfChanged);
haveFuture = true;
return future;
} finally {
if (!haveFuture) {
endRecordStoreStateWrite();
}
}
}
use of com.apple.foundationdb.Transaction in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method vacuumReadableIndexesBuildData.
public void vacuumReadableIndexesBuildData() {
Transaction tr = ensureContextActive();
// also adds state to read conflicts
Map<Index, IndexState> indexStates = getAllIndexStates();
for (Map.Entry<Index, IndexState> entry : indexStates.entrySet()) {
if (entry.getValue().equals(IndexState.READABLE)) {
clearReadableIndexBuildData(tr, entry.getKey());
}
}
}
Aggregations