PracticeVideosBlogFlashcards

7.1 Spanner Keys, Indexes, and Transactions

Key Takeaways

  • High-write monotonic leading keys can hotspot one range; random or bit-reversed keys distribute writes.

  • STORING columns can make a secondary index cover the requested projection.

  • Mutation API limits are cumulative per commit, while current DML mutation limits apply per statement; transaction size still matters.

Last updated: October 2026

Spanner Schema Design: Hotspotting & Table Interleaving

In Cloud Spanner, data is automatically split horizontally into chunks called splits based on primary key ranges. Each split is managed by an independent Spanner node. Achieving high throughput requires distributing reads and writes evenly across all splits.

ANTI-PATTERN (Monotonically Increasing Key):
Split 1: [1000 - 1999]  (IDLE)
Split 2: [2000 - 2999]  (IDLE)
Split 3: [3000 - 3999]  <=== ALL WRITES HIT HERE (100% CPU HOTSPOT!)

OPTIMIZED PATTERN (Bit-Reversed / UUIDv4 Distributed Keys):
Split 1: [0000 - 3FFF]  <=== 25% Writes
Split 2: [4000 - 7FFF]  <=== 25% Writes
Split 3: [8000 - BFFF]  <=== 25% Writes
Split 4: [C000 - FFFF]  <=== 25% Writes

The Hotspotting Anti-Pattern

If a table uses a monotonically increasing primary key (such as an auto-increment integer, a sequential sequence, or a current timestamp CURRENT_TIMESTAMP()), all new inserts will have keys greater than all existing keys. Every insert targets the exact same split at the end of the key space, overwhelming a single node while all other nodes sit idle.

Anti-Hotspotting Solutions

  1. UUIDv4 (Universally Unique Identifiers): Generate random 128-bit UUIDv4 values as primary keys. Random bits ensure uniform distribution across all splits.
  2. Bit-Reversed Sequential Keys: Reverse the binary bits of an auto-incrementing integer sequence using Spanner's built-in BIT_REVERSE() function. Sequential numbers (1, 2, 3...) produce radically dispersed binary values, distributing writes across all splits while preserving numeric compactness.
  3. Hash Prefixing: Prepend a hash of a column attribute or shard ID (an application-computed stable hash bucket) to the primary key.

Table Interleaving

In traditional relational databases, joining a parent table (Customers) with a child table (Orders) requires cross-server network hops if tables reside on different disks or nodes. Spanner resolves this using Table Interleaving:

-- Parent Table
CREATE TABLE Customers (
  CustomerId STRING(36) NOT NULL,
  FullName STRING(100),
  Email STRING(100)
) PRIMARY KEY (CustomerId);

-- Child Table Interleaved in Parent
CREATE TABLE Orders (
  CustomerId STRING(36) NOT NULL,
  OrderId STRING(36) NOT NULL,
  OrderDate DATE,
  TotalAmount NUMERIC,
  OrderStatus STRING(30)
) PRIMARY KEY (CustomerId, OrderId),
INTERLEAVE IN PARENT Customers ON DELETE CASCADE;
  • Physical Co-location: Interleaving physically co-locates the child Orders rows directly alongside the parent Customers row in related key ranges to improve locality; large interleaved groups can still be split.
  • Performance Impact: Joins between Customers and Orders on CustomerId can benefit from locality, but are not guaranteed to have zero network communication, dramatically accelerating query execution and parent-child transactional commits.

Spanner Secondary Indexes, STORING, and Transactions

Secondary Indexes and the STORING Clause

A secondary index in Cloud Spanner is implemented as an independent distributed table sorted by the index key. If a query filters on the indexed column but selects columns not present in the index, Spanner must perform an expensive back-join across the network to the base table split.

To eliminate back-joins, use the STORING clause:

-- Index without STORING: Requires expensive back-join to fetch TotalAmount
CREATE INDEX OrdersByDate ON Orders(OrderDate);

-- Covering Index with STORING: Eliminates back-joins
CREATE INDEX OrdersByDateCovering ON Orders(OrderDate)
STORING (TotalAmount, OrderStatus);

