Answer
This page says what backups exist for each site, how to bring back the whole production database, one brand or one file, and how the monthly restore test is run, so a restore is a known routine rather than a first attempt under pressure.
Today
What is kept
| Site | Kind | Kept for | Source |
|---|---|---|---|
| Production database | Point-in-time recovery | 7 days, to the second, into a new instance | brew/.wt-prod/docs/production.md:37 |
| Production database | Automated backups | daily, 14 kept | brew/.wt-prod/docs/production.md:38 |
| Production database | On-demand backup before every promote | until deleted | brew/tools/promote.sh:54-62 |
| Production database | Nightly logical export, Cloud Scheduler brew-prod-nightly-export at 21:30 UTC, to gs://brew-prod-backups/nightly/brew.sql.gz |
separate bucket, survives deleting the instance | brew/.wt-prod/docs/production.md:40 |
| Production files | gs://brew-prod versioning plus 7-day soft delete |
old versions until removed | brew/.wt-prod/docs/production.md:41 |
| Staging files | gs://brew-prototype versioning; noncurrent versions deleted after 30 days |
30 days | memory brew-storage |
| Staging records | Every row also mirrored to the bucket while OBJECTS_MIRROR=1 |
as the bucket | brew/deploy.sh:30-32 |
| Staging jam and brand resets | A copy in _deleted/jams-<time>/ or _deleted/brand-<slug>-<time>/ unless --no-archive |
until removed | brew/db/reset-jams.mjs:4 brew/db/wipe-cli.js:18-19 |
| Production brand reset or deletion | No _deleted/ copy; the database backups hold the brand until they age out |
7 days of logs, 14 daily backups | brew/db/wipe-cli.js:18-20 brew/db/brand-wipe.js:236-238 |
- Staging's Cloud SQL backup settings are not written down anywhere we read. brew/.wt-prod/docs/production.md:11
- The demo has no backups recorded; its brands come again from staging with
tools/demo-sync.sh. brew/tools/demo-sync.sh:1-7
The restore test
- 25 Oct 2026: the export was imported into a scratch database on
brew-staging; every brand's row and part counts matched live production. brew/.wt-prod/docs/production.md:43 - It is to be repeated monthly. No later run is recorded. brew/.wt-prod/docs/production.md:43
Sign-ins after a restore
A whole-database restore also brings back old sign-ins. Arata's tokens do not rotate, so this is safe for Arata, but db/connector-ping.mjs must be run afterwards, and Arata is never reconnected. brew/.wt-prod/docs/production.md:50 memory never-disconnect-arata-meta
Tomorrow
| Item | Version | Tag |
|---|---|---|
| Point-in-time recovery, daily backups, nightly export, versioned files | v1 | Stays |
| Monthly restore test, logged with its date and result | v1 | New |
A one-brand restore job written and tested, from db/partner-copy.js |
v1 | New |
| Staging backups written down, or staging declared rebuildable | v1 | New |
| Point-in-time window longer than 7 days once partners pay | v2 | Changes, Param's call |
How we do it
The whole production database to a moment
- Clone, about 20 to 40 minutes:
gcloud sql instances clone brew-prod brew-prod-restore --point-in-time=<RFC3339>
- Check the clone. Prefer copying the damaged rows back. Only if that is not possible, point
BREW_PROD_DATABASE_URLat the clone and promote. - Run
db/connector-ping.mjs arata-inthrough thebrew-prod-pingjob. Never reconnect Arata.
brew/.wt-prod/docs/production.md:47-50
One brand
- Clone to a moment before the damage, as above.
- Copy that brand's
objectsanddoc_partsrows from the clone with a small one-off SQL job, modelled ondb/partner-copy.jsminus its rule that keeps production's own rows. Keep the currentprivate/vault/*rows unless told otherwise. - Bring the brand's files in
gs://brew-prod/brands/<slug>/back from object versions.
brew/.wt-prod/docs/production.md:52-55
Files only
gcloud storage ls -a gs://brew-prod/brands/<slug>/<path>
gcloud storage cp gs://brew-prod/brands/<slug>/<path>#<generation> gs://brew-prod/brands/<slug>/<path>
brew/.wt-prod/docs/production.md:57
Monthly restore test
gcloud scheduler jobs run brew-prod-nightly-export, then wait for the EXPORT operation.- Give the
brew-stagingCloud SQL service account read access ongs://brew-prod-backupsfor the duration. gcloud sql databases create restore_drill --instance=brew-staginggcloud sql import sql brew-staging gs://brew-prod-backups/nightly/brew.sql.gz --database=restore_drill- Count rows per brand in both databases with a one-off job inside the VPC, compare, then drop
restore_drilland remove the temporary access. - Write the date and result in Numbers below.
brew/.wt-prod/docs/production.md:59-64
Numbers
| Metric | Definition | Where measured |
|---|---|---|
| Last restore test | Date and pass or fail of the monthly test | This page; 25 Oct 2026, passed brew/.wt-prod/docs/production.md:43 |
| Restore time | Minutes from clone start to a checked clone | Proposed; production.md estimates 20 to 40 |
| Nightly export age | Hours since the last successful export | Cloud Monitoring alert "export failed" brew/.wt-prod/docs/production.md:68 |
Open
- Who runs the monthly restore test, and on which day? Proposal: Param, on the first Monday of each month, starting 2 Nov 2026, with Claude doing the steps and Param checking the counts.
- Is staging data worth backing up? The partners now live on production. Proposal: treat staging as rebuildable, keep only the bucket versioning, and say so here.
- Is 7 days of point-in-time recovery enough? Proposal: keep 7 days during the design partnership and revisit when the first partner pays.