Database / Google Spanner Database Interview questions
1. What is Google Cloud Spanner?
Google Cloud Spanner is a fully managed, horizontally scalable relational database that combines the strong consistency and SQL support of traditional databases with the scale of distributed NoSQL systems.
It spreads data across multiple zones or regions and uses synchronous Paxos-based replication together with the TrueTime API to guarantee external consistency, even when a transaction touches rows stored on different machines. Resharding, replica placement, and failover are all handled automatically, so a single table can grow to petabytes without an engineer manually splitting shards.
Spanner supports two query dialects, GoogleSQL and a PostgreSQL-compatible dialect, and is commonly used for globally distributed workloads such as financial ledgers, ad-serving platforms, and multiplayer game backends that cannot tolerate either downtime or inconsistent reads.
2. What are the main features of Google Spanner?
Spanner's feature set centers on giving developers relational guarantees at global scale.
- External consistency - every committed transaction gets a timestamp that reflects real commit order, even across regions.
- Horizontal scalability - adding compute capacity (nodes or processing units) increases throughput without downtime or manual sharding.
- Automatic sharding and rebalancing - Spanner splits and moves data ranges based on load and size.
- Multi-region replication - configurable regional or multi-region instance configs with 99.999% availability SLAs on multi-region setups.
- SQL support - joins, secondary indexes, and schema constraints via GoogleSQL or PostgreSQL dialect.
- Online schema changes - DDL statements run without taking the database offline.
Together these remove the classic trade-off where teams had to pick either SQL semantics or web-scale distribution.
3. What is the purpose of TrueTime in Spanner?
TrueTime is a globally synchronized clock API that Spanner uses to assign commit timestamps to transactions. Instead of returning a single instant, TrueTime returns an interval [earliest, latest] that is guaranteed to contain the actual current time, using GPS and atomic clock references distributed across Google's data centers.
Spanner uses this bounded uncertainty to enforce external consistency: before committing a write, it waits out the remaining uncertainty window ("commit wait") so that any transaction that starts after another one commits is guaranteed to see a later timestamp. This lets Spanner order transactions correctly across data centers without needing a central coordinator for every commit, which is what makes global strong consistency practical at low latency.
4. Define interleaved tables in Google Spanner?
An interleaved table is a child table whose rows are physically stored next to the parent row they belong to, based on a shared primary key prefix. You declare the relationship with INTERLEAVE IN PARENT, and the child's primary key must start with the parent's primary key columns.
CREATE TABLE Orders ( CustomerId INT64 NOT NULL, OrderId INT64 NOT NULL, OrderDate DATE ) PRIMARY KEY (CustomerId, OrderId), INTERLEAVE IN PARENT Customers ON DELETE CASCADE;
Because parent and child rows sit in the same storage split, fetching a customer and their orders together avoids extra network hops, which is far cheaper than a traditional join across separately sharded tables. It also lets Spanner co-locate related data as it splits and moves ranges for load balancing.
5. What is a Spanner instance?
A Spanner instance is the top-level allocation of compute and storage resources within a Google Cloud project. It defines an instance configuration (regional or multi-region, which fixes where replicas live) and a compute capacity, expressed in nodes or processing units, that determines available throughput.
One instance can host multiple databases, each with its own schema, but they all share the instance's compute capacity and replica topology. Choosing an instance configuration is largely a latency-versus-availability decision: a regional instance keeps writes fast because all replicas are close together, while a multi-region instance trades some write latency for higher read availability and a stronger SLA.
6. What are processing units in Google Spanner?
Processing units are Spanner's fine-grained measure of compute capacity, introduced so customers don't have to provision a full node just to get started. 1,000 processing units equal one node, and capacity can be provisioned in increments as small as 100 processing units.
| Nodes | Use case |
| Whole-node increments | Larger, predictable production workloads |
| Processing units (100 PU steps) | Small workloads, dev/test, gradual scale-up |
Processing units let a team start with a fraction of a node's cost and storage limit, then scale up smoothly as query load or data volume grows, rather than jumping straight to a full node's worth of capacity and price.
7. Describe primary keys in Google Spanner schema design?
Every Spanner table requires a primary key, and unlike most relational databases, that key also decides how rows are physically distributed across storage splits. Rows are stored in primary-key order, so the key you choose directly controls which machine handles which rows.
This makes primary key choice a performance decision, not just a uniqueness rule. A key that increases monotonically, like an auto-incrementing integer or a timestamp, sends all new writes to the same split, creating a hotspot. Common fixes include prefixing the key with a hashed or shuffled value, using a UUID, or bit-reversing a sequential ID so consecutive values land on different splits. Composite keys are also used to support interleaved parent-child relationships.
8. What are the supported database dialects in Spanner?
Spanner databases are created with one of two dialects, chosen at creation time and fixed for the life of the database.
| GoogleSQL dialect | PostgreSQL dialect |
| Google's native SQL syntax, closest to standard SQL with Spanner-specific extensions. | PostgreSQL-compatible syntax and types, useful for teams migrating from Postgres. |
Data types like STRUCT and ARRAY are first-class. | Uses Postgres-style types such as bigint, numeric, and text. |
The choice affects DDL syntax, function names, and driver compatibility, so teams typically pick PostgreSQL dialect when porting an existing Postgres application and GoogleSQL when building new on Spanner or integrating with other Google Cloud tooling.
9. List the data types supported by Google Spanner?
Spanner's GoogleSQL dialect supports a standard set of scalar and structured types for schema design:
- BOOL - true/false values.
- INT64 - 64-bit signed integers.
- FLOAT64 / FLOAT32 - floating point numbers.
- NUMERIC - high-precision decimal, suited to financial data.
- STRING and BYTES - variable-length text and binary data.
- DATE and TIMESTAMP - calendar dates and UTC instants, the latter used for commit timestamps.
- JSON - semi-structured document storage.
- ARRAY - an ordered collection of any of the above (except another array).
Choosing the right type matters for both storage cost and index behavior; for example, NUMERIC avoids the rounding issues FLOAT64 would introduce in monetary columns.
10. How do you create a database in Google Spanner?
A database is created inside an existing instance, either through the Cloud Console, the gcloud CLI, or a client library, and it requires choosing the dialect up front.
gcloud spanner databases create orders-db \ --instance=prod-instance \ --database-dialect=GOOGLE_STANDARD_SQL
You can optionally pass an initial DDL file to create tables at the same time the database is created. After creation, further schema changes are applied as separate DDL statements, which Spanner runs online without blocking reads or writes on unrelated tables. Access to the new database is then controlled through IAM roles or, for finer granularity, database roles defined with GRANT statements.
11. What is a secondary index in Spanner?
A secondary index is an additional sorted structure on one or more non-primary-key columns, created so queries filtering or sorting on those columns don't have to scan the base table.
CREATE INDEX OrdersByDate ON Orders(OrderDate);
By default, a secondary index is stored separately from the base table and only contains the indexed columns plus the primary key. Adding STORING(column) lets you include extra columns directly in the index so a query can be satisfied without a lookup back to the base table, at the cost of extra storage. Indexes can also be declared UNIQUE, and NULL_FILTERED indexes exclude rows where the indexed column is null, which keeps sparse indexes smaller.
12. Explain the purpose of splits in Spanner?
A split is a contiguous range of rows, ordered by primary key, that Spanner manages as a single unit of data movement and load balancing. Splits are the mechanism behind Spanner's horizontal scalability.
As a table grows or a range of keys gets busy, Spanner automatically divides it into smaller splits and can move individual splits to different servers, spreading both storage and query load across the cluster. Each split is independently replicated using Paxos across the replicas defined by the instance configuration. Good schema design (avoiding hot, sequential keys) matters because it directly affects how evenly splits distribute traffic; a bad key pattern can concentrate load onto very few splits regardless of how many nodes are provisioned.
13. What are mutations in Google Spanner?
A mutation is a buffered write operation, either an insert, update, delete, or replace, applied to a Spanner table outside of SQL DML syntax, typically through a client library. Mutations are queued on a transaction object and only sent to Spanner when the transaction commits.
transaction.insert('Orders', ['CustomerId','OrderId','OrderDate'], [1001, 5001, '2026-08-01']);
Mutations are often preferred over DML for simple, high-throughput writes because they skip SQL parsing and query planning, making them faster for straightforward row-level changes. DML remains useful when the write depends on a condition or needs to touch rows matched by a query.
14. How do you apply schema changes in Spanner?
Schema changes are applied as DDL statements, such as ALTER TABLE, CREATE INDEX, or ADD COLUMN, submitted through the console, gcloud, or a client library's updateDatabaseDdl call.
ALTER TABLE Orders ADD COLUMN Status STRING(20);
Most DDL statements in Spanner run online: reads and writes continue against the table while the change propagates across replicas, and Spanner internally manages backfilling data for changes like adding a NOT NULL column with a default. Some operations, such as adding a column with a non-null default on a very large table, take longer because Spanner must backfill every existing row. Best practice is to batch related DDL statements together and monitor long-running operations before assuming they've completed.
15. What is the Spanner emulator?
The Cloud Spanner emulator is a local, in-memory version of Spanner that implements the same gRPC API as the production service, letting developers write and test code without connecting to a real instance or incurring cost.
gcloud emulators spanner start gcloud config set auth/disable_credentials true gcloud config set api_endpoint_overrides/spanner http://localhost:9020/
It supports DDL, DML, mutations, and transactions, making it suitable for unit tests and CI pipelines. It does not enforce real replication, TrueTime uncertainty, or performance characteristics, so latency-sensitive or scale-related behavior still needs to be validated against a real instance before production rollout.
16. Why does Spanner use TrueTime for consistency?
Spanner needs to order transactions correctly across machines that may be thousands of miles apart, and clocks on separate servers naturally drift, so a plain local timestamp can't be trusted to reflect real-world order. TrueTime solves this by exposing clock uncertainty explicitly, as an interval, instead of pretending clocks are perfectly synchronized.
When a transaction commits, Spanner picks a timestamp and then waits until it is certain that timestamp has passed everywhere, the "commit wait" period equal to the uncertainty bound. This guarantees that if transaction B starts after transaction A commits, B's timestamp will be greater than A's, which is the definition of external consistency. Without TrueTime, Spanner would need a slower consensus round trip on every single commit just to establish ordering, which is why TrueTime is central to keeping global transactions both correct and fast.
17. How does Spanner achieve external consistency?
External consistency means that if transaction A commits before transaction B starts (in real time), then A's timestamp is guaranteed to be less than B's, and any client observing both will see them in that same order. Spanner delivers this through three cooperating mechanisms.
- TrueTime supplies a bounded time interval for "now" rather than a single unreliable clock reading.
- Commit wait delays acknowledging a commit until TrueTime guarantees the assigned timestamp is in the past everywhere.
- Two-phase commit over Paxos groups coordinates transactions that span multiple splits, ensuring all participants agree before the client sees success.
The result is a system where transaction ordering matches real-world causality, which is a stronger guarantee than the serializability most distributed databases offer, since serializability alone doesn't pin ordering to wall-clock time.
18. What is the difference between read-write and read-only transactions in Spanner?
| Read-write transaction | Read-only transaction |
| Uses locking and two-phase commit to safely mix reads and writes. | Never takes locks; only reads data. |
| Always reads the latest committed data (strong reads). | Can read at a specific timestamp, including a slightly stale one. |
| Higher latency due to commit coordination across replicas. | Lower latency, and can be served from the nearest read replica. |
Read-write transactions are the only way to write data and are used whenever a read result influences a subsequent write. Read-only transactions are cheaper because they skip locking entirely; using exact staleness or bounded staleness with a read-only transaction lets an application trade a small amount of freshness for significantly lower latency, which matters for read-heavy, latency-sensitive workloads like dashboards or product catalogs.
19. When should you use interleaved tables versus foreign keys?
Both express a parent-child relationship, but they optimize for different things. Interleaved tables physically co-locate child rows with their parent, so use them when the child is almost always accessed together with its parent, such as an order's line items, and when the child's primary key can meaningfully start with the parent's key.
Foreign keys enforce referential integrity without changing physical layout, so they suit relationships where the child is queried independently, has its own natural primary key, or where interleaving would force an awkward composite key. Foreign keys also support cross-hierarchy references that interleaving can't express, since an interleaved table can only nest under one parent chain.
A practical rule: reach for interleaving when co-location speeds up your main access pattern; reach for a foreign key when you mainly need integrity checking and independent access.
20. What happens when a hotspot occurs in Spanner?
A hotspot happens when a disproportionate share of reads or writes lands on a small number of splits, usually because the primary key is monotonically increasing (like a timestamp or auto-increment ID) or because one key value, such as a popular tenant ID, gets far more traffic than others.
When this happens, throughput on that narrow key range gets capped by a single split's capacity, no matter how many nodes the instance has overall; you'll see elevated latency and CPU utilization concentrated on specific splits in Cloud Monitoring, along with lock contention if writes are also hitting the same rows. Spanner can move a hot split to its own server, but it cannot subdivide traffic on a single key value, so the fix is schema-level: hash or shuffle the key, add a random prefix, or use bit-reversed sequential IDs so writes spread across the keyspace.
21. How is data partitioned across nodes in Spanner?
Spanner partitions data by primary key range into splits, and each split is assigned to a Paxos group of replicas spread across the zones or regions in the instance configuration. As data grows or a range becomes hot, Spanner's placement driver automatically divides an oversized or overloaded split into smaller ones and can reassign splits to different servers to balance load.
Interleaved child tables share the same splits as their parent, since their keys are prefixed by the parent's key, which keeps related rows together during this partitioning. Nodes and processing units don't map one-to-one to fixed shards; instead, Spanner continuously reassigns splits across available compute capacity, so scaling up adds capacity without any manual resharding step from the application team.
22. Why should you avoid monotonically increasing primary keys?
Because Spanner stores rows in primary-key order, a key that always increases, such as an auto-incrementing integer, a sequential ID, or a plain TIMESTAMP, means every new row is appended at the same end of the keyspace. That range becomes one split, and every insert competes for the same split's write capacity, creating a hotspot that no amount of extra nodes can relieve, since the other nodes simply sit idle.
The common fixes preserve uniqueness and rough ordering while breaking the monotonic pattern: bit-reverse a sequential ID before storing it, prepend a hash or a shard key derived from another column, or generate a UUID instead of a sequence. Each approach scatters new rows across many splits so writes parallelize across the full cluster instead of funneling through one.
23. What is the difference between Spanner and Cloud SQL?
| Cloud Spanner | Cloud SQL |
| Horizontally scalable, distributed across zones/regions automatically. | Runs on a single managed instance (with read replicas), vertically scaled. |
| Strong external consistency via TrueTime and Paxos. | Standard MySQL/PostgreSQL/SQL Server consistency model. |
| Custom dialect (GoogleSQL) or PostgreSQL-compatible. | Real MySQL, PostgreSQL, or SQL Server engines. |
| Priced by node/processing unit capacity, not storage tiers alone. | Priced by instance size (vCPU/RAM) and storage. |
Cloud SQL is the right default for typical application databases that fit on one machine and need full engine compatibility, including extensions and stored procedures. Spanner is worth the added complexity when a single instance's write throughput or storage ceiling becomes the actual bottleneck, or when the application needs strong consistency across multiple regions rather than just read replicas.
24. How does Spanner handle schema changes without downtime?
Spanner treats DDL as a distributed operation coordinated across all replicas rather than a lock that halts the database. When a DDL statement is submitted, Spanner assigns it a future timestamp at which the new schema becomes active, and it propagates the pending change to every replica ahead of that time.
Existing reads and writes keep running against the current schema version right up until the transition point, so there's no window where the database is unavailable. For changes that require touching existing data, such as adding a non-null column with a default value, Spanner runs a background backfill process that populates the new column across all rows without blocking foreign traffic, and the schema change is only marked complete once the backfill finishes.
25. When would you choose bounded staleness over strong reads?
Bounded staleness tells Spanner "give me data that is at most N seconds old, but pick whatever timestamp lets you answer fastest," instead of forcing a strong read that must confirm it has the absolute latest commit before responding. Spanner is free to route the read to the closest available replica and serve it without waiting on the current leader.
This trade-off makes sense for read-heavy, latency-sensitive paths where near-real-time data is acceptable: a product catalog page, an analytics dashboard, or a recommendation feed rarely needs data that's only milliseconds old. Strong reads remain necessary whenever a read result feeds directly into a decision that must reflect the very latest write, such as checking current inventory right before finalizing a purchase.
26. How can you optimize query performance in Spanner?
Most Spanner performance problems trace back to either schema design or query shape, so optimization usually targets both.
- Design keys to avoid hotspots - a hot split caps throughput regardless of query tuning.
- Add targeted secondary indexes with
STORINGcolumns so common queries avoid a base-table lookup. - Use interleaving for parent-child access patterns to cut cross-split reads.
- Check query plans via
EXPLAINin the console to catch full table scans hiding behind a missing index. - Prefer parameterized queries so Spanner can reuse cached query plans instead of re-optimizing each call.
- Batch reads using
Reador index range reads instead of issuing many single-row point queries in a loop.
Query tuning without fixing an underlying hot key rarely helps much, so it's worth confirming split-level metrics in Cloud Monitoring before assuming the query itself is the bottleneck.
27. What is the difference between Data Boost and standard reads?
| Standard reads | Data Boost |
| Executed using the instance's provisioned nodes/processing units. | Executed on separate, serverless compute Google provisions on demand. |
| Competes with production transactional traffic for capacity. | Isolated from production traffic, so it doesn't affect transactional latency. |
| Billed as part of normal instance compute cost. | Billed separately, based on data processed for the analytical job. |
Data Boost is aimed at large analytical or batch reads, like exporting data to BigQuery or running a heavy reporting query, that would otherwise compete with the instance's OLTP workload for the same nodes. By offloading that read path to independent compute, transactional latency for the primary application stays stable even while a large scan or export runs in parallel.
28. Why do we use commit timestamps in Spanner tables?
A commit timestamp column, declared with OPTIONS (allow_commit_timestamp=true), lets Spanner fill in the exact TrueTime-derived commit time of the transaction automatically, rather than the application computing its own "updated at" value.
CREATE TABLE AuditLog ( Id STRING(36) NOT NULL, ChangedAt TIMESTAMP OPTIONS (allow_commit_timestamp=true) ) PRIMARY KEY (Id);
This matters because application-side clocks aren't synchronized the way TrueTime is, so two servers writing "now" independently could disagree about ordering. Using the Spanner-assigned commit timestamp instead guarantees the value is externally consistent with every other transaction in the database, which makes commit timestamp columns the standard building block for change tracking, audit trails, and change streams.
29. How does Spanner's query optimizer choose an execution plan?
Spanner's optimizer is cost-based: it evaluates candidate plans using table and index statistics, such as row counts and cardinality estimates, and picks the plan with the lowest estimated cost in terms of data scanned and rows processed.
Key inputs to that decision include which indexes exist and whether they're covering for the query's columns, whether a join can be satisfied by co-located interleaved data instead of a distributed join, and predicate selectivity, which determines whether filtering early via an index beats scanning the base table. Developers can inspect the chosen plan with EXPLAIN or the query plan visualizer in the console, and can nudge the optimizer using index hints (FORCE_INDEX) when it consistently picks a suboptimal plan, though this should be a last resort after confirming statistics and indexes are actually appropriate for the query.
30. What is the difference between batch DML and partitioned DML?
| Batch DML | Partitioned DML |
| Groups several DML statements into one transaction. | Splits one large DML statement across many transactions internally. |
| Fully atomic - all statements commit or none do. | Not atomic as a whole - executes as independent sub-ranges. |
| Bound by normal transaction size and lock limits. | Designed for bulk updates/deletes across millions of rows. |
Batch DML is the right tool when you have several related, moderate-size statements that must succeed or fail together, like updating a handful of related tables in one logical change. Partitioned DML is built for maintenance-style operations, such as deleting rows older than a retention window across an entire large table, where wrapping the whole thing in a single transaction would exceed transaction limits or hold locks far too long.
31. When should you use change streams in Spanner?
Change streams capture row-level insert, update, and delete activity on watched tables (or the whole database) in near real time, ordered and partitioned by commit timestamp, without the application having to write custom trigger logic or poll for changes.
CREATE CHANGE STREAM OrderChanges FOR Orders;
They fit scenarios like replicating changes into BigQuery for analytics, invalidating caches when underlying rows change, driving event-driven microservices off database mutations, or building audit trails that need the exact sequence of changes rather than just a periodic snapshot. Because change streams read through the same transactional, externally consistent commit log Spanner already maintains, they avoid the missed-update or duplicate-event problems common with naive polling approaches, and Dataflow provides ready-made connectors for consuming them into downstream systems.
32. How is fine-grained access control implemented in Spanner?
Beyond project-level IAM roles, which grant or deny access to an entire instance or database, Spanner supports database roles that restrict access down to individual tables, columns, or views inside a single database.
CREATE ROLE analyst; GRANT SELECT(CustomerId, OrderDate) ON TABLE Orders TO ROLE analyst; GRANT ROLE analyst TO 'user:jane@example.com';
A database role is defined with standard GRANT/REVOKE DDL, and IAM is used to grant a Cloud Identity principal the ability to act under that database role via the spanner.databaseRole.user permission. This two-layer model lets a team give broad IAM access to an application's service account while still restricting exactly which columns that account, or specific human users, can query, which matters for masking sensitive fields like PII from analysts who only need aggregate data.
33. Why doesn't Spanner support auto-incrementing primary keys?
Spanner deliberately omits a native auto-increment or identity-column feature because it conflicts with how the database achieves horizontal scale. An auto-incrementing sequence, by definition, produces monotonically increasing values, and since rows are stored in primary-key order, every new row would land in the same split, creating exactly the write hotspot Spanner's distributed architecture is designed to avoid.
A single-node database can afford auto-increment because there's only one place for writes to go anyway, but that assumption breaks down once data is sharded across many machines. Instead, Spanner encourages key strategies that preserve uniqueness without sequential order: UUIDs, bit-reversed sequence values, or a hash prefix combined with a secondary ordering column when insertion order still needs to be queryable through an index rather than the primary key itself.
34. What is the difference between GoogleSQL and PostgreSQL dialects in Spanner?
| GoogleSQL dialect | PostgreSQL dialect |
| Native Spanner types like STRUCT and ARRAY are first-class. | Uses Postgres types: bigint, numeric, text, timestamptz. |
| DDL uses Spanner-specific clauses (e.g. INTERLEAVE IN PARENT). | DDL closely mirrors standard Postgres DDL syntax. |
| Works with Spanner client libraries and GoogleSQL-aware tooling. | Works with many existing PostgreSQL drivers via the PGAdapter proxy. |
The two dialects are not interchangeable within one database - the choice is fixed at creation - and query behavior can differ subtly, for instance in how NULLs sort or how implicit type coercion works. Teams migrating an existing Postgres application typically pick the PostgreSQL dialect specifically to reduce query rewrite effort and to reuse existing Postgres-compatible tooling through PGAdapter, while greenfield Spanner projects more often default to GoogleSQL.
35. How do you troubleshoot high latency in Spanner queries?
Latency issues in Spanner usually fall into a few diagnosable buckets, so troubleshooting starts with narrowing down which one applies.
- Check Cloud Monitoring's split-level metrics for CPU and lock-wait concentration, which points to a hotspot rather than a query problem.
- Run EXPLAIN / query stats to see whether the query is hitting a full table scan instead of using an index.
- Inspect lock wait and transaction abort metrics - frequent aborts usually mean contention on the same rows from concurrent read-write transactions.
- Review read type - a strong read that could tolerate staleness is paying an unnecessary latency cost versus bounded staleness.
- Check for cross-region round trips - a client in one region hitting a leader replica in another adds fixed network latency that no query tuning fixes.
Isolating which layer, schema, query plan, contention, or topology, is responsible avoids wasted effort tuning a query when the real cause is a hot key or a distant leader replica.
36. Explain the internal working of Paxos in Spanner replication?
Every split in Spanner is replicated across a set of replicas (typically 3, 5, or more depending on the instance configuration) that form an independent Paxos group. One replica is elected leader for that group; the rest are followers that can serve reads but don't originate writes for that split.
A write only needs acknowledgment from a majority of replicas in the group, not all of them, so the group tolerates the loss of a minority of replicas without losing data or availability. If the leader fails, the surviving replicas run a Paxos leader election to pick a new leader, using a lease mechanism so at most one leader is active at a time. Because every split has its own independent Paxos group, Spanner runs thousands of these groups in parallel across a large database, which is what lets replication scale horizontally alongside the data itself.
37. Explain the execution flow of a read-write transaction in Spanner?
A read-write transaction in Spanner moves through acquisition of locks, buffered writes, and a two-phase commit if the transaction spans more than one split.
Reads within the transaction take shared locks on the rows they touch, and writes take exclusive locks, held until commit or abort. If the transaction only touches one split, the leader for that split can commit directly through its own Paxos group. If it spans multiple splits, one participant is chosen as the transaction coordinator, running two-phase commit: it collects a "prepared" vote from every participant group, then picks a commit timestamp using TrueTime and applies commit wait before releasing locks and returning success to the client. Any lock conflict with a concurrent transaction can cause an abort, which the client library typically retries transparently.
38. Explain the lifecycle of a split in Spanner?
A split begins as part of a larger key range, typically the entire table when it's small, and evolves as data volume and traffic change over time.
As a split grows past internal size thresholds, or its traffic concentrates load unevenly, Spanner's placement driver splits it into two or more smaller ranges, each getting its own Paxos group of replicas. The driver continuously monitors CPU and storage metrics per split and can move a split's replicas to different zones or servers to rebalance the cluster, all without any application-visible downtime, since reads and writes are redirected transparently as splits move. Splits can also merge back together if data is deleted and a range becomes small and cold, keeping split count proportional to actual load rather than growing indefinitely.
39. How does Spanner guarantee external consistency across regions?
Cross-region external consistency relies on the fact that TrueTime's uncertainty bound is a physical property of the clock infrastructure, not something tied to any one data center, so every region can independently agree on "has this timestamp definitely passed" without talking to each other for that specific check.
When a transaction commits, Spanner assigns it a timestamp s and then performs commit wait, delaying the acknowledgment until TrueTime's current interval guarantees the real time is past s everywhere, no matter which region a subsequent transaction starts in. Because every region's TrueTime interval brackets the same real time within the same bounded uncertainty (typically single-digit milliseconds), a transaction that begins in Region B after a commit in Region A is mathematically guaranteed to receive a later timestamp, even if B never directly communicated with A's replicas for that write.
Multi-region instance configurations add a further piece: reads can be served from regional replicas using bounded staleness, but any read that must reflect the very latest state still ultimately depends on the leader region's Paxos group, since that's where the authoritative commit for a given split occurs.
40. What happens internally when Spanner commits a distributed transaction?
For a transaction touching multiple splits, Spanner's client picks one participant Paxos group as the transaction coordinator and runs a two-phase commit protocol across all groups involved.
In the prepare phase, every participant group replicates its portion of the write via its own Paxos majority and reports back "prepared," meaning it has durably logged the intent but not yet made it visible. The coordinator then picks a single commit timestamp, using TrueTime, that is later than all participants' prepare timestamps, and performs commit wait to ensure that timestamp has definitely passed. In the commit phase, it tells every participant to apply the write at that timestamp and release locks. If any participant fails to prepare, the coordinator aborts the whole transaction and every participant rolls back, preserving atomicity across splits that may live on entirely different machines.
41. How can you optimize a multi-region Spanner configuration for latency?
Multi-region latency optimization mostly comes down to controlling where reads and writes physically go relative to the leader region.
- Place the app close to the leader region for write-heavy services, since every write ultimately routes through the leader replicas of the split it touches.
- Use directed reads to pin read traffic to the nearest replica type/region instead of always hitting the leader.
- Use bounded or exact staleness reads where slightly older data is acceptable, letting Spanner serve from the closest replica rather than waiting on leader confirmation.
- Choose an instance configuration whose regions match your user base - a poorly chosen triad of regions can add unnecessary hops for both quorum writes and reads.
- Split read-heavy and write-heavy access patterns across services so read replicas absorb load without adding write latency.
Because write latency is bounded by the quorum round trip among leader-region replicas, no amount of client-side caching fixes a poorly chosen region topology - the fix has to happen at the instance configuration and access-pattern level.
42. Which is better and why: multi-region or regional Spanner configuration for a global app?
Neither is universally better; the right choice depends on which failure mode and latency profile the application can least tolerate.
| Regional | Multi-region |
| Lower write latency - all replicas are close together. | Higher write latency due to cross-region quorum. |
| 99.99% availability SLA. | 99.999% availability SLA, survives a full region outage. |
| Reads are fast but only from one geography. | Reads can be served near users in multiple geographies. |
A regional configuration is the better fit when the user base and write-heavy traffic are concentrated in one geography and the priority is minimizing write latency - most single-market SaaS backends fall here. A multi-region configuration is worth its added write latency and cost when the application genuinely serves users across continents and needs to survive a full regional outage without downtime, such as global payment or ad-serving systems where an extra region-level nine of availability matters more than shaving milliseconds off each write. Teams sometimes get this wrong by defaulting to multi-region "for safety" on an app that's actually single-region in practice, paying a real latency tax for availability they don't need yet.
43. How does directed reads improve read latency in multi-region Spanner?
By default, some read paths in a multi-region instance can end up routed toward the leader region even when a closer replica could have served them, because the client isn't explicitly telling Spanner where it prefers to read from. Directed reads let the client specify a preferred replica type (read-write or read-only) and a preferred region or set of regions for a given request.
// Client option example (conceptual) options.directedReadOptions = { includeReplicas: { replicaSelections: [{ location: "us-east4", type: "READ_ONLY" }] } };
With that hint, Spanner routes the read to a nearby read-only replica instead of the leader whenever the request's consistency requirements (like bounded staleness) allow it, cutting the network hop that would otherwise cross to a distant leader region. This is most valuable for read-heavy services with a geographically distributed user base, where consistently steering reads to the nearest healthy replica meaningfully reduces tail latency without changing the transaction's correctness guarantees.
44. Why is clock skew uncertainty critical to Spanner's TrueTime API?
TrueTime's core insight is refusing to pretend clocks are perfectly synchronized. Every physical clock, even GPS and atomic-clock-disciplined ones, drifts slightly, so instead of returning a single "current time" value that might be wrong by an unknown margin, TrueTime returns an interval [earliest, latest] and a mathematical guarantee that real time falls somewhere inside it.
That explicit uncertainty bound (epsilon), typically a few milliseconds, is exactly what commit wait is built around: Spanner deliberately delays acknowledging a commit until epsilon has definitely elapsed, guaranteeing the assigned timestamp is safely in the past everywhere before any other transaction could observe a smaller one. If Spanner instead trusted a single unsynchronized clock reading, two transactions on different machines could be assigned timestamps that don't reflect their real commit order, silently breaking external consistency without any error being raised.
The size of epsilon also has a direct performance cost: a larger uncertainty window means a longer commit wait, so Google invests heavily in GPS and atomic clock hardware specifically to keep epsilon small (single-digit milliseconds), which is why TrueTime's accuracy is treated as a first-class piece of infrastructure rather than an implementation detail.
45. How do you troubleshoot transaction aborts in Spanner?
Aborts in Spanner almost always trace back to lock contention between concurrent read-write transactions, so troubleshooting focuses on finding and reducing that contention rather than treating each abort as a bug.
- Check abort rate and lock-wait metrics in Cloud Monitoring, broken down by table, to identify which rows or key ranges are contended.
- Look for long-running transactions that hold locks while doing unrelated work, like an external API call, between reads and the final commit - shortening the transaction body usually helps more than anything else.
- Check for a hot key where many concurrent transactions touch the same row, such as a shared counter, and consider redesigning it (e.g. sharded counters) if so.
- Confirm retry logic is in place - client libraries generally retry aborted transactions automatically, but custom transaction-handling code must do the same or aborts surface as user-facing errors.
- Review transaction priority if mixing latency-sensitive OLTP work with background batch jobs, since batch work can be given lower priority to reduce its impact on interactive traffic.
The key mental shift is that occasional aborts are an expected part of Spanner's optimistic concurrency model; the goal of troubleshooting is reducing their rate on hot paths, not eliminating them entirely.
46. Explain the internal working of the Spanner query execution engine?
Once the optimizer picks a plan, Spanner executes it as a tree of operators, distributed across whichever splits the query touches, and streams partial results back rather than materializing the whole result set at once.
For a query that only touches one split, execution is straightforward: the leader replica for that split runs the plan locally. For a query spanning multiple splits, for example a range scan across a large interleaved table, Spanner distributes the relevant operator subtrees to each split involved, and a coordinating node merges and, if needed, re-sorts or re-aggregates the partial results (for operations like ORDER BY, GROUP BY, or joins that cross split boundaries). Interleaved tables let some joins execute entirely within a split, avoiding this fan-out and merge step, which is a major reason interleaving is recommended for hot, frequently-joined parent-child access patterns rather than left as separate, non-interleaved tables.
47. What happens when a leader region becomes unavailable in Spanner?
In a multi-region configuration, each split's Paxos group has replicas spread across the configured regions, with the leader typically hosted in the designated "leader region" for low write latency. If that region becomes unavailable, whether from a network partition or an outage, the affected Paxos groups detect the loss of the current leader once its lease expires and run a new leader election among the surviving replicas in the other regions.
Because commits only require a majority quorum, not the original leader region specifically, a multi-region configuration (typically using a 2-2-1-region-style replica layout) can still form a majority from the remaining regions and continue accepting writes, just with higher latency until the new leader is elected and located farther from application traffic that was tuned for the old leader region. Reads against read-only replicas in unaffected regions are largely unaffected throughout, which is why multi-region configurations carry a 99.999% availability SLA: the system is designed to survive a full regional outage without becoming unavailable, at the cost of temporarily elevated write latency during the transition.
48. How does Spanner implement point-in-time recovery internally?
Point-in-time recovery (PITR) relies on Spanner's version_retention_period, a per-database setting (up to 7 days) that controls how long old row versions are kept before garbage collection instead of being purged immediately after being superseded.
ALTER DATABASE orders_db SET OPTIONS (version_retention_period = '7d');
Internally, every write in Spanner is already timestamped by TrueTime and stored as a new version rather than an in-place overwrite, since Spanner is a multi-version system that needs historical versions anyway to serve stale reads. Extending the retention window simply keeps those historical versions around longer. A stale read or read-only transaction can then specify an exact past timestamp within that window, and Spanner reconstructs the database (or a specific table) as it existed at that instant by reading the appropriate row versions from each split, effectively giving a consistent snapshot without restoring from a separate backup file. This is why PITR in Spanner is fast to initiate compared to restoring a traditional backup - it reuses existing multi-version storage rather than replaying a backup image - but it is bounded by the retention window, so anything older requires a full backup restore instead.
49. Explain the execution flow of a partitioned DML statement in Spanner?
Partitioned DML is designed for bulk updates or deletes that would be impractical, or impossible, inside a single transaction, so instead of one atomic operation it decomposes the statement into many independent pieces.
Spanner first analyzes the statement's key range and splits it into partitions roughly aligned with existing table splits. Each partition is then executed as its own small, independent read-write transaction that commits on its own, rather than all partitions committing together atomically. This means a partitioned DML statement is not all-or-nothing: if it's interrupted partway through, some partitions will have committed and others won't, so it's designed to be idempotent and safely re-runnable rather than treated like a single ACID operation. The trade-off is deliberate - by giving up whole-statement atomicity, Spanner can update or delete rows across an entire large table without a single long-running transaction holding excessive locks or exceeding transaction size limits.
50. How can you optimize schema design to avoid hotspotting at scale?
Hotspot prevention is primarily a key-design problem, so the fix has to happen before the table fills up rather than after traffic concentrates.
- Avoid monotonic keys - sequential IDs, auto-increment, or plain timestamps all funnel new rows to one end of the keyspace.
- Bit-reverse sequential values when insertion order matters for external systems but the raw sequence would otherwise be monotonic.
- Use UUIDs or hashed prefixes to scatter writes randomly across the keyspace when no meaningful ordering is required.
- Shard hot logical keys - for a single frequently-updated row like a global counter, split it into N sharded rows and sum them on read instead of writing to one row from many transactions.
- Interleave thoughtfully - co-locating truly related data helps, but interleaving a high-write child under a low-cardinality parent can recreate the same hotspot one level up.
- Monitor split-level CPU in Cloud Monitoring proactively, since hotspots often only become visible under real production load, not in testing at small scale.
The unifying idea is that Spanner distributes load by key range, so any design that causes many concurrent transactions to target the same narrow range of keys will bottleneck no matter how much compute capacity is provisioned.
