Understanding Spanner’s External Consistency Guarantee with TrueTime
Learn how Google Cloud Spanner uses TrueTime to provide externally consistent transactions, see a concrete two‑client example, and understand the latency trade‑off and verification steps.
26 May 2026, 12:35 UTC

The problem: needing linearizable reads across regions without a global lock
When you run a distributed database that spans multiple continents, a simple read-after-write check can fail if the read replica lags behind the write replica. Traditional solutions either block writes while propagating data or accept the risk of seeing stale data. Applications that require strict ordering—such as financial ledgers or inventory systems—need a way to guarantee that if transaction T1 commits before T2 starts, T2 will always see T1’s effects, no matter where the participants are located.
Thesis: Spanner’s external consistency, powered by TrueTime, provides that guarantee automatically
Google Cloud Spanner assigns each transaction a timestamp derived from the TrueTime API, which reports the current time as an interval [earliest, latest] with a bounded uncertainty ε (typically a few milliseconds). By waiting until the uncertainty interval of a transaction’s timestamp has passed before committing, Spanner ensures that any later transaction will obtain a timestamp that is guaranteed to be greater. Read‑only transactions can safely read at a timestamp that is at least ε seconds old, giving a consistent snapshot without blocking writes.
How TrueTime bounds clock uncertainty
TrueTime combines GPS and atomic clocks in each datacenter to produce a time interval whose width is the current uncertainty ε. Spanner’s API exposes this interval via the TrueTime service; the client libraries retrieve it internally. The key property is that for any two events, if the latest possible time of the first is earlier than the earliest possible time of the second, then the first event truly happened before the second in real time.
Transaction timestamp assignment and the commit wait
When a read‑write transaction begins, Spanner picks a timestamp ts from the current TrueTime interval. Before committing, it executes a wait‑for‑safe‑time: it delays until the current TrueTime’s earliest time is at least ts + ε. This wait guarantees that no later transaction can obtain a timestamp less than or equal to ts. The duration of this wait is directly proportional to the current ε and is reported by the client as commit_wait_time.
Read‑only transactions and stale‑read possibility
A read‑only transaction specifies a read timestamp read_ts. Spanner allows any read_ts that is at least ε seconds old relative to the latest committed timestamp. This lets the transaction proceed without acquiring locks, because the data it reads is guaranteed to be immutable at that point. However, if the application chooses a timestamp that is too old, it will see a stale snapshot—a permissible anomaly under external consistency.
Worked example: ensuring a read sees a prior write
Assume two clients, A and B, using the Go client library. Both connect to the same Spanner instance.
// Client A: write a row
ctx := context.Background()
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
_, err := txn.Update(ctx, spanner.Statement{
SQL: "INSERT INTO Users (ID, Name) VALUES (@id, @name)",
Params: map[string]interface{}{"id": "user42", "name": "Alice"},
})
return err
})
if err != nil { log.Fatal(err) }
After receiving the commit acknowledgment, Client B starts a read‑only transaction at the latest timestamp:
// Client B: read after A’s commit
ro := client.Single().ReadOnlyTransaction()
row, err := ro.ReadRow(ctx, "Users", spanner.Key{"user42"}, []string{"Name"})
if err != nil { log.Fatal(err) }
var name string
if err := row.Column(0, &name); err != nil { log.Fatal(err) }
fmt.Printf("Name: %s\n", name) // Expects "Alice"
Because Client B’s read‑only transaction uses a timestamp that is at least ε seconds after Client A’s commit timestamp, Spanner guarantees that the row inserted by A is visible. If B had instead requested a timestamp older than the commit, it could have seen an empty result—a stale read that is still allowed.
Trade‑off: latency versus guarantee
The wait‑for‑safe‑time step adds latency proportional to the current TrueTime uncertainty. In practice this is a few milliseconds, but for transactions that involve participants far apart (e.g., us‑central1 and europe‑west1) the uncertainty can grow, increasing commit latency. High write contention can also raise abort rates because conflicting transactions may pick timestamps that force a wait‑for‑safe‑time that pushes them into each other’s uncertainty intervals.
Developers can mitigate latency by:
- Keeping transaction scopes small to reduce the chance of conflicts.
- Using read‑only transactions for workloads that tolerate slightly stale data.
- Monitoring the
commit_wait_time exported by the client libraries or via Cloud Spanner’s request‑level logging.
How to verify the guarantee in your environment
- Enable request‑level logging on a test instance (or the emulator) and look for the fields
commit_timestampandread_timestampin the logs. - Run the two‑client scenario above, ensuring Client B starts only after Client A’s commit acknowledgment.
- Confirm that the log entry for Client B’s read shows
read_timestamp ≥ commit_timestamp_of_A - ε. The value of ε can be obtained from theget_latest_timestampRPC or from theTrueTimemetric exposed by the client. - Measure the commit latency; the reported
commit_wait_timeshould match the current ε (within measurement noise).
If the observed timestamps violate the inequality, either the test is not respecting the ordering (e.g., Client B started too early) or the environment is not a genuine Spanner deployment (the emulator may simplify the timing model).
Actionable closing
Spanner’s external consistency lets you write ordinary SQL or DML and rely on the database to enforce linearizability without explicit locking or a single leader. The cost is a small, predictable latency increase tied to TrueTime’s uncertainty, and the possibility of stale reads when you deliberately request an older snapshot. By monitoring commit wait times and verifying timestamp ordering in logs, you can confirm that the guarantee holds for your workload and adjust transaction size or read‑staleness settings to meet your latency targets.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.