Migrate: the safe lane

Migrate is the one command that can move real data — start on the safe lane: dry-run, review, then run.

# 1. Safe lane: plan-only dry-run — zero target writes
pgdba migrate --source postgresql://u@h:5432/a --target postgresql://u@h:5432/b \
  --plan-only --state-dir /var/lib/pgdba-mig

# 2. Review migrate-report.json/.md: preflight refusals, table plan, warnings
#    (each refusal carries its remediation text)

# 3. Real run — writes only after preflight passes and you confirm
pgdba migrate --source ... --target ... --state-dir /var/lib/pgdba-mig

# Non-interactive (CI) + parallel jobs + retry only failed tables
pgdba migrate --source ... --target ... --yes --jobs 8
pgdba migrate --source ... --target ... --retry-tables public.orders,public.events

What each phase refuses, and why

  • Preflight — version/encoding/locale match, extension availability, disk space, privileges, 100GB scope cap. Mismatches abort with remediation text.
  • Confirm — the table plan is printed and confirmed interactively; --yes overrides for CI; a non-TTY run without --yes aborts with zero target writes.
  • Copy — pgcopydb clone --resume when available, otherwise phased pre-data → per-table data → post-data with per-table resume.
  • Verify — exact row counts, sequence vs max(pk), invalid-index scan, target ANALYZE; non-zero exit with a diff table on mismatch.

The selected engine and any fallback are recorded in the plan and migrate-report.json.

Danger

--force proceeds past scope/health gates with warnings recorded — only when you accept the reported risks: it can migrate into a target preflight flagged as incompatible or over the 100GB scope cap, and the warnings (not refusals) land in migrate-report.json.

Warning

--yes skips the confirm prompt for CI — every action still passes preflight gates, but no human sees the table plan before data moves.

Note

Runtime gating is deliberately not enforced (no refusal for --force without a prior plan-only report): the safe lane is signposted here and in pgdba migrate --help, not policed.

Done looks like

Exit 0 with verify.success: true and matching row counts in migrate-report.json — or an early refusal with the exact remediation instead of a half-copied target. Measured end-to-end at ~48 MB/s on the evidence page.