public static CoordinationResource create(TransactionManager transactionManager) { return new SimpleCoordinationResource(transactionManager, new TransactionSchemaManager( CoordinationServices.createDefault( transactionManager.getKeyValueService(), transactionManager.getTimestampService(), false))); }
/** * Returns the version of the transactions schema associated with the provided timestamp. * * This method may perpetuate the existing state one or more times to achieve consensus. It will repeatedly * attempt to perpetuate the existing state until a consensus for the provided timestamp argument is achieved. * * This method should only be called with timestamps that have already been given out by the timestamp service; * otherwise, achieving a consensus may take a long time. */ public int getTransactionsSchemaVersion(long timestamp) { if (timestamp < AtlasDbConstants.STARTING_TS) { throw new SafeIllegalStateException("Query attempted for timestamp {} which was never given out by the" + " timestamp service, as timestamps start at {}", SafeArg.of("queriedTimestamp", timestamp), SafeArg.of("startOfTime", AtlasDbConstants.STARTING_TS)); } Optional<Integer> possibleVersion = extractTimestampVersion(coordinationService.getValueForTimestamp(timestamp), timestamp); while (!possibleVersion.isPresent()) { CheckAndSetResult<ValueAndBound<InternalSchemaMetadata>> casResult = tryPerpetuateExistingState(); possibleVersion = extractTimestampVersion(casResult.existingValues() .stream() .filter(valueAndBound -> valueAndBound.bound() >= timestamp) .findAny(), timestamp); } return possibleVersion.get(); }
@Override public void forceInstallNewTransactionsSchemaVersion(int newVersion) { while (transactionSchemaManager.getTransactionsSchemaVersion( timestampService.getFreshTimestamp()) != newVersion) { transactionSchemaManager.tryInstallNewTransactionsSchemaVersion(newVersion); advanceOneHundredMillionTimestamps(); } }
@Override public int getTransactionsSchemaVersion(long timestamp) { return transactionSchemaManager.getTransactionsSchemaVersion(timestamp); }
@Override public boolean tryInstallNewTransactionsSchemaVersion(int newVersion) { return transactionSchemaManager.tryInstallNewTransactionsSchemaVersion(newVersion); }
/** * Attempts to install a new transactions table schema version, by submitting a relevant transform. * * The execution of this method does not guarantee that the provided version will eventually be installed. * This method returns true if and only if in the map agreed by the coordination service evaluated at the validity * bound, the transactions schema version is equal to newVersion. */ public boolean tryInstallNewTransactionsSchemaVersion(int newVersion) { List<Integer> presentVersionPeakValidity = coordinationService.tryTransformCurrentValue( valueAndBound -> installNewVersionInMapOrDefault(newVersion, valueAndBound)) .existingValues() .stream() .map(valueAndBound -> valueAndBound.value() .orElseThrow(() -> new SafeIllegalStateException("Unexpectedly found no value in store")) .timestampToTransactionsTableSchemaVersion() .getValueForTimestamp(valueAndBound.bound())) .collect(Collectors.toList()); return Iterables.getOnlyElement(presentVersionPeakValidity) == newVersion; }
private InternalSchemaMetadata installNewVersionInMapOrDefault(int newVersion, ValueAndBound<InternalSchemaMetadata> valueAndBound) { if (!valueAndBound.value().isPresent()) { log.warn("Attempting to install a new transactions schema version {}, but no past data was found," + " so we attempt to install default instead. This should normally only happen once per" + " server, and only on or around first startup since upgrading to a version of AtlasDB" + " that is aware of the transactions table. If this message persists, please contact" + " support.", SafeArg.of("newVersion", newVersion)); return InternalSchemaMetadata.defaultValue(); } log.info("Attempting to install a new transactions schema version {}, on top of schema metadata" + " that is valid up till timestamp {}.", SafeArg.of("newVersion", newVersion), SafeArg.of("oldDataValidity", valueAndBound.bound())); InternalSchemaMetadata internalSchemaMetadata = valueAndBound.value().get(); return InternalSchemaMetadata.builder() .from(internalSchemaMetadata) .timestampToTransactionsTableSchemaVersion( installNewVersionInMap( internalSchemaMetadata.timestampToTransactionsTableSchemaVersion(), valueAndBound.bound() + 1, newVersion)) .build(); }
@Test public void canForceCoordinations() { assertThat(manager.getTransactionsSchemaVersion(337)).isEqualTo(1); }
@Before public void setUp() { assertThat(manager.tryInstallNewTransactionsSchemaVersion(1)).isTrue(); }
/** * Attempts to install a new transactions table schema version, by submitting a relevant transform. * * The execution of this method does not guarantee that the provided version will eventually be installed. * This method returns true if and only if in the map agreed by the coordination service evaluated at the validity * bound, the transactions schema version is equal to newVersion. */ public boolean tryInstallNewTransactionsSchemaVersion(int newVersion) { List<Integer> presentVersionPeakValidity = coordinationService.tryTransformCurrentValue( valueAndBound -> installNewVersionInMapOrDefault(newVersion, valueAndBound)) .existingValues() .stream() .map(valueAndBound -> valueAndBound.value() .orElseThrow(() -> new SafeIllegalStateException("Unexpectedly found no value in store")) .timestampToTransactionsTableSchemaVersion() .getValueForTimestamp(valueAndBound.bound())) .collect(Collectors.toList()); return Iterables.getOnlyElement(presentVersionPeakValidity) == newVersion; }
private InternalSchemaMetadata installNewVersionInMapOrDefault(int newVersion, ValueAndBound<InternalSchemaMetadata> valueAndBound) { if (!valueAndBound.value().isPresent()) { log.warn("Attempting to install a new transactions schema version {}, but no past data was found," + " so we attempt to install default instead. This should normally only happen once per" + " server, and only on or around first startup since upgrading to a version of AtlasDB" + " that is aware of the transactions table. If this message persists, please contact" + " support.", SafeArg.of("newVersion", newVersion)); return InternalSchemaMetadata.defaultValue(); } log.info("Attempting to install a new transactions schema version {}, on top of schema metadata" + " that is valid up till timestamp {}.", SafeArg.of("newVersion", newVersion), SafeArg.of("oldDataValidity", valueAndBound.bound())); InternalSchemaMetadata internalSchemaMetadata = valueAndBound.value().get(); return InternalSchemaMetadata.builder() .from(internalSchemaMetadata) .timestampToTransactionsTableSchemaVersion( installNewVersionInMap( internalSchemaMetadata.timestampToTransactionsTableSchemaVersion(), valueAndBound.bound() + 1, newVersion)) .build(); }
private static TransactionService createSplitKeyTransactionService( KeyValueService keyValueService, CoordinationService<InternalSchemaMetadata> coordinationService) { TransactionSchemaManager transactionSchemaManager = new TransactionSchemaManager(coordinationService); return new PreStartHandlingTransactionService( new SplitKeyDelegatingTransactionService<>( transactionSchemaManager::getTransactionsSchemaVersion, ImmutableMap.of(1, createV1TransactionService(keyValueService)))); }
@Test public void canSwitchBetweenSchemaVersions() { assertThat(manager.tryInstallNewTransactionsSchemaVersion(2)).isTrue(); fastForwardTimestampByOneHundredMillion(); assertThat(manager.getTransactionsSchemaVersion(timestamps.getFreshTimestamp())).isEqualTo(2); assertThat(manager.tryInstallNewTransactionsSchemaVersion(1)).isTrue(); fastForwardTimestampByOneHundredMillion(); assertThat(manager.getTransactionsSchemaVersion(timestamps.getFreshTimestamp())).isEqualTo(1); }
/** * Returns the version of the transactions schema associated with the provided timestamp. * * This method may perpetuate the existing state one or more times to achieve consensus. It will repeatedly * attempt to perpetuate the existing state until a consensus for the provided timestamp argument is achieved. * * This method should only be called with timestamps that have already been given out by the timestamp service; * otherwise, achieving a consensus may take a long time. */ public int getTransactionsSchemaVersion(long timestamp) { if (timestamp < AtlasDbConstants.STARTING_TS) { throw new SafeIllegalStateException("Query attempted for timestamp {} which was never given out by the" + " timestamp service, as timestamps start at {}", SafeArg.of("queriedTimestamp", timestamp), SafeArg.of("startOfTime", AtlasDbConstants.STARTING_TS)); } Optional<Integer> possibleVersion = extractTimestampVersion(coordinationService.getValueForTimestamp(timestamp), timestamp); while (!possibleVersion.isPresent()) { CheckAndSetResult<ValueAndBound<InternalSchemaMetadata>> casResult = tryPerpetuateExistingState(); possibleVersion = extractTimestampVersion(casResult.existingValues() .stream() .filter(valueAndBound -> valueAndBound.bound() >= timestamp) .findAny(), timestamp); } return possibleVersion.get(); }
@Test public void throwsIfTryingToGetAnImpossibleTimestamp() { assertThatThrownBy(() -> manager.getTransactionsSchemaVersion(AtlasDbConstants.STARTING_TS - 3141592)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("was never given out by the timestamp service"); }
@Test public void canFailToInstallNewVersions() { TransactionSchemaManager newManager = createTransactionSchemaManager(new InMemoryTimestampService()); // Always need to seed the default value, if it's not there assertThat(newManager.tryInstallNewTransactionsSchemaVersion(5)).isFalse(); assertThat(newManager.tryInstallNewTransactionsSchemaVersion(5)).isTrue(); }
private static TransactionSchemaManager createTransactionSchemaManager(TimestampService ts) { CoordinationServiceImpl<VersionedInternalSchemaMetadata> rawService = new CoordinationServiceImpl<>( KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), new InMemoryKeyValueService(true), PtBytes.toBytes("aaa"), ts::getFreshTimestamp, VersionedInternalSchemaMetadata.class, false)); return new TransactionSchemaManager(CoordinationServices.wrapHidingVersionSerialization(rawService)); }
@Test public void newSchemaVersionsCanBeInstalledWithinOneHundredMillionTimestamps() { assertThat(manager.tryInstallNewTransactionsSchemaVersion(2)).isTrue(); fastForwardTimestampByOneHundredMillion(); assertThat(manager.getTransactionsSchemaVersion(timestamps.getFreshTimestamp())).isEqualTo(2); }
private static TransactionService createSplitKeyTransactionService( KeyValueService keyValueService, CoordinationService<InternalSchemaMetadata> coordinationService) { TransactionSchemaManager transactionSchemaManager = new TransactionSchemaManager(coordinationService); return new PreStartHandlingTransactionService( new SplitKeyDelegatingTransactionService<>( transactionSchemaManager::getTransactionsSchemaVersion, ImmutableMap.of(1, createV1TransactionService(keyValueService)))); }