The STORING clause duplicates the specified non-key columns (TotalAmount, OrderStatus) into the index split. The query engine satisfies the query entirely from the index split, reducing latency and compute consumption.

Mutation Limits

Cloud Spanner enforces strict limits on transaction size to maintain predictable latency:

  • Maximum Mutations per Commit: A Mutation API commit permits up to 80,000 mutations, including index effects. Current DML limits apply per statement rather than cumulatively across separate DML statements (where a mutation is an insert, update, or delete of a cell—calculated as rows * columns + index updates).
  • Maximum Transaction Size: A commit payload cannot exceed 100 MiB.
  • Developer Mitigation for Bulk Loads: When ingesting millions of rows, developers must partition data into smaller batches (e.g., 2,000 to 5,000 mutations per batch) and commit them independently, or use the Spanner Dataflow/Bulk Import connectors.

Read-Write vs. Read-Only Transactions

  • Read-Write Transactions: Provide atomic, externally consistent updates; concurrency-control/isolation options determine locking and conflict behavior. Contending operations can abort and require the entire transaction callback to be retried. Commit wait ensures TrueTime external consistency.
  • Read-Only Transactions: Never acquire locks and never block incoming writes. They execute using a TrueTime timestamp (strong read or bounded staleness). For analytics, dashboards, and batch exports, developers can choose read-only transactions with a freshness bound suitable for the task (e.g., exact_staleness = 15s) to read from local replicas without cross-region consensus latency.

Architectural Decision Framework: Cloud SQL vs. Cloud Spanner

Evaluation DimensionCloud SQLCloud Spanner
Workload ScaleManaged engine instance; limits vary by engine and editionDistributed compute/storage; scale within documented capacity and workload limits
Geographic ScopeRegional with cross-region read replicas (async)Native multi-region active-active synchronous replication
Availability SLADepends on edition and HA configuration99.99% regional outside Stockholm (99.95% Stockholm); 99.999% dual/multi-region, subject to current SLA terms
Failover RTOMeasure recovery and reconnection for the chosen configurationDepends on failure, quorum and application recovery; no universal sub-second RTO
Engine CompatibilityDirect MySQL, PostgreSQL, SQL Server compatibilitySpanner GoogleSQL or PostgreSQL dialect (proprietary engine)
Primary Key StrategyStandard auto-incrementing sequences permittedStrict anti-hotspotting mandatory (UUIDv4, bit-reversed)
Cost ProfileLow baseline cost (runs on modest single VMs)Enterprise baseline cost (minimum 100 processing units / 0.1 node)
Join OptimizationStandard relational query optimizerOptimized via Table Interleaving & STORING indexes

Official references checked October 18, 2026: Google Cloud 1, Google Cloud 2.

Loading diagram...
Cloud SQL HA vs Cloud Spanner Distributed Architecture
Test Your Knowledge

A retail banking platform is migrating a high-throughput transaction ledger to Cloud Spanner. The developer designs the primary key schema using an auto-incrementing integer sequence with a monotonically increasing leading key. During initial peak testing, database throughput stalls and CPU utilization spikes on a single Spanner node while other nodes remain idle. What schema modification eliminates this write bottleneck?

A

Replace the sequential integer primary key with a randomly distributed UUIDv4 or bit-reversed sequence

B

Add a secondary index on the transaction timestamp column using the STORING clause

C

Increase the number of Spanner compute nodes from 3 to 10 to add more CPU capacity

D

Configure the table with INTERLEAVE IN PARENT Customers to group transactions by customer

Test Your Knowledge

An e-commerce developer queries Cloud Spanner for customer orders using the query SELECT OrderId, OrderDate, TotalAmount, OrderStatus FROM Orders WHERE CustomerId = @customerId AND OrderDate >= @cutoffDate. Frequent executions of this query cause high latency due to back-joins to the primary table split. How should the developer optimize the secondary index to eliminate back-joins?

A

Convert the secondary index into an interleaved parent-child relationship with the Customers table

B

Recreate the secondary index on (CustomerId, OrderDate) and include TotalAmount and OrderStatus in the STORING clause

C

Execute the query using bounded staleness within a read-only transaction

D

Partition the Orders table across multiple regions using hash-prefixing

Sections you finish are checked off in the contents.