GCP Reliability and Disaster Recovery Design
gcp/databases.md, gcp/gke.md, and gcp/storage.md each already cover the
per-service HA and replication mechanics in depth — Cloud SQL's regional
standby, Spanner's TrueTime commit-wait, GKE's regional control plane, GCS's
multi-region replication. What's missing is the layer above all of that: a
single framework for deciding how much of that machinery a given workload
actually needs, and how compute, data, and networking choices have to move
together during a real failure. This file is that synthesizing layer — it
cross-references the per-service mechanics rather than re-deriving them.
Start From RTO/RPO, Not From the Architecture
Two numbers should exist before anyone picks a database tier or a cluster topology:
- RTO (Recovery Time Objective): how long the service is allowed to be down before the business impact is unacceptable.
- RPO (Recovery Point Objective): how much data — measured in time — the business can afford to lose if the primary disappears mid-write.
The common mistake runs backwards: a team picks "multi-region" because it sounds robust, then reverse-engineers a justification for the cost. The correct order is to state RTO/RPO first as an actual SLA-backed number, then let the architecture fall out of it. A workload with an RTO of 4 hours and an RPO of 1 hour needs almost none of the machinery a workload with an RTO of 30 seconds needs — and paying for the latter when you only need the former is a standing cost, not a one-time decision.
| RTO / RPO band | Pattern | Typical GCP shape | Relative cost |
|---|---|---|---|
| RTO: hours–days · RPO: hours | Backup and restore | Cloud SQL automated backups + PITR restored into a new instance; GKE Backup for GKE restored into a freshly created cluster; GCS cross-bucket copy via Storage Transfer Service | $ |
| RTO: 10s of minutes · RPO: minutes | Pilot light | A minimal, always-on core kept warm — a small cross-region Cloud SQL read replica, an empty or near-empty standby GKE cluster with manifests staged — scaled up only on failover | $$ |
| RTO: minutes · RPO: seconds | Warm standby | A full-shape but scaled-down replica stack running continuously in the second region — GKE regional cluster at low replica count, Cloud SQL cross-region replica already caught up, GCS dual-region bucket | $$$ |
| RTO: near-zero · RPO: near-zero | Multi-site active/active | Spanner multi-region config (no failover step exists), GKE regional clusters in 2+ regions serving live traffic simultaneously behind one Global External LB, GCS multi-region bucket | $$$$ |
A team wants "the most resilient setup possible" for a workload with no stated RTO/RPO target. What's the actual first step, before picking any GCP service?
Google's Four DR Patterns
This is Google's own documented DR framework, and it maps directly onto the mechanisms already covered elsewhere in this section — nothing below is a new mechanic, only a new way of grouping the ones that already exist.
gcloud sql instances promote-replica, per gcp/databases.md) and the cluster's autoscaler adds capacity. Spanner has no real pilot-light tier — multi-region Spanner is already past this pattern entirely.
NAM4, per gcp/storage.md) already replicates synchronously with a Turbo Replication SLA. Failover is "promote and scale out," not "provision" — the main worked walkthrough below is this pattern.
gcp/databases.md's TrueTime section, every region already accepts strongly consistent reads and writes continuously, so there's nothing to promote. GKE regional clusters in 2+ regions behind one Global External LB and a GCS multi-region bucket complete the picture. Cloud SQL cannot do this — it's single-writer by architecture, which is exactly the gap Spanner exists to close.
Does Cloud Spanner in a multi-region configuration need a manual failover step the way Cloud SQL's replica promotion does?
promote-replica action to become writable — that promotion step is exactly where a chunk of RTO gets spent. Spanner multi-region has no equivalent step: every region is already a live participant in the same strongly consistent database via TrueTime's commit-wait, so losing one region doesn't require promoting anything — the remaining regions were already serving reads and writes. This is the core reason Spanner is Google's answer to the multi-site active/active pattern specifically, and why it "skips" the pilot-light/warm-standby patterns that exist mainly to work around a single-writer architecture.Multi-Zone vs Multi-Region: An Explicit Tradeoff
Most production workloads should default to regional HA (multi-zone), not multi-region. Zone failures are the statistically dominant failure mode a cloud workload actually experiences — a rack loses power, a zone's network fabric degrades — and regional HA absorbs exactly that at a fraction of the cost and operational complexity of running two full regional footprints. Multi-region is worth its cost when there's a genuine near-zero-RTO requirement, a regulatory data-residency mandate, or the workload is inherently global (Spanner's own sweet spot) — not as a default "just in case" posture.
--availability-type=REGIONAL), a zonal GKE cluster, a regional GCS bucket. Cheapest possible tier, and the correct choice for dev/test or genuinely disposable workloads — but a single zone outage is a full outage, with no automatic failover anywhere in the stack.
--availability-type=REGIONAL (synchronous standby in a different zone, per gcp/databases.md), a regional GKE cluster (control plane replicated across 3 zones, nodes spread across zones, per gcp/gke.md), and a regional GCS bucket. Survives the dominant real-world failure mode — one zone going down — with automatic failover and no cross-region latency or replication-lag tradeoffs to reason about.
A team argues every production service should run multi-region "to be safe." What failure-mode argument pushes back on that as the default?
Worked Walkthrough: Regional Outage Failover
Scenario: a 3-tier app sits behind a Global External LB, with GKE regional clusters in two regions and a Cloud SQL primary plus a continuously-caught-up cross-region read replica — the warm standby pattern from above.
graph TD
classDef lb fill:#4285F4,stroke:#1a73e8,color:#fff
classDef gke fill:#34A853,stroke:#188038,color:#fff
classDef db fill:#EA4335,stroke:#c5221f,color:#fff
classDef standby fill:#FBBC04,stroke:#f9ab00,color:#000
CLIENT["Clients worldwide"] --> GLB["Global External LB<br/>single Anycast IP<br/>health-checks both regions"]:::lb
subgraph REGA["us-central1 — primary"]
GKEA["GKE regional cluster<br/>full replica count"]:::gke
SQLA["Cloud SQL primary<br/>REGIONAL HA"]:::db
GKEA --> SQLA
end
subgraph REGB["us-east1 — warm standby"]
GKEB["GKE regional cluster<br/>scaled-down replica count"]:::standby
SQLB["Cloud SQL cross-region<br/>read replica"]:::standby
GKEB --> SQLB
end
GLB -->|"active traffic"| GKEA
GLB -.->|"standby, promoted on failover"| GKEB
SQLA -.->|"async replication"| SQLB
A GCP-specific nuance worth calling out before the timeline: the Global External LB serves one Anycast IP address for both regions, not a DNS record pointing at a region-specific endpoint. That means failover at the networking layer doesn't wait on DNS TTL propagation the way Route 53 failover does — the LB itself simply stops routing new connections to the unhealthy backend. The RTO clock here is dominated by something else entirely.
sequenceDiagram
participant HC as Health Check
participant GLB as Global External LB
participant GKEB as GKE us-east1
participant SQLB as Cloud SQL replica
Note over HC,GLB: t=0s region us-central1 loses power
HC->>HC: probe every 5s, unhealthy after 3 consecutive failures
Note over HC,GLB: t=15s backend marked UNHEALTHY, no DNS change involved
GLB->>GKEB: shift new connections to us-east1 NEG
GKEB->>GKEB: HPA scales pods from low replica count to full capacity
Note over GKEB: t=15s to roughly t=60s, pod scale-up dominates this phase
GKEB->>SQLB: gcloud sql instances promote-replica
Note over GKEB,SQLB: t=60s to roughly t=240s, replica catch-up plus promotion
GKEB->>SQLB: app reconnects to the promoted endpoint
Note over GKEB,SQLB: t=240s to roughly t=250s, connection pool re-establish
us-east1 immediately, with no client-side DNS cache to invalidate and no propagation delay to wait out.
us-east1 was already running at a low replica count (warm standby), so this step is the Horizontal Pod Autoscaler adding pods to already-provisioned nodes — not a cluster being created from scratch, which is the whole point of not using a colder pattern here.
gcloud sql instances promote-replica turns the cross-region read replica into a standalone writable primary. This step, not the network failover or the pod scale-up, eats most of the RTO budget — the replica has to finish catching up to the primary's last replicated transaction and flip out of read-only mode.
Total RTO here lands around 2.5-5 minutes, and — just like the AWS Aurora
Global Database walkthrough this mirrors — the dominant cost isn't losing
data (the replica's RPO is typically seconds) and it isn't DNS. It's the
database promotion step. The mechanism differs from AWS's "DNS TTL plus
promotion" story specifically because GCP's Global External LB is
Anycast-based rather than DNS-based, but the shape of the lesson repeats:
compute fails over fast, data-layer promotion is what you're actually
waiting on. Swap Cloud SQL for Spanner multi-region in this same diagram and
step 4 disappears entirely — there is no replica to promote, because
us-east1 was already a live write participant before the outage started.
In this walkthrough, why doesn't the failover wait on DNS propagation the way a Route 53-based AWS failover does?
Backup and Restore, Applied: A Cloud SQL PITR Walkthrough
The backup-and-restore pattern from earlier isn't hypothetical — it's what happens whenever the failure isn't infrastructure loss but a bad write (a migration that drops data, an application bug that corrupts rows) and a warm-standby replica would faithfully replicate the mistake too.
gcloud sql backups list plus the transaction log determine the last known-good timestamp, inside the 7-day PITR window covered in gcp/databases.md.
A migration corrupts rows in the Cloud SQL primary. Does promoting the cross-region read replica (the warm-standby mechanism from the earlier walkthrough) fix this?
Backup Strategy Across the Data Layer
Every pattern above eventually depends on what each individual service can actually back up, and at what granularity. This is the cross-cutting summary — mechanism and typical RPO per service, so the full data-layer DR posture is visible at a glance rather than scattered across five files.
| Service | Backup mechanism | PITR support | Typical RPO | Cross-region option |
|---|---|---|---|---|
| Cloud SQL | Automated daily backups + transaction logs | Yes, 7-day window (gcp/databases.md) |
Seconds–minutes (log-based) | Cross-region read replica, manually promoted |
| Spanner | Scheduled backups (up to 1 year retention) + PITR (up to 7 days via versioned reads) | Yes | Near-zero in a multi-region config (no replica to lag) | Built into multi-region config — no separate DR copy needed |
| GKE | Backup for GKE (Velero-based): captures workload config + PV volume snapshots | No — snapshot-based, not continuous | Depends on backup schedule (typically hours) | Restore into a cluster in any region |
| GCS | Object Versioning (gcp/storage.md) + Storage Transfer Service for independent copies |
Effectively yes via versioning | Near-zero for versioning; scheduled for cross-bucket copies | Multi-region/dual-region built-in; independent copy needs its own bucket/project |
| Bigtable | Multi-cluster replication (gcp/bigtable.md) — not a backup, a live async copy; separate table backups also exist |
No | Seconds (replication lag) for HA; backup-based RPO is scheduled | Cross-region clusters within one instance |
| Firestore | Managed daily export to GCS, plus point-in-time recovery (7-day window) | Yes (PITR add-on) | Near-zero with PITR enabled; otherwise daily | Multi-region database mode is built-in, not a separate backup |
A team stores its GCS bucket as multi-region and considers that sufficient protection against an engineer accidentally running a bulk delete. Is multi-region replication doing that job?
gcp/storage.md) or a genuinely separate backup copy via Storage Transfer Service — a different failure class needs a different mechanism, the same lesson the Cloud SQL PITR walkthrough above makes for logical corruption.Putting It Together
The through-line across every section here is the same: state the RTO/RPO number first, pick the cheapest of the four patterns that actually satisfies it, and default to regional HA rather than multi-region unless the number demands otherwise. Spanner is the one service in this whole section that lets you buy your way out of the failover-step problem entirely — every other service still has some manual or automated promotion sitting between an outage and full recovery, and that promotion step is usually where the real RTO budget goes, not the data loss itself.