Relational Databases at Scale

Aurora, Spanner, and cloud-native distributed SQL.

The Scale Dilemma for Relational Databases

Traditional relational databases (MySQL, PostgreSQL, SQL Server) excel at maintaining ACID guarantees, but they hit a wall when data grows beyond a single server's capacity. When you need to handle billions of records, thousands of transactions per second, or serve users across continents, you face a fundamental choice: regional replication with eventual consistency or global strong consistency. Cloud-native databases like Aurora, Spanner, and Azure's offerings solve scale differently, each with distinct trade-offs in consistency, latency, cost, and complexity.

Two Philosophies: Aurora (AWS) achieves scale through regional replication with read replicas and eventual consistency for cross-region reads. Spanner (Google) achieves global scale with strong consistency everywhere, using atomic clocks and a unique architecture. Azure offers both models with Cosmos DB for PostgreSQL and SQL Hyperscale.

Why Traditional Relational Databases Struggle at Scale

Traditional databases were designed when data fit on a single powerful server. As applications grow to serve millions of users globally, these databases face architectural limits that can't be solved by just adding more RAM or CPU.

Single-Server Limits

Even the largest servers have finite CPU, RAM, and I/O capacity. When your database exceeds these limits (hundreds of millions of rows, thousands of writes/second), you can't vertically scale further. Upgrading hardware becomes exponentially expensive and eventually impossible.

Multi-Region Challenges

Users in Asia shouldn't wait 200ms to query a database in Virginia. Replicating data across continents introduces consistency problems: do you prioritize low latency (eventual consistency) or strong guarantees (synchronous replication with higher latency)?

CAP Theorem Trade-offs

CAP is usually quoted as "pick two of Consistency, Availability, Partition Tolerance", which is memorable and slightly misleading. Partitions are not something you choose: cables get cut whether or not your architecture approves. The real question is what the system does during a partition, keep answering with possibly stale data (AP) or refuse to answer rather than risk being wrong (CP). With no partition, which is nearly all the time, you get both. A single-node database is not a CAP trade-off at all, it simply fails.

Cost of Downtime

Single-server databases have a single point of failure. Hardware failures, network issues, or maintenance windows cause complete outages. For e-commerce, financial services, or SaaS applications, even seconds of downtime costs thousands or millions of dollars.

When Do You Need Relational Databases at Scale?

✅ Use Distributed SQL When:
  • Need ACID guarantees across large datasets
  • Complex queries with JOINs and transactions
  • Existing SQL expertise and tooling
  • Schema enforcement and referential integrity required
  • Multi-region availability with failover
  • Data exceeds single-server capacity (>1 TB actively queried)
❌ Consider NoSQL Instead When:
  • Flexible schema with frequent changes
  • Key-value or document-based access patterns
  • Eventual consistency is acceptable
  • Horizontal scaling is primary requirement
  • Simple queries without complex JOINs
  • Millisecond latency is critical

AWS Aurora: Regional Replication with Global Database

Amazon Aurora is AWS's cloud-native relational database built from the ground up to take advantage of distributed storage. It's MySQL and PostgreSQL compatible, meaning existing applications work without code changes. Aurora separates compute from storage, enabling independent scaling and resilience through 6-way storage replication across 3 Availability Zones.

Architecture Highlights

Decoupled Storage

Storage layer is separate from compute, distributed across hundreds of storage nodes. Data is replicated 6 ways across 3 AZs for fault tolerance. Storage scales automatically from 10 GB to 128 TB.

Compute Layer

Database instances (1 primary + up to 15 read replicas) run queries. Compute can scale independently from storage. Aurora Serverless auto-scales from 0 to 256 ACUs based on demand (0 ACUs means auto-pause; older engine versions floor at 0.5 and cap at 128).

High Availability

Automatic failover to a reader instance: AWS documents service typically restored in under 60 seconds, and often under 30. With no reader in the cluster, Aurora has to recreate the primary instead, which typically takes under 10 minutes. Continuous backup to S3. Aurora can lose 2 copies of data without affecting write availability, 3 copies without affecting reads.

Aurora: Compute Separated from 6-Way Replicated Storage

AZ 1AZ 2AZ 3AuPrimarywritesAuReaderreadsEBSStoragecopy 1EBSStoragecopy 2EBSStoragecopy 3EBSStoragecopy 4EBSStoragecopy 5EBSStoragecopy 6log records, 6 copies / 3 AZsreads

Figure 1: Aurora's compute instances (1 primary + up to 15 readers) share a distributed storage volume that keeps 6 copies across 3 Availability Zones. It tolerates losing 2 copies with no write impact, 3 with no read impact.

