A sharded Postgres with a coordinator is “horizontally-scalable SQL” only up to a point: each node is serialisable only against its own shards; cross-shard transactions areCoordinator-mediated and slow; consistent global secondary indexes are a myth. NewSQL — distributed SQL — emerged in the 2010s as the proposition: “Postgres semantics, global scale, strong consistency, linearisable writes.” Spanner, CockroachDB, TiDB, and FoundationDB are the production outcomes.
This topic is the architecture of distributed SQL: what it buys, what it costs, and when to use it.
Why “Global SQL” Is Hard
A SQL database promises strong ACID isolation. Doing that on one machine is the textbook problem; doing it across machines that can fail independently and maintain consistent state is one of the genuinely-hard research problems of the last twenty years.
The complications layer:
| Aspect | One machine | Across machines |
|---|---|---|
| Write ordering | Single lock, single log | Multi-shard log; needs total order |
| Read ordering | Snapshot at SCN | Snapshot across replicas that may lag by different amounts |
| Atomic commit | Just commit | Two-phase commit (2PC) across participants |
| Failure semantics | The whole database is up, or it isn’t | Some shards up, some down; splits and merges mid-transaction |
| Clocks | One clock | Many clocks, oscillator drift, leap seconds |
The architectural move of NewSQL is to commit each on its own, with one crucial addition: a globally-agreed time oracle that gives every write a unique timestamp ordering.
Spanner and TrueTime
Google’s Spanner (2012) is the origin. The deep insight: if you can bound the uncertainty of time globally — now ∈ [now_earliest, now_latest] with an error bound ε ≈ 7ms — you can assign timestamps that do not interleave between concurrent writes from different data centers. Distributed serialisability collapses to “wait out the uncertainty, then commit”.
The architectural primitives:
- TrueTime API —
TT.now()returns an interval[earliest, latest];TT.after(t)returns true ifthas definitely passed;TT.before(t)if it’s definitely in the future. Implemented with GPS + atomic clocks in each data center to boundε. - Paxos per shard — each shard’s writes are committed via Paxos (a majority must agree). The Paxos leader assigns a timestamp.
- Commit-wait — when a write is committed at time
t, the system waits untilTT.after(t)is true before ack-ing to the client. This guarantees no future write can come in with a smaller timestamp that violates ordering. - 2-safe replication — the Paxos group spans multiple data centers, with the leader in one; commit takes one cross-DC round trip.
The result: a globally-distributed SQL database with externally-consistent serialisable transactions, at the price of write latency = max(one cross-DC round trip, TrueTime uncertainty).
CockroachDB and TiDB
CockroachDB (Cockroach Labs, 2015) and TiDB (PingCAP, 2016) are the open-source descendants of Spanner’s architecture, both modeled around the same core ideas.
CockroachDB:
- Language: Go; storage engine: RocksDB / Pebble.
- Raft per range — each range (64MB shard of a table) is a Raft group.
- Hybrid Logical Clocks (HLC) instead of TrueTime — CockroachDB cannot assume GPS-corrected atomic clocks in every DC; HLC combines a physical clock with a Lamport-clock component to preserve causality despite drift.
- Read replicas for latency — read-only transactions can read from local replicas, accepting slightly-stale reads for non-strong reads.
- Geo-partitioning — the user can pin specific ranges to specific regions, controlling where a row’s Raft leader lives.
TiDB:
- Language: Rust + Go; storage engine: TiKV (Rust, RocksDB-based).
- Separation of storage and compute — TiKV is the distributed KV layer; TiDB nodes are stateless SQL layers; placement driver coordinates.
- Perf-dependent on SSDs — designed for NVMe-backed nodes; not typically deployed on spinning rust.
- Strong ecosystem for OLAP — TiDB + TiFlash (columnar replica) makes TiDB hybrid transactional/analytical (HTAP) out of the box.
| Property | Spanner | CockroachDB | TiDB |
|---|---|---|---|
| Time oracle | TrueTime (GPS + atomic) | HLC | TSO (single timestamp oracle, via placement driver) |
| Consensus | Paxos | Raft | Raft |
| Replication | 2-safe cross-DC | Configurable | Configurable |
| Open source | No | Yes (BSL) | Yes (Apache 2.0) |
| Migration path | Postgres-flavoured dialect | Postgres wire | MySQL wire |
Calvin and Deterministic Transactions
Calvin (Yale, 2012) is an alternative architecturally: instead of expensive 2PC and clocks, pre-determine the transaction order before they execute. All transactions enter a deterministic log; replicas run them in the same order; no locks needed because the order is decided up front.
- Pros: deterministic execution means replicas never need to coordinate beyond log replication; throughput is high.
- Cons: all transactions must be declared up front — including their read/write set — which precludes interactive transactions; the model fits workloads with predetermined transaction shapes (e.g., payments) but not ad-hoc SQL.
Calvin’s lineage lives on in FoundationDB’s transaction layer — FDB uses deterministic ordering on a single sequencer per database, with multi-region sequencers for high availability.
Linearisable vs Serializable
Two guarantees that NewSQL systems provide and that “good enough” distributed stores often don’t:
| Guarantee | What it means |
|---|---|
| Linearisability (consistency on individual operations) | Every operation appears to occur atomically at a single point between the request and response — writes are visible immediately after ack; reads see the latest committed value |
| Serialisability (transaction-level) | The result of concurrent transactions is equivalent to some serial execution of those transactions |
| Strict serialisability (both) | Equivalent to a serial execution where the order matches the real-time order in which transactions began |
Spanner achieves strict serialisability via TrueTime and commit-wait. CockroachDB and TiDB achieve close variants with HLC / TSO. The reason the distinction matters: a database can be serialisable but not linearisable (“eventual consistency across rows, but transactions appear ordered”) — which is fine for analytics, ambiguous for the “did this write happen” question.
When to Use Distributed SQL
Distributed SQL has high operational complexity and per-write cost. Choose it deliberately.
| Situation | Recommendation |
|---|---|
| Schema is sharded by tenant; cross-shard transactions rare | Sharded Postgres / MySQL — distributed SQL’s guarantees are overkill |
| Global application with strict consistency requirements | CockroachDB / Spanner / TiDB |
| Strong consistency + hybrid analytical | TiDB with TiFlash |
| Multi-region active-active writes needed, SQL semantics | CockroachDB with geo-partitioning |
| Workload is key-value with cross-key transactions | FoundationDB (Calvin lineage) |
The honest take: NewSQL is the right tool for a small fraction of applications. The default for most teams is still a single Postgres at moderate scale (~1TB, ~10K TPS), with sharding as a deliberate next step when scaling forces it.
Practice Trajectory
- Pick a workload you operate. List three properties of it that would push you toward distributed SQL and three that would push you away.
- Compare TrueTime and HLC: in two regions with 50ms network round-trip, what are the achievable latency properties? Which technique is right without atomic clocks?
- Trace a single-row write through Spanner through the commit-wait model. Identify where the TrueTime uncertainty interval lives in the latency budget.
- Configure CockroachDB or TiDB locally with three-node or single-node; execute a multi-statement transaction; forcefully kill one node mid-way; verify the database’s commit protocol handles it correctly.
- Convert a small schema from Postgres to CockroachDB or TiDB. Note what changes (GeoPartition, replica placement); note what doesn’t (SQL semantics).
When It’s the Right Tool
| Situation | Takeaway |
|---|---|
| Single-shard SQL is sufficient | Stay — distributed SQL has cost and complexity most applications don’t need |
| Cross-region, low-latency, strong consistency | CockroachDB with geo-partitioning |
| Global adtech / financial write consistency | Spanner |
| Hybrid analytical + transactional (HTAP) | TiDB + TiFlash (or Snowflake + a transactional store) |
| Deterministic, pre-declared transaction order | FoundationDB / Calvin-lineage systems |