Backup and Restore (Cloud SQL)

Backup and Restore (Cloud SQL) #

Admin only. Needs gcloud sql write access and a database login. Both sit with the GCP admins. If you have lost data, post in #back-end with the table, the rows, and the approximate time it happened.

Cloud SQL has no table-level restore. You clone the whole instance to a point in time, then copy out just what you need. Budget 1 to 2 hours — the clone alone takes 15 to 40 minutes depending on instance size.

GCP equivalent of the Aurora table recovery runbook.

Before you start: check backups actually exist #

Staging (app DB) Production (app DB)
Terraform config backups off, PITR off, 7 retained backups on, PITR 35 days, 36 retained
Live right now off off
gcloud sql instances describe funnelstory-production \
  --project=funnelstory-infra-production \
  --format='value(settings.backupConfiguration)'

Production backups and PITR are off, and that blocks go-live. Demoting the production instance for the migration silently turned both off. Existing backups were kept; new ones stopped. Terraform will not fix it — the Cloud SQL module has ignore_changes on backup_configuration.

Re-enable after promote. This command fails while the instance is still a read replica:

gcloud sql instances patch funnelstory-production --project=funnelstory-infra-production \
  --backup-start-time=10:00 --enable-point-in-time-recovery --retained-transaction-log-days=35

Phase 1: clone the instance #

  1. Clone to a point in time. Drop --point-in-time to clone the latest state:

    gcloud sql instances clone funnelstory-production restore-<date> \
      --project=funnelstory-infra-production \
      --point-in-time='2026-08-01T12:00:00Z'
    
  2. Wait for RUNNABLE:

    gcloud sql instances describe restore-<date> \
      --project=funnelstory-infra-production --format='value(state)'
    
  3. Connect to the clone and confirm the data is there. See psql to Cloud SQL for reaching a private-IP instance.

Phase 2: extract and restore #

  1. Count first, then stage the rows:

    SELECT count(*) FROM <table> WHERE <conditions>;
    CREATE TABLE back AS SELECT * FROM <table> WHERE <conditions>;
    
  2. Export. Small dataset → a SQL script of INSERT/UPSERT, checked into the incident artifacts. Large dataset → COPY back TO STDOUT as CSV.

  3. Restore into production, dry-run first:

    BEGIN;
    \i /path/to/restore.sql
    ROLLBACK;   -- confirm no errors, then re-run with COMMIT
    
  4. Validate row counts and spot-check, then drop the staging table.

  5. Delete the clone — it bills as a full instance:

    gcloud sql instances delete restore-<date> --project=funnelstory-infra-production
    

Untested #

This procedure has not been run against Cloud SQL. It follows the same shape as the AWS Aurora runbook, which is proven, but the Cloud SQL version is not.

Next: do a dry run on staging before you need this for real on production.