Key Features

Aurora Global Database

One primary region for writes plus up to 10 read-only secondary regions, for disaster recovery and low-latency global reads. Primary region handles writes, secondary regions serve read-only traffic with <1 second replication lag.

# Create Aurora Global Database cluster
aws rds create-global-cluster \
  --global-cluster-identifier global-app-cluster \
  --engine aurora-postgresql

# Primary cluster in us-east-1
aws rds create-db-cluster \
  --db-cluster-identifier primary-cluster \
  --engine aurora-postgresql \
  --global-cluster-identifier global-app-cluster \
  --region us-east-1

# Secondary cluster in eu-west-1 (read-only)
aws rds create-db-cluster \
  --db-cluster-identifier secondary-cluster-eu \
  --engine aurora-postgresql \
  --global-cluster-identifier global-app-cluster \
  --region eu-west-1

# Application writes to the primary, reads from its local region.
# AWS documents replication to secondary regions with "latency typically
# under a second". Two different operations exist and they are not the same:
#   switchover - planned, on a healthy global database, with NO data loss
#   failover   - unplanned, after losing the primary region; this is where
#                an RPO greater than zero is possible, because replication
#                is asynchronous
# AWS markets an RPO of 1 second and RTO under 1 minute for the managed
# path. Treat those as targets to validate in a game day, not guarantees.
Use case: E-commerce platform serving users globally. Writes happen in primary region (us-east-1), but European users read from eu-west-1 replica with ~10ms latency instead of 100ms+ cross-Atlantic latency.
Backtrack (Time Travel)

Rewind the whole cluster to a point in the last 72 hours without restoring from a backup, turning an accidental DELETE into a minutes-long recovery instead of an hours-long one.

Three constraints that decide whether you can use this at all:
  • Aurora MySQL only. AWS states plainly that "Backtrack is not available for Aurora PostgreSQL". Every other example on this page uses aurora-postgresql, so Backtrack is not an option for them.
  • Enabled at creation, never after. "You can't modify a DB cluster to enable the Backtrack feature." If you did not turn it on when the cluster was created, your only path is restore-from-backup.
  • Not free of downtime. Aurora "pauses the database, closes any open connections, and drops any uncommitted reads and writes" while it rewinds. Stop your application first.
Backtrack is also incompatible with Aurora Global Database: AWS lists "Backtracking in Aurora" among the features a global database does not support. You get one or the other, not both.
# Backtrack must be enabled when the cluster is CREATED, and only
# works on Aurora MySQL. There is no way to add it to an existing
# cluster and no equivalent for Aurora PostgreSQL.
aws rds create-db-cluster \
  --db-cluster-identifier my-cluster \
  --engine aurora-mysql \
  --backtrack-window 259200          # 72 hours, the maximum

# Rewind the cluster to two hours ago
aws rds backtrack-db-cluster \
  --db-cluster-identifier my-cluster \
  --backtrack-to "2026-01-30T12:00:00Z"

# Aurora pauses the database, closes open connections and drops
# uncommitted work while it rewinds, so quiesce the application first.
# Check where it actually landed (Aurora snaps to the nearest
# consistent point, not necessarily the exact timestamp you asked for):
aws rds describe-db-cluster-backtracks \
  --db-cluster-identifier my-cluster
Aurora Serverless v2

Automatically scales database capacity based on application demand. Recent engine versions scale from 0 to 256 ACUs (Aurora Capacity Units) in increments as small as 0.5, and the larger the current capacity, the larger the increment, so scaling gets faster the bigger you are. A minimum of 0 enables auto-pause: the instance stops consuming compute entirely when idle, at the cost of a cold resume. Older versions are limited to 0.5-128. Pay only for resources consumed.

# Create Aurora Serverless v2 cluster
aws rds create-db-cluster \
  --db-cluster-identifier serverless-cluster \
  --engine aurora-postgresql \
  --serverless-v2-scaling-configuration \
    MinCapacity=0,MaxCapacity=16,SecondsUntilAutoPause=3600
# MinCapacity=0 enables auto-pause and needs a recent engine version
# (Aurora PostgreSQL 13.15+/14.12+/15.7+/16.3+, Aurora MySQL 3.08+).
# Older versions floor at 0.5. The full range today is 0-256 ACUs.

# Create serverless instance
aws rds create-db-instance \
  --db-instance-identifier serverless-instance \
  --db-cluster-identifier serverless-cluster \
  --engine aurora-postgresql \
  --db-instance-class db.serverless

