The plan is always the same. Build the replacement, run a parallel period that gets shortened when the date slips, pick a quiet weekend, cut over, and hold your breath.
What goes wrong is rarely the data. It is behaviour: an overnight job nobody documented, a rounding rule that only matters at year end, an integration that silently repairs bad records, a report someone in finance depends on that was never in scope because nobody knew it existed.
The old system is not a specification. It is an accumulation, and most of it is undocumented.
Strangle it instead
Route one narrow slice of traffic or one entity type through the new system while everything else stays put. Expand the slice only when it is boring.
This is slower on paper and much faster in practice, because every surprise arrives one at a time, at low volume, while the old system is still there to fall back to. A big-bang cutover discovers all the same surprises — simultaneously, at full volume, on a weekend.
Run both and compare, before you trust either
The most valuable phase is shadow mode: the new system processes real input and writes nowhere, and its output is compared against the old system's, automatically, every day.
The diffs are the specification you never had. Most turn out to be the old system doing something undocumented and correct. Some turn out to be the old system doing something undocumented and wrong, which is its own conversation.
Do not skip this because the numbers matched in testing. Test data does not contain the record from 2019 with the null in it.
Decide what "done" means for data
Three separate questions, usually collapsed into one:
- What migrates? Not everything has to. Archived data can often stay readable in place.
- What is backfilled versus recomputed? Recomputing is cleaner and slower; copying preserves history including its errors.
- What is the reconciliation? A count and a checksum per entity, run after each batch, with a documented tolerance. "It looked fine" is not reconciliation.
Keep the rollback real
A rollback plan that has never been executed is a hope. If the new system has been writing for a week, rolling back means reconciling a week of divergence — which is a project, not a button.
Which is the real argument for the slice-by-slice approach: it keeps rollback cheap the entire way through, because there is never much divergence to unwind.