Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions docs/backup-and-recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ It records, in order:
1. **Backup configuration as observed** — the source branch (cluster size, region, replica count)
and the recent backups with state, size, completion and expiry, from which the actual cadence
and retention are derived. This is the "backup settings" half of the evidence.
2. **The restore** — `pscale branch create <db> restore-test-<stamp> --restore <backup-id>`, then
waits for the branch to be ready. The source branch is only ever read (`branch show`,
2. **The restore** — `pscale branch create <db> restore-test-<stamp> --restore <backup-id>
--cluster-size PS_DEV`, then waits for the branch to be ready. The source branch is only ever read (`branch show`,
`backup list`); the restore lands in a new branch on its own cluster.
3. **Verification** on the restored branch, each a pass/fail row in the report:
- the Drizzle migrations journal is present;
Expand Down Expand Up @@ -69,10 +69,18 @@ login` session. Takes 10–20 minutes, most of it PlanetScale provisioning the r
- **Failure path without a restore:** `RESTORE_TEST_SOURCE_BRANCH=does-not-exist` fails at
`branch show`, before anything is created.
- **In CI:** the workflow only runs on its schedule or `gh workflow run backup-restore-test.yml`
(after merge — a PR's CI does not run it). The first dispatch validates the service token: it
needs `read_backups`, `restore_production_branch_backups` and branch create/delete on the
database; a token missing the restore permission fails at `branch create` with nothing to
clean up.
(after merge — a PR's CI does not run it). The first dispatch validates the service token. On
the database it needs `read_branch`, `create_branch`, `delete_branch`, `connect_branch`,
`delete_branch_password`, `read_backups` and `restore_production_branch_backup` — the
development-branch accesses only, never `delete_production_branch`. A token missing
`read_backups` fails at `backup list`, one missing the restore access fails at `branch
create`, both before anything is created; one missing the delete accesses restores fine and
then leaves the branch behind, which the drill reports as a failed check. Only the token's
creator or an organization administrator can change its accesses.
- **Why the restore is `--cluster-size PS_DEV`:** a restore without an explicit size becomes a
PS-10 branch flagged `production: true`, which only `delete_production_branch` can remove —
and PlanetScale cannot demote a Postgres restore branch afterwards. A PS_DEV restore is a
development branch, so the CI token never needs an access that could reach `main`.

### Schedule

Expand Down
21 changes: 18 additions & 3 deletions packages/db/scripts/restore-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* 1. Records the backup configuration as PlanetScale reports it: the source
* branch (cluster, region, replicas) and its recent backups, from which
* the observed cadence and retention are derived.
* 2. Restores the newest successful backup into a fresh, throwaway branch
* (`restore-test-<utc stamp>`) with `pscale branch create --restore`.
* 2. Restores the newest successful backup into a fresh, throwaway PS_DEV
* branch (`restore-test-<utc stamp>`) with `pscale branch create --restore`.
* 3. Connects to the restored branch and verifies the data is usable: the
* migrations journal is present, every critical table exists and holds
* rows, the newest record sits inside the backup's window (so this is the
Expand Down Expand Up @@ -504,7 +504,22 @@ const main = async (): Promise<void> => {
}
})

const create = runPscale(["branch", "create", database, branch, "--restore", latest.id, "--wait"])
// PS_DEV is not only the cheapest size: a restore without an explicit size
// lands as a PS-10 branch flagged `production: true`, which the CI token can
// only delete with production-delete accesses — the same accesses that could
// delete `main`. A PS_DEV restore is a development branch, so `delete_branch`
// and `delete_branch_password` suffice and the token never holds more.
const create = runPscale([
"branch",
"create",
database,
branch,
"--restore",
latest.id,
"--cluster-size",
"PS_DEV",
"--wait",
])
cleanupOwed = true
if (create.exitCode !== 0 && !/timed out/i.test(`${create.stdout}\n${create.stderr}`)) {
fail(`Could not create ${branch} from backup ${latest.id}`)
Expand Down