# Scales automatically between the min and max you set. AWS documents the
# behaviour, not a fixed latency: capacity moves in increments as small as
# 0.5 ACU, and the increment grows with current capacity, so a large
# instance scales faster than a small one. Aurora does not wait for a quiet
# point: scaling happens with connections open, transactions in flight and
# tables locked, and does not disrupt work underway.
#
# Do not promise a number you have not measured. Load-test your own
# workload and watch the ServerlessDatabaseCapacity and ACUUtilization
# CloudWatch metrics to see how your cluster actually behaves.
Clone Database (Copy-on-Write)

Create full database clones in minutes for testing, development, or analytics without copying data. Uses copy-on-write, so clones only consume storage for changed data.

# Clone production database for testing
aws rds restore-db-cluster-to-point-in-time \
  --source-db-cluster-identifier production-cluster \
  --db-cluster-identifier test-clone \
  --restore-type copy-on-write \
  --use-latest-restorable-time

# The clone is available quickly because no data is copied up front: it
# shares the source's storage pages and only allocates new ones as either
# side writes (copy-on-write). So initial storage cost is near zero and
# grows with divergence, not with the size of the source database.
# Test schema changes safely on the clone; drop it when done, with no
# impact on production. AWS doesn't publish a fixed creation time, so
# measure it on your own cluster rather than planning around a number.
Read Replicas (up to 15)

Aurora supports up to 15 read replicas in the same region; AWS documents replica lag as usually well under 100ms, because replicas read the same shared storage volume rather than replaying a log. Distribute read traffic across replicas for read scaling.

# Application connection routing
# Write endpoint (primary instance)
writer_endpoint = "my-cluster.cluster-xyz.us-east-1.rds.amazonaws.com"

# Read endpoint. It balances each *connection request* across the replicas,
# not each query: every statement on one connection lands on whichever
# replica that connection opened against. A pooled engine like the one below
# therefore pins each pooled connection to one replica for its lifetime.
# Caveat: if the cluster has no replicas at all, the reader endpoint connects
# to the primary, and writes through it will silently succeed.
reader_endpoint = "my-cluster.cluster-ro-xyz.us-east-1.rds.amazonaws.com"

# Python example with SQLAlchemy
from sqlalchemy import create_engine, text

# Writes go to primary
write_engine = create_engine(f"postgresql://user:pass@{writer_endpoint}/db")

# Reads use read replicas
read_engine = create_engine(f"postgresql://user:pass@{reader_endpoint}/db")

# Insert user (write operation)
with write_engine.connect() as conn:
    conn.execute(text("INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"))
    conn.commit()

# Query users (read operation, served by whichever replica this connection
# was balanced onto). Aurora Replicas are asynchronous, so a read issued
# immediately after the commit above may not see Alice yet. Read your own
# writes from write_engine, or wait/retry, rather than assuming replicas are
# current.
with read_engine.connect() as conn:
    result = conn.execute(text("SELECT * FROM users WHERE name = 'Alice'"))
    print(result.fetchall())
Pricing Model
Storage$0.10/GB/month, auto-scales, pay only for used space
Compute$0.29-$4.05/hour (instance-based) or $0.12/ACU-hour (serverless)
I/O Requests$0.20/million requests (can be significant for write-heavy workloads)
When to Choose Aurora: AWS-centric architecture, need MySQL/PostgreSQL compatibility, regional high availability is sufficient, cost-sensitive workloads, read-heavy applications that benefit from read replicas, or variable workloads that benefit from Aurora Serverless v2.

Google Cloud Spanner: Global Strong Consistency

Cloud Spanner is Google's globally-distributed relational database that achieves something most thought impossible: strong consistency across continents. Using atomic clocks (TrueTime API) and sophisticated consensus protocols, Spanner provides ACID guarantees for transactions that span the globe. It's the database powering Google's most critical services like AdWords and Google Play.

Architecture Highlights

TrueTime API

Hardware atomic clocks and GPS clocks in every datacenter provide a global time reference with bounded uncertainty. This enables external consistency - transactions commit in a globally consistent order.

Paxos Consensus

Data is automatically sharded and replicated across zones/regions using Paxos algorithm for leader election. Every write is synchronously replicated to majority of replicas before commit.

External Consistency

Stronger than ACID. If transaction T1 commits before T2 starts (in real time), all observers see T1's effects before T2's effects. No eventual consistency anomalies.

Key Features

Global ACID Transactions

Execute transactions that read/write data across multiple continents with full ACID guarantees. No eventual consistency, no conflict resolution - it just works correctly.

# Create multi-region Spanner instance
gcloud spanner instances create global-inventory \
  --config=nam-eur-asia3 \
  --description="Multi-region inventory system" \
  --processing-units=1000

