private ValueAndBound<T> chooseValueWithGreaterBound( ValueAndBound<T> currentValue, ValueAndBound<T> nextValue) { if (currentValue.bound() > nextValue.bound()) { return currentValue; } log.info("Updating cached coordination value to a new value, valid till {}", SafeArg.of("newBound", nextValue.bound())); return nextValue; }
@Override public Optional<ValueAndBound<T>> getValueForTimestamp(long timestamp) { ValueAndBound<T> cachedReference = cache.get(); if (cachedReference.bound() < timestamp) { return readLatestValueFromStore() .filter(valueAndBound -> valueAndBound.bound() >= timestamp); } return Optional.of(cachedReference); }
private static <F, T> Function<ValueAndBound<F>, ValueAndBound<T>> preservingBounds(Function<F, T> base) { return fromValueAndBound -> ValueAndBound.of(fromValueAndBound.value().map(base), fromValueAndBound.bound()); } }
/** * Registers a gauge which tracks the eventual transactions schema version - that is, at the end of the current * period of validity for the bound, what the metadata says the transactions schema version should be. * * @param metricsManager metrics manager to register the gauge on * @param metadataCoordinationService metadata coordination service that should be tracked */ private static void registerEventualTransactionsSchemaVersionMetric(MetricsManager metricsManager, CoordinationService<InternalSchemaMetadata> metadataCoordinationService) { metricsManager.registerMetric( MetadataCoordinationServiceMetrics.class, AtlasDbMetricNames.COORDINATION_EVENTUAL_TRANSACTIONS_SCHEMA_VERSION, TrackerUtils.createCachingExceptionHandlingGauge( log, Clock.defaultClock(), AtlasDbMetricNames.COORDINATION_EVENTUAL_TRANSACTIONS_SCHEMA_VERSION, () -> { Optional<ValueAndBound<InternalSchemaMetadata>> latestValue = metadataCoordinationService.getLastKnownLocalValue(); return latestValue .map(ValueAndBound::value) .flatMap(Function.identity()) .map(InternalSchemaMetadata::timestampToTransactionsTableSchemaVersion) .map(timestampMap -> timestampMap.getValueForTimestamp(latestValue.get().bound())) .orElse(null); })); }
/** * 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; }
/** * 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(); }
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 canApplyMultipleTransformations() { coordinationStore.transformAgreedValue(unused -> VALUE_1); ValueAndBound<String> firstValueAndBound = coordinationStore.getAgreedValue().get(); coordinationStore.transformAgreedValue(unused -> VALUE_2); ValueAndBound<String> secondValueAndBound = coordinationStore.getAgreedValue().get(); assertThat(firstValueAndBound.value()).contains(VALUE_1); assertThat(secondValueAndBound.value()).contains(VALUE_2); assertThat(firstValueAndBound.bound()).isLessThan(secondValueAndBound.bound()); }
@Test public void valuePreservingTransformationsAdvanceTheBound() { coordinationStore.transformAgreedValue(unused -> VALUE_1); ValueAndBound<String> firstValueAndBound = coordinationStore.getAgreedValue().get(); coordinationStore.transformAgreedValue(VALUE_PRESERVING_FUNCTION); ValueAndBound<String> secondValueAndBound = coordinationStore.getAgreedValue().get(); assertThat(firstValueAndBound.value()).contains(VALUE_1); assertThat(secondValueAndBound.value()).contains(VALUE_1); assertThat(firstValueAndBound.bound()).isLessThan(secondValueAndBound.bound()); }
@Test public void canStoreAndRetrieveValues() { CheckAndSetResult<ValueAndBound<String>> casResult = coordinationStore.transformAgreedValue(unused -> VALUE_1); assertThat(casResult.successful()).isTrue(); assertThat(Iterables.getOnlyElement(casResult.existingValues()).value()).contains(VALUE_1); assertThat(coordinationStore.getAgreedValue()).hasValueSatisfying( valueAndBound -> { assertThat(valueAndBound.value()).contains(VALUE_1); assertThat(valueAndBound.bound()).isGreaterThanOrEqualTo(0); }); }
private ValueAndBound<T> chooseValueWithGreaterBound( ValueAndBound<T> currentValue, ValueAndBound<T> nextValue) { if (currentValue.bound() > nextValue.bound()) { return currentValue; } log.info("Updating cached coordination value to a new value, valid till {}", SafeArg.of("newBound", nextValue.bound())); return nextValue; }
@Override public Optional<ValueAndBound<T>> getValueForTimestamp(long timestamp) { ValueAndBound<T> cachedReference = cache.get(); if (cachedReference.bound() < timestamp) { return readLatestValueFromStore() .filter(valueAndBound -> valueAndBound.bound() >= timestamp); } return Optional.of(cachedReference); }
private static <F, T> Function<ValueAndBound<F>, ValueAndBound<T>> preservingBounds(Function<F, T> base) { return fromValueAndBound -> ValueAndBound.of(fromValueAndBound.value().map(base), fromValueAndBound.bound()); } }
/** * Registers a gauge which tracks the eventual transactions schema version - that is, at the end of the current * period of validity for the bound, what the metadata says the transactions schema version should be. * * @param metricsManager metrics manager to register the gauge on * @param metadataCoordinationService metadata coordination service that should be tracked */ private static void registerEventualTransactionsSchemaVersionMetric(MetricsManager metricsManager, CoordinationService<InternalSchemaMetadata> metadataCoordinationService) { metricsManager.registerMetric( MetadataCoordinationServiceMetrics.class, AtlasDbMetricNames.COORDINATION_EVENTUAL_TRANSACTIONS_SCHEMA_VERSION, TrackerUtils.createCachingExceptionHandlingGauge( log, Clock.defaultClock(), AtlasDbMetricNames.COORDINATION_EVENTUAL_TRANSACTIONS_SCHEMA_VERSION, () -> { Optional<ValueAndBound<InternalSchemaMetadata>> latestValue = metadataCoordinationService.getLastKnownLocalValue(); return latestValue .map(ValueAndBound::value) .flatMap(Function.identity()) .map(InternalSchemaMetadata::timestampToTransactionsTableSchemaVersion) .map(timestampMap -> timestampMap.getValueForTimestamp(latestValue.get().bound())) .orElse(null); })); }
/** * 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; }
/** * 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(); }
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(); }