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
Designing Chat Presence: Online, Away, and the Lie of Instant Status
Heartbeat vs subscriptions, fan-out of presence, privacy, and why a boolean online flag does not survive a million concurrent sockets.
Consistency Models Beyond CAP: Linearizability, Causal, and Session Guarantees
'Strong vs eventual' is a cartoon. The real spectrum — linearizable, sequential, causal, session guarantees — and how to pick per operation, not per system.
Designing an API Gateway: The Front Door as a System
Authentication, routing, rate limits, transformations, and a plugin chain — in a tier that must add ~1ms and never be the outage. Envoy/Kong architecture from scratch.
Designing a CDC Pipeline: Change Data Capture from Binlog to Downstream
Dual writes are a lie; the database's own log is the truth. Log-based CDC, the snapshot-plus-stream handoff, schema evolution, and ordering guarantees that survive resharding.
Designing a Content Moderation Pipeline: Trust and Safety at Platform Scale
A billion uploads a day, a legal requirement to act in hours, and irreversible mistakes in both directions: hash matching, classifier tiers, human review queues, and appeals.
Designing a Distributed Rate Limiter: Local Buckets, Global Truth
One user, twenty gateway nodes, one limit of 100 req/s. Where does the counter live? Redis+Lua atomicity, the local-cache compromise, and failing open vs closed.
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.