Database migration safety
Count correct rows, not just migrated rows
Progress counters can report success while conversions are incomplete or wrong. Use coverage, correctness and production impact as separate acceptance measures.
In this article
Define the eligible population
State which records the migration covers and when that population is measured. A live table continues changing, so a total captured before the job starts may not match the final row count.
Separate historical rows from new writes handled by the updated application. Include deleted, rejected and conflicted records in the reconciliation model.
For example, an account-code migration should distinguish converted accounts, accounts with no code by design and accounts held because the old value is ambiguous. Combining all three into processed hides unresolved work.
Verify the conversion independently
Compare selected outputs against an independent calculation or reviewed examples. Reusing the same conversion function to generate expected values can reproduce the same defect on both sides of the test.
Use aggregate checks where meaningful, such as counts by status or totals that should remain unchanged. Aggregates cannot replace record-level checks because offsetting errors may cancel each other out.
Choose boundary cases deliberately. Empty values, maximum lengths, unusual characters and records edited during the run often reveal more than a large random sample of ordinary rows.
Track the remaining work honestly
Report eligible rows still pending, conflicts awaiting retry and exceptions requiring a decision. Keep the oldest unresolved item visible so a small stranded group does not disappear behind a high completion percentage.
Measure processing rate over a useful interval and estimate completion from remaining work. A rate measured on easy rows can overstate progress when the final group contains expensive or invalid records.
Do not declare success merely because the worker exited normally. Its exit condition may mean the current scan ended, not that every eligible record reached an accepted state.
Include the live-service cost
Monitor application latency, error rates, lock waits, storage headroom and replication lag while the migration runs. Define pause thresholds before starting.
Acceptance should establish that the new representation is correct, readers can use it and ordinary business operations remained within the agreed limits. Keep these conditions separate so a fast conversion cannot conceal service degradation.
After switching readers, compare results from the normal application path. This catches mapping or query defects that a direct database inspection misses. The migration is complete when the application uses the new data correctly, not when the last update statement finishes.
Primary sources
PostgreSQL: progress reportingReferences checked 11 September 2026.