Google Cloud Spanner’s Write Lock Pricing Forces a PostgreSQL Team to Relearn Transaction Visibility
When a mid-stage startup decided to migrate its PostgreSQL-backed analytics platform to Google Cloud Spanner in early 2022, the team expected a straightforward scaling story. Spanner promised global strong consistency, automatic sharding, and no more manual partition management. What they did not anticipate was a new line item on their monthly cloud bill: write-lock operations. Each row-level lock during a read-write transaction cost a small but nonzero amount. By the third month, lock costs accounted for roughly 40% of their total Spanner spend. The PostgreSQL team, accustomed to a world where transaction visibility came free with multi-version concurrency control, had to relearn how to think about locks—not just as a performance concern, but as a direct cost driver.
The Lock That Costs a Dime a Row
Spanner's pricing model charges for each lock operation during read-write transactions. As of late 2024, the per-lock fee is small—on the order of a fraction of a cent—but it adds up quickly under write-heavy workloads. A single transaction that updates 10,000 rows incurs 10,000 lock operations, each billed separately. For a team processing millions of writes per hour, the lock costs can easily surpass compute and storage charges.
The PostgreSQL team in question had designed their application around short, frequent writes—a pattern that works well with PostgreSQL's MVCC, where locks are lightweight and not metered. When they ported the same logic to Spanner, they found that each UPDATE statement was locking rows for the duration of the transaction. Even with careful use of secondary indexes and batched writes, the lock count per transaction remained high.
Lock contention became a budget line item. The team began monitoring lock-wait metrics as closely as CPU utilization. They discovered that long-running transactions were particularly expensive, because locks are held until commit, blocking other writers and increasing the total lock-seconds billed. The old assumption that transaction visibility is free—inherited from PostgreSQL's snapshot isolation—broke under Spanner's metering model.
One engineer described the shock: "We went from paying for CPU and storage to paying for every lock we take. It's like being charged per page fault." The team had to re-architect their write patterns to minimize lock duration and lock count, sometimes sacrificing read freshness to avoid lock costs altogether.
Spanner's Consistency Trade-Off: Money vs. Staleness
Spanner's strong consistency is powered by TrueTime, Google's globally synchronized clock infrastructure. TrueTime ensures that read-write transactions see a consistent snapshot across all regions, but this guarantee comes at a cost. Each read-write transaction must coordinate with TrueTime, adding latency and requiring locks on the involved partitions.
For teams accustomed to PostgreSQL's snapshot isolation—where reads never block writes and writes never block reads—Spanner's read-write transactions can feel punitive. A read-write transaction in Spanner locks the rows it reads, even if it only reads them. This means that a simple SELECT followed by an UPDATE can lock rows for the entire transaction duration, potentially blocking other writers and inflating the lock bill.
Spanner offers stale reads as a cheaper alternative. Stale reads return data from a timestamp a few seconds in the past, avoiding locks entirely. But stale reads are not suitable for all application logic. The PostgreSQL team found that critical paths—like inventory updates or payment processing—required fresh data and thus paid the lock premium. Less critical analytics queries were migrated to stale reads, cutting lock costs by roughly 30%.
The trade-off is stark: pay for strong consistency or accept staleness. For a team used to PostgreSQL's single-node snapshot isolation, where stale reads are the exception rather than the rule, this was a painful adjustment. The cost of global consistency is not just latency—it is a direct line item on the invoice.
PostgreSQL's MVCC Assumptions Don't Survive Spanner
PostgreSQL's multi-version concurrency control (MVCC) is the bedrock of its transaction isolation. Each transaction sees a snapshot of the database at its start time, determined by transaction ID ordering. Locks are used only for write-write conflicts, not for reads. This design means that read-heavy workloads can scale without lock contention, and there is no per-lock pricing.
In Spanner, the concurrency model is fundamentally different. Spanner uses a combination of pessimistic locking and timestamp ordering to achieve serializable isolation. Every read-write transaction must acquire locks on the rows it accesses, and these locks are tracked per shard. The lock table itself is a distributed data structure that incurs storage and compute costs, passed on to the user as lock fees.
The PostgreSQL team's migration revealed that many of their query patterns relied on implicit lock avoidance. For example, they used SERIALIZABLE isolation in PostgreSQL, which uses optimistic concurrency control and aborts on conflict rather than locking. Spanner's serializable mode is pessimistic by default, locking rows on read. The team had to rewrite many transactions to use explicit locking or to reduce the number of rows touched per transaction.
One common pattern was a bulk update that scanned a large table and updated a subset of rows. In PostgreSQL, this would use a single snapshot and avoid locking unmodified rows. In Spanner, the scan itself would lock the entire partition unless the query used a stale read. The team eventually split bulk updates into smaller batches, each touching fewer rows, to keep lock costs under control.
CockroachDB: The Middle Ground That Charges by CPU
CockroachDB, like Spanner, is a distributed SQL database that offers global consistency through a clock synchronization mechanism (hybrid logical clocks). But its pricing model is closer to traditional databases: it charges per CPU hour, not per lock operation. This difference has significant implications for write-heavy workloads.
CockroachDB achieves serializable isolation without explicit lock costs. It uses a combination of optimistic concurrency and timestamp ordering, aborting transactions on conflict rather than locking rows. This means that write-heavy workloads can run without incurring per-row lock fees. However, write contention still hurts performance: conflicting transactions are aborted and retried, increasing latency and CPU usage.
For the PostgreSQL team evaluating alternatives, CockroachDB offered a more predictable cost model. Their write-heavy workload would not surprise them with lock charges. But they found that CockroachDB's performance under high contention was worse than Spanner's, because aborted transactions wasted CPU cycles. The team ran benchmarks showing that at 30% write contention, CockroachDB's throughput dropped by roughly 40% compared to Spanner's lock-based approach.
The choice between Spanner and CockroachDB often comes down to workload characteristics. Spanner's pricing punishes write-heavy workloads with lock fees, but its pessimistic locking can handle high contention more gracefully. CockroachDB's CPU-based pricing aligns cost with compute usage, but its optimistic concurrency can lead to wasted resources under contention. Neither is a drop-in replacement for PostgreSQL, but each offers different economics.
Real-World Numbers: A Migration Story
The startup that moved from PostgreSQL to Spanner in 2022 saw its monthly database bill jump from roughly $8,000 to $24,000 after migration. Of that increase, lock costs accounted for about $6,400—nearly 40% of the Spanner spend. The team had not budgeted for this. Their initial cost estimates, based on compute and storage alone, were off by a factor of two.
To mitigate the lock costs, the team added read replicas in Spanner. Read replicas serve stale reads without incurring lock fees, because they do not participate in write transactions. By routing all non-critical reads to replicas, the team reduced lock costs by roughly 25%. But this introduced operational complexity: replica lag had to be monitored, and some queries needed freshness guarantees that replicas could not provide.
After six months, the team adopted a hybrid architecture. They kept Spanner for read-heavy and globally distributed workloads, but moved write-heavy transactional logic back to a self-managed PostgreSQL cluster. The PostgreSQL cluster handled roughly 60% of writes, while Spanner handled 90% of reads. The combined bill dropped to $15,000 per month, still higher than the original PostgreSQL setup but with better global latency.
This migration story is not unique. Other teams have reported similar experiences on forums and conference talks. The lesson is that distributed SQL pricing models are not interchangeable with monolithic databases. Lock costs, in particular, are a hidden tax that can double or triple a cloud bill for write-heavy applications.
What a Distributed SQL Buyer Should Ask
Before signing a contract for a distributed SQL database, teams should ask vendors for a lock-cost simulation based on their actual workload. Spanner's pricing documentation is transparent about per-lock fees, but many teams overlook them until the first bill arrives. A simulation should include read-write transactions, lock duration, and contention patterns.
Benchmark with your own write patterns. Do not rely on synthetic benchmarks that use trivial workloads. Run a week-long trial with production-like traffic and monitor lock-related metrics. Spanner provides lock-wait charts in its console; CockroachDB provides transaction retry rates. Use these to estimate costs under load.
Check whether the vendor charges per lock or per CPU. Spanner charges per lock; CockroachDB charges per CPU hour. For write-heavy workloads, CPU-based pricing is often more predictable. But for read-heavy workloads with low contention, Spanner's lock costs may be negligible. The right choice depends on your ratio of reads to writes and the level of contention.
Consider eventual consistency for non-critical reads. Both Spanner and CockroachDB offer stale reads or follower reads at reduced cost. Design your application to tolerate staleness where possible. This can cut lock costs by 30–50% in Spanner, and reduce CPU usage in CockroachDB. Negotiate burst credits for lock-heavy months. Some cloud vendors offer volume discounts or credits for sustained usage. Ask for a pricing model that caps lock costs as a percentage of total spend.
Finally, do not assume that distributed SQL is a drop-in replacement for PostgreSQL. The migration story above shows that even with careful planning, hidden costs can emerge. Run a proof of concept with real traffic and real cost monitoring before committing to a multi-year contract. The transparency of pricing models is improving, but the burden of due diligence still falls on the buyer.
Alternative Approaches: TiDB and YugabyteDB
While Spanner and CockroachDB dominate the conversation, other distributed SQL databases offer different cost and consistency trade-offs. TiDB, for example, uses a shared-storage architecture with a separation of compute and storage, and it charges primarily for compute nodes and storage, not per lock. TiDB's pricing is based on the number of TiKV nodes (storage) and TiDB nodes (compute), which makes it more predictable for write-heavy workloads. However, TiDB's consistency model is snapshot isolation, which may be weaker than Spanner's strict serializable isolation. For applications that can tolerate slightly stale reads, TiDB's cost model can be attractive.
YugabyteDB is another contender, built on a foundation similar to CockroachDB but with a focus on PostgreSQL compatibility. Its pricing is also CPU-based, but YugabyteDB offers a "read replica" feature that can reduce lock costs by serving reads from follower nodes. The PostgreSQL team's hybrid architecture with Spanner could have been replicated using YugabyteDB with read replicas, potentially at a lower total cost. However, YugabyteDB's lock management is less transparent than Spanner's, and some teams have reported unexpected CPU spikes under contention.
The key takeaway is that no two distributed SQL databases have the same pricing model. Teams should evaluate at least three vendors—Spanner, CockroachDB, and one alternative like TiDB or YugabyteDB—using their own workload patterns. A cost comparison should include not only the per-lock or per-CPU charges, but also the cost of read replicas, cross-region replication, and data transfer.
Counter-Argument: When Lock Costs Are Acceptable
Not every workload suffers from Spanner's lock pricing. For read-mostly applications with infrequent writes, lock costs are negligible. A typical e-commerce catalog that updates prices once per day and serves millions of reads will see lock fees that are a tiny fraction of the total bill. Similarly, applications that use Spanner's partitioned DML to batch updates can reduce the number of lock operations by updating many rows in a single transaction, though this increases lock duration.
Spanner also offers a "read-only" transaction mode that does not acquire locks. For workloads that can use read-only transactions for all reads, lock costs vanish. However, read-only transactions still require a consistent snapshot, which Spanner provides via TrueTime without locking. The PostgreSQL team's mistake was using read-write transactions for all operations, including reads that could have been read-only.
Some teams argue that the lock costs are a feature, not a bug. By charging per lock, Spanner incentivizes developers to write more efficient transactions—shorter, fewer rows touched, and less contention. This can lead to better overall system performance and lower CPU usage. In this view, the lock fee is a signal that encourages good engineering discipline. The PostgreSQL team's experience, while painful, forced them to optimize their queries and reduce unnecessary locking, which improved latency and throughput.
Future Trends: Pricing Evolution
Cloud database pricing is still evolving. Google has introduced committed use discounts for Spanner that can reduce lock costs by up to 40% for long-term contracts. CockroachDB has experimented with burstable CPU credits that cap costs during spikes. It is possible that within a few years, all distributed SQL vendors will converge on a hybrid model that charges a base fee for compute and storage, with a small surcharge for lock operations that exceeds a threshold.
Another trend is the rise of serverless database offerings that charge per operation, similar to Spanner's lock model. Amazon Aurora Serverless v2, for example, charges per ACU (Aurora Capacity Unit) per hour, but does not charge per lock. However, Aurora's consistency model is weaker than Spanner's, and it does not offer global strong consistency out of the box. The trade-off between cost and consistency will continue to be a central decision for architects.
The PostgreSQL team's story is a cautionary tale, but it also highlights the importance of understanding the operational model of a database before migrating. As distributed SQL becomes more mainstream, the gap between monolithic and distributed pricing will narrow, but for now, teams must do their homework. The lock that costs a dime a row may be the most expensive lesson in cloud database economics.