Why architecture matters here

Relational databases are where the hardest operational problems concentrate: they hold irreplaceable state, they scale vertically before horizontally, and their failure semantics involve fsyncs, WAL recovery, and replication races that most teams meet for the first time mid-incident. The case for Cloud SQL's architecture is that it moves the sharpest of those problems — durable storage, crash recovery, failover orchestration, backup integrity — behind an SLA, while leaving you the parts only you can do: schema, queries, connection discipline, and honest RPO/RTO math.

The architecture matters because HA designs differ in ways that surface as application behavior. Cloud SQL's shared-regional-disk model guarantees zero committed-write loss on zonal failover — the standby mounts the same blocks the primary flushed — which logical async replication cannot promise. In exchange, the standby is cold: failover includes engine start and WAL crash recovery, so RTO is not zero, and every application must tolerate a one-to-two-minute write outage gracefully — retry with backoff, degrade reads, shed writes — rather than assuming the database is a constant. Teams that design for this find zonal failures are non-events; teams that don't find every failover is a mini-outage of their own making, as connection pools hang on dead sockets and thundering-herd reconnects arrive before the standby is ready.

The second reason architecture matters here is cost and freshness trade-offs are explicit. Read replicas multiply read throughput and give cross-region DR, but they are asynchronous: lag is normally sub-second and occasionally minutes (bulk writes, vacuum storms, replica undersizing). Whether a given read can tolerate that lag is an application-level correctness decision — read-your-own-writes flows cannot — and the topology only works when someone has made that decision per query path, not per outage.

Advertisement

The architecture: every piece explained

Top row — the connection path. Applications should not speak to Cloud SQL by IP allowlist; the Auth Proxy (sidecar/binary) or language connectors establish mutually-authenticated TLS using IAM identities — credentials are service accounts, rotation is Google's problem, and private IP via VPC peering keeps traffic off the internet. The primary instance is a single read-write engine in one zone, its data on a regional persistent disk: every block write is synchronously committed in two zones before the engine's fsync returns. The standby in the second zone is provisioned but cold — no queries, no logical replication stream; it exists to attach the disk and become the primary.

The failover controller health-checks the primary; on sustained failure (zone outage, instance crash, unresponsive engine) it fences the old primary, attaches the regional disk to the standby, starts the engine — which runs normal WAL crash recovery, exactly as if the database had power-cycled — and repoints the instance's DNS name/IP to the new node. Client-side, this appears as dropped connections followed by a window of connection refusals; well-behaved applications reconnect with jittered backoff and resume. The same machinery, invoked deliberately, is a failover drill or a reduced-downtime maintenance move.

Middle row — reads, backups, time travel. Read replicas receive asynchronous WAL streaming — in-region for read scaling, cross-region for DR; each can be promoted (manually) into a standalone primary, which is the region-loss story: nonzero RPO, minutes of RTO, your runbook. Automated backups snapshot daily; PITR keeps the WAL archive so you can restore a new instance at any minute in the retention window — the only defense against mistakes that replication faithfully propagates, like a DELETE missing its WHERE. Bottom row — the two chronic constraints. Connection pooling: Postgres pays real memory per connection and max_connections is finite, while Cloud Run/Functions scale instances without asking; PgBouncer (or the managed pooler) in transaction mode multiplexes thousands of client connections onto tens of server ones. And the maintenance window: minor-version patches restart the engine; you choose when that is survivable.

Cloud SQL — regional HA + synchronous disk replication + replicas + connectorsmanaged Postgres/MySQL with failover as a serviceApplicationsGKE, Cloud Run, GCEAuth Proxy / connectorIAM auth, TLS, no IP mgmtPrimary instancezone A, read-writeStandby instancezone B, cold, same diskRegional persistent disksynchronous 2-zone writesRead replicasasync WAL, cross-region okAutomated backupsdaily + on-demandPITR / WAL archiverestore to any minuteConnection pooling (PgBouncer)serverless bursts vs max_connectionsMaintenance + failover controllerhealth checks, DNS repointOps — replication lag + failover drills + slow query insights + storage autogrow watchSQL over TLSpromote on failsync writeasync WALarchivepooled connshealth + repoint
Cloud SQL regional HA: the primary writes synchronously to a two-zone regional disk, a cold standby in the second zone takes over on failure, while async read replicas, PITR archives, and pooled connections complete the topology.
Advertisement

End-to-end flow

Walk the topology through a Tuesday. A checkout service on Cloud Run connects through the connector with its service account; PgBouncer multiplexes its bursty instances onto 40 steady server connections. An order INSERT commits: the engine writes WAL, the regional disk acknowledges the block in zone A and zone B, fsync returns, the transaction is durable in two zones — added write latency over a zonal disk: single-digit percent. Reporting dashboards hit the in-region read replica (lag: 200ms, monitored); the analytics batch hits the cross-region replica in europe-west1.

At 14:02, zone A degrades. The primary's health checks fail for the controller's confirmation window; at 14:03 it fences the old primary, attaches the regional disk to the standby in zone B, and starts Postgres. Crash recovery replays the WAL tail — the last checkpoints were recent, so 40 seconds — and at 14:04 DNS repoints. The checkout service saw connections drop at 14:02; its pool marked them dead, retried with exponential backoff and jitter, served reads from cache, and queued two minutes of writes behind a circuit breaker. At 14:04 the pool reconnects — gradually, because PgBouncer caps server-side connection creation, so the recovering primary is not greeted by a 3,000-connection stampede. Committed orders lost: zero. Pages sent: one, informational.

At 14:30 a different incident: a migration script UPDATEs every row of prices. Replication dutifully copies the damage to both replicas within a second — replicas protect against hardware, never against SQL. The fix is PITR: restore a new instance from the WAL archive to 14:29:30, extract the prices table, reconcile the twenty-minute delta from the audit log, and swap corrected data back. Total exposure: an hour of degraded pricing, no restore-over-production gamble. The postmortem's action items are architectural, not heroic: migration dry-runs against a PITR clone, and a lag alarm that had already proven itself that morning.