nl.topicus.jdbc.shaded.com.google.cloud.spanner.TimestampBound Maven / Gradle / Ivy
Show all versions of spanner-jdbc Show documentation
/*
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nl.topicus.jdbc.shaded.com.google.cloud.spanner;
import static nl.topicus.jdbc.shaded.com.google.common.base.Preconditions.checkArgument;
import static nl.topicus.jdbc.shaded.com.google.common.base.Preconditions.checkNotNull;
import static nl.topicus.jdbc.shaded.com.google.common.base.Preconditions.checkState;
import nl.topicus.jdbc.shaded.com.google.cloud.Timestamp;
import nl.topicus.jdbc.shaded.com.google.protobuf.Duration;
import nl.topicus.jdbc.shaded.com.google.protobuf.util.Durations;
import nl.topicus.jdbc.shaded.com.google.spanner.v1.TransactionOptions;
import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Defines how Cloud Spanner will choose a timestamp for a read-only transaction or a single
* read/query.
*
* The types of timestamp bound are:
*
*
* - Strong (the default).
*
- Bounded staleness.
*
- Exact staleness.
*
*
* If the Cloud Spanner database to be read is geographically distributed, stale read-only
* transactions can execute more quickly than strong or read-write transactions, because they are
* able to execute far from the leader replica.
*
*
Each type of timestamp bound is discussed in detail below.
*
*
Strong reads
*
* Strong reads are guaranteed to see the effects of all transactions that have committed before
* the start of the read. Furthermore, all rows yielded by a single read are consistent with each
* other - if any part of the read observes a transaction, all parts of the read see the
* transaction.
*
*
Strong reads are not repeatable: two consecutive strong read-only transactions might return
* inconsistent results if there are concurrent writes. If consistency across reads is required, the
* reads should be executed within a transaction or at an exact read timestamp.
*
*
Use {@link #strong()} to create a bound of this type.
*
*
Exact Staleness
*
* These timestamp bounds execute reads at a user-specified timestamp. Reads at a timestamp are
* guaranteed to see a consistent prefix of the global transaction history: they observe
* modifications done by all transactions with a commit timestamp less than or equal to the read
* timestamp, and observe none of the modifications done by transactions with a larger commit
* timestamp. They will block until all conflicting transactions that may be assigned commit
* timestamps less than or equal to the read timestamp have finished.
*
*
The timestamp can either be expressed as an absolute Cloud Spanner commit timestamp or a
* staleness relative to the current time.
*
*
These modes do not require a "negotiation phase" to pick a timestamp. As a result, they
* execute slightly faster than the equivalent boundedly stale concurrency modes. On the other hand,
* boundedly stale reads usually return fresher results.
*
*
Use {@link #ofReadTimestamp(Timestamp)} and {@link #ofExactStaleness(long, TimeUnit)} to
* create a bound of this type.
*
*
Bounded Staleness
*
* Bounded staleness modes allow Cloud Spanner to pick the read timestamp, subject to a
* user-provided staleness bound. Cloud Spanner chooses the newest timestamp within the staleness
* bound that allows execution of the reads at the closest available replica without blocking.
*
*
All rows yielded are consistent with each other -- if any part of the read observes a
* transaction, all parts of the read see the transaction. Boundedly stale reads are not repeatable:
* two stale reads, even if they use the same staleness bound, can execute at different timestamps
* and thus return inconsistent results.
*
*
Boundedly stale reads execute in two phases: the first phase negotiates a timestamp among all
* replicas needed to serve the read. In the second phase, reads are executed at the negotiated
* timestamp.
*
*
As a result of the two phase execution, bounded staleness reads are usually a little slower
* than comparable exact staleness reads. However, they are typically able to return fresher
* results, and are more likely to execute at the closest replica.
*
*
Because the timestamp negotiation requires up-front knowledge of which rows will be read, it
* can only be used with single-use reads and single-use read-only transactions.
*
*
Use {@link #ofMinReadTimestamp(Timestamp)} and {@link #ofMaxStaleness(long, TimeUnit)} to
* create a bound of this type.
*
*
Old Read Timestamps and Garbage Collection
*
* Cloud Spanner continuously garbage collects deleted and overwritten data in the background to
* reclaim storage space. This process is known as "version GC". By default, version GC reclaims
* versions after they are four hours old. Because of this, Cloud Spanner cannot perform reads at
* read timestamps more than four hours in the past. This restriction also applies to in-progress
* reads and/or SQL queries whose timestamp become too old while executing. Reads and SQL queries
* with too-old read timestamps fail with the error {@link ErrorCode#FAILED_PRECONDITION}.
*
* @see Session#singleUse(TimestampBound)
* @see Session#singleUseReadOnlyTransaction(TimestampBound)
* @see Session#readOnlyTransaction(TimestampBound)
*/
public final class TimestampBound implements Serializable {
private static final TimestampBound STRONG_BOUND = new TimestampBound(Mode.STRONG, null, null);
private static final TransactionOptions.ReadOnly STRONG_PROTO =
TransactionOptions.ReadOnly.newBuilder().setStrong(true).build();
private static final long serialVersionUID = 9194565742651275731L;
private final Mode mode;
private final Timestamp timestamp;
private final Duration staleness;
private TimestampBound(Mode mode, Timestamp timestamp, Duration staleness) {
this.mode = mode;
this.timestamp = timestamp;
this.staleness = staleness;
}
/**
* Returns a timestamp bound that will perform reads and queries at a timestamp where all
* previously committed transactions are visible.
*/
public static TimestampBound strong() {
return STRONG_BOUND;
}
/**
* Returns a timestamp bound that will perform reads and queries at the given timestamp. Unlike
* other modes, reads at a specific timestamp are repeatable; the same read at the same timestamp
* always returns the same data. If the timestamp is in the future, the read will block until the
* specified timestamp, modulo the read's deadline.
*
*
This mode is useful for large scale consistent reads such as mapreduces, or for coordinating
* many reads against a consistent snapshot of the data.
*/
public static TimestampBound ofReadTimestamp(Timestamp timestamp) {
return new TimestampBound(Mode.READ_TIMESTAMP, checkNotNull(timestamp), null);
}
/**
* Returns a timestamp bound that will perform reads and queries at a timestamp chosen to be at
* least {@code timestamp}. This is useful for requesting fresher data than some previous read, or
* data that is fresh enough to observe the effects of some previously committed transaction whose
* timestamp is known.
*
*
Note that this option can only be used in single-use reads and single-use read-only
* transactions.
*/
public static TimestampBound ofMinReadTimestamp(Timestamp timestamp) {
return new TimestampBound(Mode.MIN_READ_TIMESTAMP, checkNotNull(timestamp), null);
}
/**
* Returns a timestamp bound that will perform reads and queries at an exact staleness. The
* timestamp is chosen soon after the read is started.
*
*
Guarantees that all writes that have committed more than the specified number of seconds ago
* are visible. Because Cloud Spanner chooses the exact timestamp, this mode works even if the
* client's local clock is substantially skewed from Cloud Spanner commit timestamps.
*
*
Useful for reading at nearby replicas without the distributed timestamp negotiation overhead
* of {@link #ofMaxStaleness(long, TimeUnit)}.
*/
public static TimestampBound ofExactStaleness(long num, TimeUnit units) {
checkStaleness(num);
return new TimestampBound(Mode.EXACT_STALENESS, null, createDuration(num, units));
}
/**
* Returns a timestamp bound that will perform reads and queries at a timestamp chosen to be at
* most {@code num units} stale. This guarantees that all writes that have committed more than the
* specified number of seconds ago are visible. Because Cloud Spanner chooses the exact timestamp,
* this mode works even if the client's local clock is substantially skewed from Cloud Spanner
* commit timestamps.
*
*
Useful for reading the freshest data available at a nearby replica, while bounding the
* possible staleness if the local replica has fallen behind.
*
*
Note that this option can only be used in single-use reads and single-use read-only
* transactions.
*/
public static TimestampBound ofMaxStaleness(long num, TimeUnit units) {
checkStaleness(num);
return new TimestampBound(Mode.MAX_STALENESS, null, createDuration(num, units));
}
/**
* The type of timestamp bound. See the class documentation of {@link TimestampBound} for a
* detailed discussion of the various modes.
*/
public enum Mode {
STRONG,
READ_TIMESTAMP,
MIN_READ_TIMESTAMP,
EXACT_STALENESS,
MAX_STALENESS,
}
public Mode getMode() {
return mode;
}
/**
* Returns the timestamp at which reads will be performed.
*
* @throws IllegalStateException if {@code mode() != Mode.EXACT_TIMESTAMP}
* @see #ofReadTimestamp(Timestamp)
*/
public Timestamp getReadTimestamp() {
checkMode(Mode.READ_TIMESTAMP);
return timestamp;
}
/**
* Returns the minimum timestamp at which reads will be performed.
*
* @throws IllegalStateException if {@code mode() != Mode.MIN_READ_TIMESTAMP}
* @see #ofMinReadTimestamp(Timestamp)
*/
public Timestamp getMinReadTimestamp() {
checkMode(Mode.MIN_READ_TIMESTAMP);
return timestamp;
}
/**
* Returns the exact staleness, in the units requested, at which reads will be performed.
*
* @throws IllegalStateException if {@code mode() != Mode.EXACT_STALENESS}
* @see #ofExactStaleness(long, java.util.concurrent.TimeUnit)
*/
public long getExactStaleness(TimeUnit units) {
checkMode(Mode.EXACT_STALENESS);
return durationToUnits(staleness, units);
}
/**
* Returns the maximum staleness, in the units requested, at which reads will be performed.
*
* @throws IllegalStateException if {@code mode() != Mode.MAX_STALENESS}
* @see #ofMaxStaleness(long, java.util.concurrent.TimeUnit)
*/
public long getMaxStaleness(TimeUnit units) {
checkMode(Mode.MAX_STALENESS);
return durationToUnits(staleness, units);
}
StringBuilder toString(StringBuilder b) {
// TODO(user): Convert all internal toString(StringBuilder) methods to return StringBuilder.
switch (mode) {
case STRONG:
return b.append("strong");
case READ_TIMESTAMP:
return b.append("exact_timestamp: ").append(timestamp.toString());
case MIN_READ_TIMESTAMP:
return b.append("min_read_timestamp: ").append(timestamp.toString());
case EXACT_STALENESS:
return b.append("exact_staleness: ").append(Durations.toString(staleness));
case MAX_STALENESS:
return b.append("max_staleness: ").append(Durations.toString(staleness));
default:
throw new AssertionError("Unexpected mode: " + mode);
}
}
@Override
public String toString() {
return toString(new StringBuilder()).toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimestampBound that = (TimestampBound) o;
return mode == that.mode
&& Objects.equals(staleness, that.staleness)
&& Objects.equals(timestamp, that.timestamp);
}
@Override
public int hashCode() {
return Objects.hash(mode, timestamp, staleness);
}
TransactionOptions.ReadOnly toProto() {
// TODO(user): Use full proto as the internal representation if we eliminate Timestamp.
if (mode == Mode.STRONG) {
return STRONG_PROTO;
}
TransactionOptions.ReadOnly.Builder builder = TransactionOptions.ReadOnly.newBuilder();
applyToBuilder(builder);
return builder.build();
}
TransactionOptions.ReadOnly.Builder applyToBuilder(TransactionOptions.ReadOnly.Builder builder) {
switch (mode) {
case STRONG:
return builder.setStrong(true);
case READ_TIMESTAMP:
return builder.setReadTimestamp(timestamp.toProto());
case MIN_READ_TIMESTAMP:
return builder.setMinReadTimestamp(timestamp.toProto());
case EXACT_STALENESS:
return builder.setExactStaleness(staleness);
case MAX_STALENESS:
return builder.setMaxStaleness(staleness);
default:
throw new AssertionError("Unexpected mode: " + mode);
}
}
private static void checkStaleness(double num) {
checkArgument(num >= 0, "Staleness cannot be negative");
}
private void checkMode(Mode requiredMode) {
checkState(mode == requiredMode, "Invalid call for mode %s", mode);
}
private static Duration createDuration(long num, TimeUnit units) {
switch (units) {
case NANOSECONDS:
return Durations.fromNanos(num);
case MICROSECONDS:
return Durations.fromMicros(num);
default:
return Durations.fromMillis(units.toMillis(num));
}
}
private static long durationToUnits(Duration duration, TimeUnit units) {
// TODO(user): Handle overflow.
switch (units) {
case NANOSECONDS:
return Durations.toNanos(duration);
case MICROSECONDS:
return Durations.toMicros(duration);
default:
return units.convert(Durations.toMillis(duration), TimeUnit.MILLISECONDS);
}
}
}