241 lines
8.8 KiB
Go
241 lines
8.8 KiB
Go
/*
|
|
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 spanner
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
pbd "github.com/golang/protobuf/ptypes/duration"
|
|
pbt "github.com/golang/protobuf/ptypes/timestamp"
|
|
sppb "google.golang.org/genproto/googleapis/spanner/v1"
|
|
)
|
|
|
|
// timestampBoundType specifies the timestamp bound mode.
|
|
type timestampBoundType int
|
|
|
|
const (
|
|
strong timestampBoundType = iota // strong reads
|
|
exactStaleness // read with exact staleness
|
|
maxStaleness // read with max staleness
|
|
minReadTimestamp // read with min freshness
|
|
readTimestamp // read data at exact timestamp
|
|
)
|
|
|
|
// TimestampBound defines how Cloud Spanner will choose a timestamp for a single
|
|
// read/query or read-only transaction.
|
|
//
|
|
// There are three types of timestamp bound: strong, bounded staleness and exact
|
|
// staleness. Strong is the default.
|
|
//
|
|
// 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. A TimestampBound
|
|
// can be specified when creating transactions, see the documentation of
|
|
// spanner.Client for an example.
|
|
//
|
|
// 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 StrongRead to create a bound of this type.
|
|
//
|
|
// Exact staleness
|
|
//
|
|
// An exact staleness timestamp bound executes 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 ReadTimestamp and ExactStaleness 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 this 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 MinReadTimestamp and MaxStaleness 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 timestamps become too old while
|
|
// executing. Reads and SQL queries with too-old read timestamps fail with the
|
|
// error ErrorCode.FAILED_PRECONDITION.
|
|
type TimestampBound struct {
|
|
mode timestampBoundType
|
|
d time.Duration
|
|
t time.Time
|
|
}
|
|
|
|
// StrongRead returns a TimestampBound that will perform reads and queries at a
|
|
// timestamp where all previously committed transactions are visible.
|
|
func StrongRead() TimestampBound {
|
|
return TimestampBound{mode: strong}
|
|
}
|
|
|
|
// ExactStaleness returns a TimestampBound that will perform reads and queries
|
|
// at an exact staleness.
|
|
func ExactStaleness(d time.Duration) TimestampBound {
|
|
return TimestampBound{
|
|
mode: exactStaleness,
|
|
d: d,
|
|
}
|
|
}
|
|
|
|
// MaxStaleness returns a TimestampBound that will perform reads and queries at
|
|
// a time chosen to be at most "d" stale.
|
|
func MaxStaleness(d time.Duration) TimestampBound {
|
|
return TimestampBound{
|
|
mode: maxStaleness,
|
|
d: d,
|
|
}
|
|
}
|
|
|
|
// MinReadTimestamp returns a TimestampBound that bound that will perform reads
|
|
// and queries at a time chosen to be at least "t".
|
|
func MinReadTimestamp(t time.Time) TimestampBound {
|
|
return TimestampBound{
|
|
mode: minReadTimestamp,
|
|
t: t,
|
|
}
|
|
}
|
|
|
|
// ReadTimestamp returns a TimestampBound that will peform reads and queries at
|
|
// the given time.
|
|
func ReadTimestamp(t time.Time) TimestampBound {
|
|
return TimestampBound{
|
|
mode: readTimestamp,
|
|
t: t,
|
|
}
|
|
}
|
|
|
|
func (tb TimestampBound) String() string {
|
|
switch tb.mode {
|
|
case strong:
|
|
return fmt.Sprintf("(strong)")
|
|
case exactStaleness:
|
|
return fmt.Sprintf("(exactStaleness: %s)", tb.d)
|
|
case maxStaleness:
|
|
return fmt.Sprintf("(maxStaleness: %s)", tb.d)
|
|
case minReadTimestamp:
|
|
return fmt.Sprintf("(minReadTimestamp: %s)", tb.t)
|
|
case readTimestamp:
|
|
return fmt.Sprintf("(readTimestamp: %s)", tb.t)
|
|
default:
|
|
return fmt.Sprintf("{mode=%v, d=%v, t=%v}", tb.mode, tb.d, tb.t)
|
|
}
|
|
}
|
|
|
|
// durationProto takes a time.Duration and converts it into pdb.Duration for
|
|
// calling gRPC APIs.
|
|
func durationProto(d time.Duration) *pbd.Duration {
|
|
n := d.Nanoseconds()
|
|
return &pbd.Duration{
|
|
Seconds: n / int64(time.Second),
|
|
Nanos: int32(n % int64(time.Second)),
|
|
}
|
|
}
|
|
|
|
// timestampProto takes a time.Time and converts it into pbt.Timestamp for calling
|
|
// gRPC APIs.
|
|
func timestampProto(t time.Time) *pbt.Timestamp {
|
|
return &pbt.Timestamp{
|
|
Seconds: t.Unix(),
|
|
Nanos: int32(t.Nanosecond()),
|
|
}
|
|
}
|
|
|
|
// buildTransactionOptionsReadOnly converts a spanner.TimestampBound into a sppb.TransactionOptions_ReadOnly
|
|
// transaction option, which is then used in transactional reads.
|
|
func buildTransactionOptionsReadOnly(tb TimestampBound, returnReadTimestamp bool) *sppb.TransactionOptions_ReadOnly {
|
|
pb := &sppb.TransactionOptions_ReadOnly{
|
|
ReturnReadTimestamp: returnReadTimestamp,
|
|
}
|
|
switch tb.mode {
|
|
case strong:
|
|
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_Strong{
|
|
Strong: true,
|
|
}
|
|
case exactStaleness:
|
|
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_ExactStaleness{
|
|
ExactStaleness: durationProto(tb.d),
|
|
}
|
|
case maxStaleness:
|
|
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_MaxStaleness{
|
|
MaxStaleness: durationProto(tb.d),
|
|
}
|
|
case minReadTimestamp:
|
|
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_MinReadTimestamp{
|
|
MinReadTimestamp: timestampProto(tb.t),
|
|
}
|
|
case readTimestamp:
|
|
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_ReadTimestamp{
|
|
ReadTimestamp: timestampProto(tb.t),
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("buildTransactionOptionsReadOnly(%v,%v)", tb, returnReadTimestamp))
|
|
}
|
|
return pb
|
|
}
|