Migration speed
330 MB / 1M rows moved end-to-end in 6.9 seconds (~48 MB/s) with the default engine and default flags — including preflight, copy, verification, and exact row-count matching.
The run
| Phase | Result |
|---|---|
--plan-only (preflight + planning, zero target writes) |
2.1 s |
Real migration (--yes --force, copy + verify) |
6.9 s |
| Throughput (dataset bytes / wall clock) | ~47.9 MB/s |
| Engine (recorded in the report) | orchestrated (pg_dump/pg_restore) |
| Copy verified | yes |
| Row counts matched exactly | yes (400k + 400k + 200k) |
The sandbox carries deliberately broken settings (small shared_buffers,
TLS off, superuser roles), so the preflight gate refused the first run —
that refusal is recorded in the snapshot, and the measured run proceeded
with --force, warnings attached to the report. The gate working is part
of the evidence.
How to read this number
- What it measures: the pipeline a real cutover uses — plan, copy, verify — at a scale where the copy dominates. If pgdba added 10 s of overhead, you would see it here; you don’t.
- What it does not measure: network transfer (both databases are on one Docker instance), production I/O contention, or pgcopydb’s parallel throughput. Cross-host runs will differ.
- The gate cost is ~2 s: preflight + planning + report, before any
byte moves. That is what
--plan-onlygives you on every real migration.
Warning
Same instance, page-cache-friendly, Apple Silicon. Treat this as proof the pipeline is fast and honest at this scale — not as a production SLA. For prod numbers, run the same script against your own hosts.
Snapshot
docs/data/migration-evidence.json holds the full snapshot: date, pg
versions, dataset bytes, per-run timings, engine, row counts, and the
refusal record. It is committed — the number and its context travel
together.
Reproduce it
uv run python devdb/bench_migration.py # ~1M rows, writes the snapshot
uv run python devdb/bench_migration.py --rows 10000 # quick smoke
The driver seeds deterministic tables, times a plan-only dry-run and the real migration, parses the report, verifies row counts on the target, and drops everything it created.