Designing a Spanner-Style Global Database: TrueTime and External Consistency
Distributed systems folklore says you cannot trust clocks, so you cannot order events across machines without communication. Spanner's heresy was to trust clocks a bounded amount: if every node knows its clock is wrong by at most ε, you can turn wall time back into a global ordering primitive — by deliberately waiting out the uncertainty. That one idea, TrueTime, is why Spanner can promise something almost no other planet-scale database does: external consistency.
What external consistency buys you
External consistency (strict serializability): if transaction T1 commits before T2 starts — in real time, as observed by the outside world — then T2 sees T1's effects, and every consistent read agrees. Eventual consistency cannot promise this; even many "strongly consistent" systems only promise it per key or per shard. For cross-shard invariants — move money between accounts on different continents, then audit the total — you need it globally.
The building blocks
Data layout: keyspace split into tablets; each tablet replicated across zones/regions by its own Paxos group (typically 5 replicas). Writes go through the group's leader; the leader holds a lease. This is the standard replicated-state-machine layer — Raft would do the same job.
Single-group transactions commit in one Paxos round. The interesting machinery is for transactions spanning groups:
Cross-group transactions: two-phase commit, but with every 2PC participant and the coordinator being a Paxos group, not a single node. Classic 2PC's fatal flaw — coordinator dies and locks hang forever — disappears because the coordinator is itself replicated and cannot "die" short of losing a quorum. 2PC's blocking problem was never about the protocol; it was about non-replicated participants. Say that sentence in an interview and you have earned the follow-up.
Concurrency control: two-phase locking for read-write transactions; lock-free snapshot reads for read-only transactions at a chosen timestamp.
TrueTime: clocks with error bars
Google equips datacenters with GPS receivers and atomic clocks. The TrueTime API never returns a timestamp — it returns an interval:
TT.now() -> [earliest, latest] // guaranteed: true time is inside
ε = (latest - earliest) / 2 // typically 1-7ms
The commit protocol uses it in two places:
- Timestamping: the coordinator assigns commit timestamp
s = TT.now().latest. - Commit-wait: the leader holds the commit (locks held, result unreleased) until
TT.now().earliest > s— i.e., until every clock in the fleet agrees thatsis in the past.
def commit(txn):
s = TT.now().latest # assign timestamp
paxos_replicate(txn, s) # happens concurrently with the wait
while TT.now().earliest <= s:
sleep_briefly() # commit-wait: ~2ε, overlapped with replication
release_locks_and_ack(txn)
Why this yields external consistency: if T2 starts after T1 committed (real time), then T1's commit-wait guarantees T1's timestamp is already "in the past" everywhere — so T2's timestamp is strictly greater. Timestamp order now matches real-time order, globally, with no cross-shard coordination at read time. The cost is explicit: every read-write transaction pays ~2ε of latency. Keep ε small and the tax is a few milliseconds — which is why the atomic clocks matter; they are not decoration, they are the latency budget.
Snapshot reads are the payoff: a read-only transaction picks timestamp t, reads from the nearest replica that is caught up past t (each replica tracks a "safe time"), touches no locks, blocks no writers, and is still strictly serializable. Analytics against the live OLTP database without a replica lag apology.
Without atomic clocks: the alternatives
Interviewers will ask "and if I don't have GPS clocks?" The landscape:
| System | Ordering trick | Trade-off |
|---|---|---|
| Spanner | TrueTime + commit-wait | Hardware; ~2ε write latency |
| CockroachDB | Hybrid logical clocks + NTP bound | Reads may hit "uncertainty restarts" instead of waiting |
| Calvin/FaunaDB | Pre-order all txns via a global log | Deterministic, but interactive txns are awkward |
| DynamoDB/Cassandra | Don't promise it | Per-item or eventual consistency only |
CockroachDB is the instructive contrast: same architecture (ranges + Raft + 2PC), no clock hardware, so it moves the uncertainty cost from writers (commit-wait) to readers (retry when a value's timestamp falls inside your uncertainty window). Same physics, different victim.
Design summary
Shard into tablets → replicate each via Paxos → 2PC across replicated groups for cross-shard writes → timestamps from TrueTime → commit-wait to make timestamp order equal real-time order → snapshot reads at a timestamp for lock-free consistent reads. The deep insight to land: Spanner did not beat CAP — during a partition, the minority side is unavailable. It made C so cheap to consume (wait a few milliseconds, bounded by clock hardware) that choosing it became rational at global scale.
Keep reading
Write Skew: The Anomaly Snapshot Isolation Does Not Catch
Two transactions each read what the other writes, both commit, and an invariant dies. Snapshot isolation allows write skew — here is how to close the hole.
Designing a Shopping Cart: Price Snapshots, Merge, and No Inventory Hold
A cart is a per-user document, not a reservation. What to store on the line, how to merge an anonymous cart at login, and when the price is allowed to change.
Designing Snowflake-Style IDs: Ordering, Workers, and Clock Rollback
A 64-bit id from a timestamp, a worker number, and a per-millisecond sequence. How many you can issue, why the clock must not step backwards, and why JavaScript cannot hold one.
PostgreSQL 19 on October 29: WAIT for Read-Your-Writes, REPACK Without the Lock, and What Got Pulled
PostgreSQL 19 GA is scheduled for October 29, 2026. The WAIT command gives standbys read-your-writes, REPACK CONCURRENTLY replaces VACUUM FULL and CLUSTER, autovacuum goes parallel, and SQL/PGQ, online checksums, and FOR PORTION OF were reverted in Beta 4.
Idempotency Keys for Write APIs
How to make POST safe to retry: the key, the stored response, the race between two identical requests, and the expiry that forgets too soon.
Read-Your-Writes: The User Just Saved and the Read Replica Does Not Know
Why a write to the primary followed by a read from a replica shows stale data, and the practical ways to pin the next read without sending all traffic to the primary.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.