Cloud Spanner for Global Consistency: When the Latency Trade-Off Makes Sense
Decide whether Cloud Spanner's external consistency justifies its cross-region write latency and node-hour cost compared with sharded PostgreSQL or MySQL.
18 Aug 2026, 19:22 UTC

The problem: strong consistency without a global lock manager
Your application serves users in multiple regions and needs read-your-writes consistency across them. A sharded PostgreSQL or MySQL topology can scale writes, but keeping replicas in sync usually means application-level reconciliation, asynchronous lag, or a single primary that becomes a cross-region bottleneck. Google Cloud Spanner promises externally consistent transactions without a single global lock manager, using TrueTime—Google’s GPS and atomic-clock time API—and Paxos groups. The engineering question is not whether Spanner is consistent; it is whether the latency and cost of that consistency fit your workload.
The takeaway: choose Spanner when external consistency across regions is a hard requirement and your write volume and budget can absorb cross-region quorum latency; otherwise a regionally scoped PostgreSQL or MySQL deployment is usually cheaper and faster.
How reads and writes actually travel
Spanner splits data into ranges called splits, each served by a Paxos group across zones. A split leader handles read-write transactions and must renew its leader lease, typically every ten seconds. Read-write transactions contact that leader, so a commit in a multi-region instance waits for a majority quorum across regions. Physics sets a floor: a US-central1 to EU round-trip adds roughly eighty milliseconds minimum, and write latency equals the slowest majority round-trip.
Read-only transactions behave differently. A transaction at a specific timestamp can be served by any up-to-date replica, and bounded-staleness reads let you trade freshness for latency. A strong read may still cross regions, but a read with max staleness of a few seconds can often be served locally at roughly five to ten milliseconds.
Worked example: bounded-staleness read versus strong read
Run this from a shell with the Google Cloud CLI installed and the spanner.databaseUser role on the target database. Replace INSTANCE_ID, DATABASE_ID, and PROJECT_ID with your own values.
gcloud spanner databases execute-sql DATABASE_ID \
--instance=INSTANCE_ID \
--project=PROJECT_ID \
--sql="SELECT order_id, status FROM orders WHERE customer_id = 'CUST-12345';"
That executes a strong read by default. To compare latency, issue the same query through your client library with a read-only transaction and a ten-second max staleness bound. The client SDKs expose this as max_staleness or equivalent; the exact syntax varies by language, so verify against your driver version before assuming behavior.
Measure both calls with client-side timing, not just server-side metrics. Risk: a stale read can return data that predates a recent write, which breaks read-your-writes expectations if your application assumes immediate visibility.
Schema design decisions that determine latency
Interleaved tables physically co-locate child rows with parent rows using the primary key prefix. A hierarchy such as Customer → Orders → LineItems becomes a local range scan instead of a distributed join. This is one of Spanner’s strongest latency levers, but only if your access patterns follow the parent-child prefix.
Primary key choice is equally important. Monotonically increasing keys, such as a timestamp default or sequential integer, concentrate writes on a single split and can saturate it. Use UUIDv4 or bit-reversed sequences to distribute writes across splits. You can observe split distribution in Cloud Monitoring using split_count and hot-split metrics after a production-scale load test.
Schema changes are online and non-blocking. Adding a column is instant; backfilling a computed column or building an index runs asynchronously. Track progress through INFORMATION_SCHEMA.INDEXES and the SPANNER_STATE column. Do not assume an index build blocks writes, but do verify the final state before retiring a migration script.
Limitations and what the emulator cannot tell you
- Cost: a minimal three-node regional instance starts around ninety cents per node per hour at list pricing, roughly two thousand three hundred dollars per month before storage and egress. Verify current pricing in the Google Cloud pricing calculator before budgeting; list prices change.
- No SELECT FOR UPDATE: Spanner uses optimistic concurrency. Long-running read-write transactions abort more often under contention, so design retries and idempotent mutations.
- Partitioned DML runs as independent transactions per split. A bulk update can partially succeed, leaving intermediate state that your application must reconcile.
- The local emulator validates SQL semantics only. It does not simulate TrueTime clock uncertainty, Paxos leader leases, or cross-region latency. Integration tests against the emulator cannot predict commit latency or split-leader failover behavior.
Practical verification before you commit
- Create a three-node regional instance in
us-central1through the Cloud Console. - Run a read-write transaction and inspect
spanner.googleapis.com/transaction/commit_latenciesin Cloud Monitoring. - Insert ten thousand rows per second with sequential keys, then repeat with UUIDv4 keys, and compare split saturation metrics.
- Build an index on a multi-million-row table and poll
INFORMATION_SCHEMA.INDEXESuntilSPANNER_STATEreports completion.
If your write volume can be absorbed by a single region and your reads tolerate bounded staleness, Spanner’s consistency model may be unnecessary. If you need externally consistent reads across continents without building your own replication layer, the latency and node-hour cost are the price of that guarantee.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.