use of com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer in project fdb-record-layer by FoundationDB.
the class FDBRecordStoreStateCacheTest method conflictWhenCachedChanged.
/**
* Make sure that if one transaction changes the store header then an open store in another transaction that
* loaded the store state from cache will fail at commit time with conflict.
*/
@ParameterizedTest(name = "conflictWhenCachedChanged (test context = {0})")
@MethodSource("testContextSource")
public void conflictWhenCachedChanged(@Nonnull StateCacheTestContext testContext) throws Exception {
FDBRecordStoreStateCache origStoreStateCache = fdb.getStoreStateCache();
try {
fdb.setStoreStateCache(testContext.getCache(fdb));
RecordMetaData metaData1 = RecordMetaData.build(TestRecords1Proto.getDescriptor());
RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecords1Proto.getDescriptor());
metaDataBuilder.addIndex("MySimpleRecord", "num_value_2");
RecordMetaData metaData2 = metaDataBuilder.getRecordMetaData();
assertThat(metaData1.getVersion(), lessThan(metaData2.getVersion()));
FDBRecordStore.Builder storeBuilder;
// Initialize the record store with a meta-data store
try (FDBRecordContext context = openContext()) {
context.getTimer().reset();
FDBRecordStore recordStore = FDBRecordStore.newBuilder().setContext(context).setMetaDataProvider(metaData1).setKeySpacePath(path).create();
assertEquals(1, context.getTimer().getCount(FDBStoreTimer.Counts.STORE_STATE_CACHE_MISS));
assertEquals(metaData1.getVersion(), recordStore.getRecordStoreState().getStoreHeader().getMetaDataversion());
commit(context);
storeBuilder = recordStore.asBuilder();
}
// Load the record store state into the cache.
try (FDBRecordContext context1 = testContext.getCachedContext(fdb, storeBuilder);
FDBRecordContext context2 = testContext.getCachedContext(fdb, storeBuilder)) {
context1.setTimer(new FDBStoreTimer());
context2.setTimer(new FDBStoreTimer());
FDBRecordStore recordStore1 = storeBuilder.copyBuilder().setContext(context1).setMetaDataProvider(metaData1).open();
assertEquals(1, context1.getTimer().getCount(FDBStoreTimer.Counts.STORE_STATE_CACHE_HIT));
assertEquals(metaData1.getVersion(), recordStore1.getRecordMetaData().getVersion());
assertEquals(metaData1.getVersion(), recordStore1.getRecordStoreState().getStoreHeader().getMetaDataversion());
// Update the meta-data in the second transaction
FDBRecordStore recordStore2 = storeBuilder.copyBuilder().setContext(context2).setMetaDataProvider(metaData2).open();
assertEquals(1, context2.getTimer().getCount(FDBStoreTimer.Counts.STORE_STATE_CACHE_HIT));
assertEquals(Collections.singletonList(recordStore2.getRecordMetaData().getRecordType("MySimpleRecord")), recordStore2.getRecordMetaData().recordTypesForIndex(recordStore2.getRecordMetaData().getIndex("MySimpleRecord$num_value_2")));
assertEquals(metaData2.getVersion(), recordStore2.getRecordMetaData().getVersion());
assertEquals(metaData2.getVersion(), recordStore2.getRecordStoreState().getStoreHeader().getMetaDataversion());
context2.commit();
// Add a write to context1 so that the conflict ranges actually get checked.
recordStore1.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder().setRecNo(1066).setNumValue2(1415).build());
// Should conflict on store header even though not actually read in this transaction
assertThrows(FDBExceptions.FDBStoreTransactionConflictException.class, context1::commit);
}
// New transaction should now see the new meta-data version
try (FDBRecordContext context = openContext()) {
context.getTimer().reset();
// Trying to load with the old meta-data should fail
assertThrows(RecordStoreStaleMetaDataVersionException.class, () -> storeBuilder.copyBuilder().setContext(context).setMetaDataProvider(metaData1).open());
assertEquals(1, context.getTimer().getCount(FDBStoreTimer.Counts.STORE_STATE_CACHE_MISS));
// Trying to load with the new meta-data should succeed
FDBRecordStore recordStore = storeBuilder.copyBuilder().setContext(context).setMetaDataProvider(metaData2).open();
assertEquals(1, context.getTimer().getCount(FDBStoreTimer.Counts.STORE_STATE_CACHE_HIT));
assertEquals(metaData2.getVersion(), recordStore.getRecordStoreState().getStoreHeader().getMetaDataversion());
}
} finally {
fdb.setStoreStateCache(origStoreStateCache);
}
}
use of com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer in project fdb-record-layer by FoundationDB.
the class LocatableResolverTest method testDirectoryCacheWithUncommittedContext.
@Test
public void testDirectoryCacheWithUncommittedContext() {
FDBDatabase fdb = FDBDatabaseFactory.instance().getDatabase();
fdb.clearCaches();
// In the scoped directory layer test, this can conflict with initializing the reverse directory layer
fdb.getReverseDirectoryCache().waitUntilReadyForTesting();
final String key = "hello " + UUID.randomUUID();
FDBStoreTimer timer = new FDBStoreTimer();
long resolved;
try (FDBRecordContext context = fdb.openContext(null, timer)) {
// Ensure initial get read version is instrumented
context.getReadVersion();
resolved = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertAll(() -> assertThat("directory resolution should not have been from cache", timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), equalTo(1)), () -> assertThat("should only have opened at most 2 child transaction", timer.getCount(FDBStoreTimer.Counts.OPEN_CONTEXT), lessThanOrEqualTo(3)), () -> assertThat("should only have gotten one read version", timer.getCount(FDBStoreTimer.Events.GET_READ_VERSION), equalTo(1)), () -> assertThat("should have only committed the inner transaction", timer.getCount(FDBStoreTimer.Events.COMMIT), lessThanOrEqualTo(1)));
// do not commit transaction (though child transaction updating the resolved key should have been committed)
}
// Should read cached value
timer.reset();
long resolved2;
try (FDBRecordContext context = fdb.openContext(null, timer)) {
// Ensure initial get read version is instrumented
context.getReadVersion();
resolved2 = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertAll(() -> assertThat("resolved value from cache does not match initial resolution", resolved2, equalTo(resolved)), () -> assertEquals(0, timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), "should not have read from the directory layer"), () -> assertEquals(1, timer.getCount(FDBStoreTimer.Counts.OPEN_CONTEXT), "should not have opened any additional contexts"), () -> assertEquals(1, timer.getCount(FDBStoreTimer.Events.GET_READ_VERSION), "should not need any additional read versions"), () -> assertEquals(0, timer.getCount(FDBStoreTimer.Events.COMMIT)));
}
// Clear the caches and see that the value in the database matches
database.clearCaches();
timer.reset();
try (FDBRecordContext context = fdb.openContext(null, timer)) {
long resolved3 = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertAll(() -> assertThat("resolved value from database does not match initial resolution", resolved3, equalTo(resolved)), () -> assertThat("directory resolution should not have been from cache", timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), equalTo(1)), () -> assertThat("should only have opened at most 2 child transaction", timer.getCount(FDBStoreTimer.Counts.OPEN_CONTEXT), lessThanOrEqualTo(3)), () -> assertThat("should only have gotten one read version", timer.getCount(FDBStoreTimer.Events.GET_READ_VERSION), equalTo(1)), () -> assertThat("should only have committed the inner transaction", timer.getCount(FDBStoreTimer.Events.COMMIT), lessThanOrEqualTo(1)));
}
}
use of com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer in project fdb-record-layer by FoundationDB.
the class LocatableResolverTest method testCachesWinnerOfConflict.
@Test
public void testCachesWinnerOfConflict() {
FDBDatabase fdb = FDBDatabaseFactory.instance().getDatabase();
fdb.clearCaches();
// In the scoped directory layer test, this can conflict with initializing the reverse directory layer
fdb.getReverseDirectoryCache().waitUntilReadyForTesting();
final String key = "hello " + UUID.randomUUID();
long resolved;
final FDBStoreTimer timer = new FDBStoreTimer();
try (FDBRecordContext context1 = fdb.openContext(null, timer);
FDBRecordContext context2 = fdb.openContext(null, timer)) {
// Ensure both started
context1.getReadVersion();
context2.getReadVersion();
// Both contexts try to create the key
CompletableFuture<Long> resolvedFuture1 = globalScope.resolve(context1, key);
CompletableFuture<Long> resolvedFuture2 = globalScope.resolve(context2, key);
long resolved1 = context1.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, resolvedFuture1);
long resolved2 = context2.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, resolvedFuture2);
assertAll(() -> assertThat("two concurrent resolutions of the same key should match", resolved1, equalTo(resolved2)), () -> assertThat("at least one transaction should read from database", timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), greaterThanOrEqualTo(1)), () -> assertThat("should not open more transactions than the two parents and five children", timer.getCount(FDBStoreTimer.Counts.OPEN_CONTEXT), lessThanOrEqualTo(7)), () -> assertThat("should not have committed more than the five children", timer.getCount(FDBStoreTimer.Events.COMMIT), lessThanOrEqualTo(5)));
resolved = resolved1;
}
timer.reset();
try (FDBRecordContext context = fdb.openContext(null, timer)) {
context.getReadVersion();
long resolvedAgain = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertAll(() -> assertThat("resolved value in cache should match initial resolution", resolvedAgain, equalTo(resolved)), () -> assertThat("should have resolved from cache", timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), equalTo(0)));
}
}
use of com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer in project fdb-record-layer by FoundationDB.
the class LocatableResolverTest method testDoesNotCacheValueReadFromReadYourWritesCache.
/**
* This is mainly to test a counter factual where the same transaction is used to actually resolve the value as
* is used by the caller. In that case, one could accidentally pollute the cache with uncommitted data. To protect
* against that, this test is designed to fail if someone changes the resolution logic so that uncommitted data
* (even possibly uncommitted data re-read from the same transaction that wrote it) might be put in the cache.
*/
@Test
public void testDoesNotCacheValueReadFromReadYourWritesCache() {
FDBDatabase fdb = FDBDatabaseFactory.instance().getDatabase();
fdb.clearCaches();
final String key = "hello " + UUID.randomUUID();
final FDBStoreTimer timer = new FDBStoreTimer();
long resolved;
try (FDBRecordContext context = fdb.openContext(null, timer)) {
// First time: nothing in cache or DB. Entry is created.
resolved = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertEquals(1, timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ), "should have read from the database");
// Second time: if same context used to create and read, then this would read from transaction's read your writes cache, not the database
long resolvedAgain = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertEquals(resolved, resolvedAgain, "resolving the same key should not change the value even in the same transaction");
// do not commit main transaction
}
// Read from cache. If present, this should not have changed its value
timer.reset();
boolean cached;
try (FDBRecordContext context = fdb.openContext(null, timer)) {
long resolvedFromCache = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
cached = timer.getCount(FDBStoreTimer.Events.DIRECTORY_READ) == 0;
if (cached) {
assertEquals(resolved, resolvedFromCache, "resolved value should have changed when reading from cache");
}
}
// Clear caches, and re-read from the database.
if (cached) {
fdb.clearCaches();
timer.reset();
try (FDBRecordContext context = fdb.openContext(null, timer)) {
long resolvedFromDb = context.asyncToSync(FDBStoreTimer.Waits.WAIT_DIRECTORY_RESOLVE, globalScope.resolve(context, key));
assertEquals(resolved, resolvedFromDb, "resolved value from database should have matched initial resolution");
}
}
}
use of com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer in project fdb-record-layer by FoundationDB.
the class LeaderboardIndexTest method getFDB.
@BeforeEach
public void getFDB() {
if (TRACE) {
FDBDatabaseFactory.instance().setTrace("/tmp", "LeaderboardIndexTest");
}
fdb = FDBDatabaseFactory.instance().getDatabase();
metrics = new FDBStoreTimer();
}
Aggregations