# Schema with interleaved tables (co-located for performance)
CREATE TABLE Warehouses (
  WarehouseId INT64 NOT NULL,
  Location STRING(100),
  Region STRING(50)
) PRIMARY KEY (WarehouseId);

CREATE TABLE Inventory (
  WarehouseId INT64 NOT NULL,
  ProductId INT64 NOT NULL,
  Quantity INT64,
  LastUpdated TIMESTAMP NOT NULL OPTIONS (
    allow_commit_timestamp=true
  )
) PRIMARY KEY (WarehouseId, ProductId),
  INTERLEAVE IN PARENT Warehouses ON DELETE CASCADE;

# One transaction moving stock between two warehouse rows.
BEGIN TRANSACTION;

# Warehouse 1001
UPDATE Inventory
SET Quantity = Quantity - 100,
    LastUpdated = PENDING_COMMIT_TIMESTAMP()
WHERE WarehouseId = 1001 AND ProductId = 5000;

# Warehouse 2001
UPDATE Inventory
SET Quantity = Quantity + 100,
    LastUpdated = PENDING_COMMIT_TIMESTAMP()
WHERE WarehouseId = 2001 AND ProductId = 5000;

COMMIT TRANSACTION;

# Spanner ensures:
# 1. Both updates succeed or both fail (atomicity)
# 2. All observers see consistent state (no partial updates visible)
# 3. Transaction commits in globally consistent order
#
# A common misreading: rows do NOT live in a region. In a multi-region
# instance every replica holds the whole database, so "the US warehouse row"
# is not stored in nam any more than in eur. What the instance config
# decides is where the *replicas* are, and specifically which regions are
# read-write. Commit latency is set by how far apart those read-write
# regions are, because a commit needs a Paxos quorum among them, and by
# where the leader currently sits, not by which warehouse ids you touched.
# In nam-eur-asia3 the read-write regions are us-central1 and us-east1;
# europe-west1 and asia-east1 are read-only replicas that serve local reads
# without voting on writes. Measure commit latency for your own config
# rather than assuming a figure.
Use case: Financial trading platform where trades must be atomic across accounts in different regions. A stock sale in Asia and corresponding purchase in North America must both succeed or both fail - no partial states.
Interleaved Tables for Performance

Parent-child relationships can be physically co-located on the same servers for faster JOINs and transactions. Spanner stores related rows together.

-- Parent table: Customers
CREATE TABLE Customers (
  CustomerId INT64 NOT NULL,
  Name STRING(100),
  Email STRING(255)
) PRIMARY KEY (CustomerId);

-- Child table: Orders (interleaved in Customers)
CREATE TABLE Orders (
  CustomerId INT64 NOT NULL,
  OrderId INT64 NOT NULL,
  TotalAmount NUMERIC,
  OrderDate TIMESTAMP
) PRIMARY KEY (CustomerId, OrderId),
  INTERLEAVE IN PARENT Customers ON DELETE CASCADE;

-- Query customer with all orders (single-server operation)
SELECT c.Name, c.Email, o.OrderId, o.TotalAmount
FROM Customers c
JOIN Orders o ON c.CustomerId = o.CustomerId
WHERE c.CustomerId = 12345;

-- Because Orders is interleaved, this JOIN executes on a single
-- Spanner server without network hops. Much faster than distributed JOIN.
Strong Reads vs Stale Reads

Choose between strong reads (globally consistent, higher latency) or stale reads (eventually consistent, lower latency) based on your use case.

-- Strong read (default): Globally consistent
SELECT ProductId, Quantity FROM Inventory
WHERE WarehouseId = 1001 AND ProductId = 5000;
-- A strong read is served at a timestamp at least as recent as every
-- committed transaction, so it may have to check with the Paxos leader
-- before answering. That round trip is the cost you are paying.

-- Stale read (lower latency). Staleness is a session/transaction setting,
-- not a per-query one. In the GoogleSQL dialect used by this schema the
-- statement has no SPANNER. prefix, and it is understood by the Spanner JDBC
-- driver rather than by the client libraries or gcloud:
SET READ_ONLY_STALENESS = 'MAX_STALENESS 10s';

--   MAX_STALENESS 10s   -> Spanner picks a timestamp at most 10s old, and is
--                          free to pick a fresher one. This is what you want
--                          for "recent enough" reads.
--   EXACT_STALENESS 10s -> read as of exactly now-10s, no fresher.
-- (The PostgreSQL dialect spells the same setting SPANNER.READ_ONLY_STALENESS.)

SELECT ProductId, Quantity FROM Inventory
WHERE WarehouseId = 1001 AND ProductId = 5000;
-- A sufficiently stale read can be served by the nearest replica out of its
-- own state, with no leader round trip. That is where the saving comes from.

-- How stale can you go? Not 15 seconds: the real ceiling is the database's
-- version_retention_period, which defaults to 1 hour and is configurable up
-- to 1 week. Older versions are garbage-collected and become unreadable.
-- The number that matters at the short end is a floor, not a ceiling: Google
-- advises at least 10 seconds of staleness before a stale read is fast
-- enough to be worth it, because below that Spanner often still has to
-- consult the leader.

-- Use strong reads for:
-- - Financial transactions
-- - Inventory that must be exact
-- - Critical consistency requirements

-- Use stale reads for:
-- - Analytics dashboards
-- - Product catalogs
-- - Reports where slight delay is acceptable
Automatic Sharding & Scaling

Spanner automatically splits tables into shards (called "splits") and distributes them across servers. As data grows or hotspots emerge, Spanner rebalances automatically.

How it works: Spanner monitors key ranges and splits them when a range gets too large for one server, or too hot (some rows read far more often than others: Google's own example is adding split boundaries around 10 unusually popular rows so each lands on a different server). Google does not publish the exact size threshold, so do not design around a specific number. Splits are moved across servers to balance load. Completely transparent - no application changes needed.
99.999% Availability SLA (5 Nines)

Multi-region Spanner instances provide 99.999% uptime - that's only 5.26 minutes of downtime per year. Automatic failover is transparent with no application changes.

Regional Config99.99% SLA (4 nines)
~52 minutes downtime/year
Multi-Region Config99.999% SLA (5 nines)
~5 minutes downtime/year
Pricing Model
Node Hours$0.90-$9.00/hour/node (regional vs multi-region)
Storage$0.30/GB/month (higher than Aurora due to multi-region replication)
Network EgressStandard Google Cloud pricing (free within region)
When to Choose Spanner: Need global strong consistency (not eventual), multi-region active-active writes, financial transactions or inventory systems where consistency is critical, 99.999% uptime requirement, can justify premium pricing for unique guarantees, or horizontal scaling across continents.

Azure: Multiple Paths to Scale

Microsoft Azure offers three distinct approaches to scaling relational databases, each targeting different compatibility and scaling needs. Cosmos DB for PostgreSQL brings horizontal scaling to PostgreSQL, SQL Database Hyperscale extends SQL Server to massive scale, and the upcoming HorizonDB promises cloud-native PostgreSQL performance.

Azure Cosmos DB for PostgreSQL (Citus)

Built on the Citus open-source extension, Cosmos DB for PostgreSQL enables horizontal scaling by distributing tables across multiple worker nodes. Perfect for multi-tenant SaaS applications and real-time analytics on massive datasets.

Read this before you pick it: Microsoft has put Azure Cosmos DB for PostgreSQL on a retirement path and no longer recommends it for new projects (as of July 2026). The successor for PostgreSQL workloads is Elastic Clusters in Azure Database for PostgreSQL, which exposes the same Citus horizontal scale-out. Everything below still describes how Citus-based sharding works, and that knowledge transfers directly, but start new builds on Elastic Clusters rather than here.
Architecture
Coordinator NodeRoutes queries to worker nodes, maintains metadata, handles non-distributed tables
Worker NodesStore sharded data, execute queries in parallel, scale horizontally by adding nodes
Try it locally (Citus 12.1)

This is the one example in the lesson you can actually run. Citus is the same open-source extension underneath Azure's managed service, so the sharding behaviour below is the real thing rather than a simulation. The Aurora, Spanner and Hyperscale sections are cloud CLI calls and dialects PostgreSQL will not parse, so read those as reference.

# The managed services on this page cannot run locally, but Citus can:
# it is the same extension behind Azure Cosmos DB for PostgreSQL.
docker run -d --name citus-demo \
  -e POSTGRES_PASSWORD=demo -e POSTGRES_USER=demo -e POSTGRES_DB=demo \
  -p 5432:5432 citusdata/citus:12.1

docker exec -it citus-demo psql -U demo -d demo
# Then run the create_distributed_table example below.

# Clean up:  docker rm -f citus-demo
-- Create distributed table for multi-tenant SaaS
CREATE TABLE events (
  tenant_id bigint,
  event_id bigserial,
  event_type text,
  event_data jsonb,
  created_at timestamptz default now()
);

-- Distribute table across worker nodes by tenant_id
-- All data for a tenant is co-located on same shard
SELECT create_distributed_table('events', 'tenant_id');

-- Create reference table (replicated to all workers)
CREATE TABLE event_types (
  type_id serial primary key,
  type_name text,
  description text
);
SELECT create_reference_table('event_types');

-- Queries are automatically routed and parallelized
-- Query for single tenant (executes on 1 worker node)
SELECT COUNT(*) FROM events
WHERE tenant_id = 12345
  AND created_at > NOW() - INTERVAL '1 day';

-- Query across all tenants (executes in parallel on all workers)
SELECT tenant_id, COUNT(*) as event_count
FROM events
WHERE created_at > NOW() - INTERVAL '1 day'
GROUP BY tenant_id
ORDER BY event_count DESC
LIMIT 100;

-- Citus parallelizes query across workers, aggregates results
Key Features
  • Real PostgreSQL plus the Citus extension, so the wire protocol, drivers and most SQL carry over unchanged; distributed tables do add restrictions, so it is not a drop-in for every schema
  • Columnar storage for analytical scans (compression and column pruning; the speedup depends entirely on the query)
  • Start with 1 node, add workers on demand
  • Up to 32 TiB of storage per worker node, 104 vCores
  • High availability with automatic failover
  • Ideal for multi-tenant applications (tenant_id sharding)
Use case: Multi-tenant SaaS application with thousands of customers. Each customer's data is isolated on the same shard for fast single-tenant queries. Cross-tenant analytics (dashboards, reporting) leverage parallel query execution.

Azure SQL Database Hyperscale

For SQL Server workloads that need to scale beyond traditional limits. Hyperscale separates compute from storage, supports databases up to 100 TB, and provides rapid scaling with named replicas for read scale-out.

-- T-SQL, run against the master database of the logical server.
-- Create Hyperscale database
CREATE DATABASE ProductionDB
  (EDITION = 'Hyperscale', SERVICE_OBJECTIVE = 'HS_Gen5_8');

-- Create a named replica for read scale-out. A named replica is its own
-- Azure SQL database resource: own name, own service objective, optionally
-- on a different logical server in the same region. Up to 30 of them.
-- (Separately, you can run 0-4 HA replicas, which are invisible hot
-- standbys for failover, not addressable databases.)
ALTER DATABASE ProductionDB
ADD SECONDARY ON SERVER 'replica-eastus2'
WITH (
  SERVICE_OBJECTIVE = 'HS_Gen5_4',
  SECONDARY_TYPE = 'Named'
);


/* Application connection strings (not T-SQL)

   Primary (writes):
     Server=primary.database.windows.net;
     Database=ProductionDB;
     ApplicationIntent=ReadWrite;

   Named replica (reads). You connect to the replica by name, so
   ApplicationIntent is not what routes you there. ApplicationIntent=ReadOnly
   is how you reach an *HA* replica on the primary's own connection string,
   and if no secondary exists it silently falls back to the read-write primary:
     Server=replica-eastus2.database.windows.net;
     Database=ProductionDB;
*/


-- Point-in-time restore, via Azure CLI (bash, not T-SQL).
-- Hyperscale backups are storage snapshots, so a same-region restore
-- "finishes in minutes instead of hours or days, even for multi-terabyte
-- databases" (Microsoft's wording): it is not a size-of-data operation.
-- Two exceptions that are size-of-data, and therefore slow:
--   * geo-restore to another region
--   * changing storage/zone redundancy as part of the restore
az sql db restore \
  --dest-name ProductionDB_Recovered \
  --resource-group myResourceGroup \
  --server myServer \
  --name ProductionDB \
  --time "2026-01-30T14:30:00"
Key Features
  • Up to 100 TB database size
  • Rapid backup & restore (minutes vs hours)
  • Zero to four HA replicas (hot standbys, automatic failover) plus up to 30 named replicas for read scale-out
  • T-SQL compatibility (SQL Server features)
  • Zone-redundant availability
  • Microsoft-published benchmarks report strong performance vs comparable cloud databases (verify against your own workload)
Use case: Enterprise application migrating from on-premise SQL Server that has grown to 50 TB. Hyperscale provides SQL Server compatibility while handling massive scale. Named replicas serve reporting workloads without impacting OLTP performance.

Azure HorizonDB (Public Preview)

Microsoft's cloud-native PostgreSQL-compatible service, announced at Ignite 2025 and moved to public preview at Build 2026. Its storage engine is written in Rust, chosen for memory safety. Microsoft claims up to 3x more throughput than open-source PostgreSQL on transactional workloads.

Highlighted Features (Preview)
  • Disaggregated compute and storage (like Aurora)
  • Scale-out compute: up to 3,072 vCores
  • Storage: up to 128 TB
  • Rust-based storage engine for security & performance
  • Native AI capabilities (vector search, model management)
  • Sub-millisecond multi-zone commit latencies
  • PostgreSQL 19 contributions by Microsoft
Note: HorizonDB is in public preview, not generally available (as of July 2026), and available in a limited set of regions. It shows promise as a future competitor to Aurora and AlloyDB, but production use should wait for GA. Every throughput figure here is Microsoft's own; treat it as a vendor claim until you benchmark your workload.

Feature Comparison & Decision Guide

Complete Feature Matrix

FeatureAWS AuroraGoogle SpannerCosmos DB PostgreSQLAzure SQL Hyperscale
Max Database Size128 TB (auto-scaling)Unlimited (petabytes)32 TiB per worker node100 TB
Consistency ModelEventually consistent (cross-region)Globally strong (external consistency)Eventually consistentEventually consistent (replicas)
Multi-Region WritesNo (1 primary region)Yes (active-active)No (1 primary)No (1 primary)
Availability SLA99.99% (4 nines)99.999% (5 nines, multi-region)99.99%99.99%
Failover Time30-120 secondsTransparent (no downtime)60-120 seconds30-120 seconds
SQL CompatibilityMySQL, PostgreSQLPostgreSQL, GoogleSQLPostgreSQL (Citus extension)SQL Server (T-SQL)
Auto-Scaling StorageYes (10 GB - 128 TB)Yes (unlimited)Manual (add worker nodes)Yes (up to 100 TB)
Serverless OptionAurora Serverless v2Autoscaling computeNoServerless tier available
Read ReplicasUp to 15 (same region)Unlimited read-only replicasHorizontal scaling with workers0-4 HA replicas + up to 30 named replicas
Point-in-Time RestoreUp to 35 daysUp to 7 daysUp to 35 daysUp to 35 days
Backtrack / Time TravelAurora MySQL only, up to 72 hours (not available on Aurora PostgreSQL)Version history (7 days)NoNo
Clone DatabaseYes (copy-on-write)NoNoDatabase copy (not instant)

Performance Characteristics

Only one of these four vendors publishes per-node throughput numbers, and it brackets them as estimates. Any table handing you a tidy "queries/sec" for all four has invented three of them. So this one lists what is actually published, and otherwise describes what determines latency on each engine, which is the part that transfers. Benchmark your own workload: query shape, row size, index design and transaction scope move these numbers by an order of magnitude.
MetricAuroraSpannerCosmos DB PGSQL Hyperscale
Published per-node throughputNone publishedPer node (1,000 processing units), SSD: about 22,500 read QPS and 3,500 write QPS regional; about 15,000 read QPS per region and 2,700 write QPS multi-region. Google labels these "estimates only", measured on single-row, 1 KB, read-only or write-only workloads.None publishedNone published
What sets write latencyQuorum write to 4 of 6 storage nodes across 3 AZs in one region. Cross-region replication is asynchronous, so it adds lag on the secondary rather than latency on the commit.Paxos quorum among the read-write regions of the instance config, plus TrueTime commit-wait. Read-only replicas do not vote, so adding a continent of read replicas does not by itself slow commits.Single-region writes; a transaction spanning several shards pays two-phase commit across the workers it touches.Commit is a write to the log service; page servers apply asynchronously.
Horizontal ScalingRead replicas onlyFull (read & write)Add worker nodesRead replicas only

Pricing Comparison

List prices below are US-region figures gathered in July 2026 and are here to show the shape of each pricing model, not to be quoted. Cloud prices change, vary by region, and ignore committed-use and reserved discounts. Price your own workload with each vendor's calculator before deciding.
AWS Aurora
  • Storage: $0.10/GB/month
  • Compute: $0.29-$4.05/hour (instance) or $0.12/ACU-hour (serverless)
  • I/O: $0.20/million requests
  • Example: 1 TB storage + db.r6g.xlarge 24/7 = $100 + $263 + ~$50 I/O = ~$413/month
Google Spanner
  • Compute: $0.90/hour/node (regional) or $3.00/hour/node (multi-region)
  • Storage: $0.30/GB/month
  • Network: Standard egress charges
  • Example: 1 TB storage + 3 nodes multi-region 24/7 = $300 + $6,480 = $6,780/month
Cosmos DB PostgreSQL
  • Compute: $0.24-$1.92/vCore-hour (depends on tier)
  • Storage: $0.20/GB/month
  • High Availability: +100% for standby
  • Example: 1 TB storage + 4 vCore coordinator + 2x 8 vCore workers = ~$500/month
Azure SQL Hyperscale
  • Compute: $1.00-$14.00/vCore-hour
  • Storage: $0.119/GB/month
  • Backup storage: $0.20/GB/month
  • Example: 1 TB storage + Gen5 8 vCore 24/7 = $119 + $1,400 = $1,519/month
Cost Observation: Aurora is most cost-effective for regional workloads. Spanner is premium-priced for global consistency guarantees. Azure options fall in between, with Cosmos DB PostgreSQL competitive for horizontal scaling needs.

Decision Guide

Choose AWS Aurora if:
  • Your infrastructure is primarily on AWS
  • Need MySQL or PostgreSQL compatibility
  • Regional high availability is sufficient
  • Cost-sensitive workload (best price/performance for regional)
  • Read-heavy applications that benefit from 15 read replicas
  • Variable workloads that benefit from Aurora Serverless v2
  • Need instant database cloning or backtrack for testing
Choose Google Cloud Spanner if:
  • Need global strong consistency (not eventual)
  • Multi-region active-active writes are required
  • Financial transactions, inventory, or other use cases where consistency is critical
  • 99.999% uptime requirement (5 nines SLA)
  • Can justify premium pricing for unique global consistency guarantees
  • Horizontal scaling across continents with ACID guarantees
  • Need transparent failover with zero downtime
Choose Citus on Azure (Elastic Clusters, or the retiring Cosmos DB for PostgreSQL) if:
  • Your infrastructure is primarily on Azure
  • Multi-tenant SaaS application (tenant-based sharding)
  • Need PostgreSQL compatibility with horizontal scaling
  • Real-time analytics dashboards on operational data
  • Want open-source foundation (Citus) to avoid lock-in
  • Time-series data or event logging at scale
Choose Azure SQL Database Hyperscale if:
  • Your infrastructure is primarily on Azure
  • SQL Server compatibility is required (T-SQL, CLR, SQL Agent)
  • Need massive single database (>10 TB)
  • Migrating from on-premise SQL Server
  • Fast backup & restore is critical (snapshot-based)
  • Need zone redundancy for regional high availability

Key Trade-offs to Consider

Consistency vs Latency

Spanner's global consistency comes with higher latency (50-100ms for writes). Aurora's eventual consistency provides lower latency (<10ms) but cross-region reads may be stale. Choose based on whether correctness or speed matters more.

Cost vs Features

Aurora is most economical for regional workloads. Spanner costs 10-15x more but provides unique global guarantees. Cosmos DB PostgreSQL offers middle ground with horizontal scaling. Consider whether premium features justify premium pricing.

Complexity vs Capabilities

Aurora and SQL Hyperscale are simpler to operate (single primary, read replicas). Spanner and Cosmos DB PostgreSQL require understanding sharding, distribution keys, and global replication. More capability means more complexity.

Vendor Lock-in vs Best-of-Breed

Aurora (AWS), Spanner (GCP), and Azure options tie you to a cloud provider. Cosmos DB PostgreSQL uses Citus (open source) for easier migration. Consider whether best-in-class features justify potential lock-in.

Key Takeaways

  • Scaling relational databases requires trade-offs. Aurora prioritizes regional performance and cost-effectiveness. Spanner prioritizes global consistency at premium pricing. Azure offers multiple paths based on compatibility needs.
  • Aurora excels for AWS-centric regional workloads. Decoupled storage, 15 read replicas, Aurora Serverless v2, backtrack, and clone features make it ideal for cost-sensitive applications needing MySQL/PostgreSQL compatibility.
  • Spanner is unique for global strong consistency. TrueTime and Paxos consensus enable ACID transactions across continents. Worth the premium price when correctness matters more than cost (financial services, inventory systems).
  • Citus-based sharding on Azure shines for multi-tenant SaaS. Horizontal sharding with tenant isolation, and an open-source foundation that reduces lock-in. Note the branding moved: Cosmos DB for PostgreSQL is retiring in favour of Elastic Clusters in Azure Database for PostgreSQL. Same Citus underneath.
  • SQL Hyperscale extends SQL Server to massive scale. Best for Azure workloads needing T-SQL compatibility with fast backups, named replicas, and up to 100 TB databases. Zone redundancy provides regional high availability.
  • Choose based on requirements, not hype. Most applications don't need global consistency - Aurora's regional replication suffices. Only pay for Spanner's guarantees if you truly need them. Consider cloud ecosystem, compatibility, and cost.
  • Industry adoption: Aurora dominates AWS ecosystems, Spanner powers Google's critical services and high-consistency use cases, Azure options serve enterprise migrations and PostgreSQL/SQL Server workloads at scale.