Legacy Migration

Nine years of live financial history moved to diff-zero — watch, or read the method below

A live system holding years of financial history had to move — new schema, new architecture — without stopping the business or corrupting a rupee of history. The method: strangler-pattern replacement, dual-write bridges, backfill in verified batches, parallel verification until diff-zero, then a cutover nobody’s customers noticed.

Who this story is for

Any business carrying a system everyone depends on and nobody will touch — the one that appears in every planning meeting as a reason. If your roadmap routes around a database the way traffic routes around a closed bridge, this is your story with different table names.

The situation

The legacy system was load-bearing and untouchable — the classic pair. It ran the business’s money flows, it predated most of the team, its documentation was three people’s memories, and every previous migration attempt had died in planning. The plans all began the same way — “first we freeze changes” — and the business could not freeze: revenue flowed through the old system every hour, and the backlog of product work wouldn’t wait for a rewrite either.

That’s the real migration problem, and it’s worth naming: not the technical difficulty of moving data, but the impossibility of the freeze. Any method that requires the business to stop is a method that will be postponed forever — while the legacy system compounds its interest as both the biggest risk on the books and the most reliable excuse for doing nothing.

What I did

Inventory first, and honestly. Every table, every consumer, every integration — and every undocumented cron, of which there are always three, each one load-bearing and known to exactly one person, who found out when it was mentioned. The inventory is unglamorous and decides everything: the migration order, the bridge design, and which “unused” table would have detonated quietly in month two.

The bridge, then dual-writes. The new system — clean schema, event-sourced money records, the architecture the old system should have had — was built alongside, not instead. Writes flowed to both worlds behind a feature flag; the flag meant retreat was always seconds away, and retreat-ability is what makes a team brave enough to proceed.

Backfill in verified batches. Years of history moved in chunks, each batch reconciled before the next departed. When the inevitable ancient data quirk surfaced — and it did, deep in the archive where old code had written impossible states — it cost a batch’s investigation, not a weekend’s rollback. A thousand verified batches beat one optimistic bulk-insert every single time; the full method is written up publicly.

The parallel run to diff-zero. Both systems answered every query; an automated comparator diffed the answers daily. Every discrepancy was chased to root cause — each one a production incident that never got to happen. The diff held at zero for two weeks before anyone said the word cutover, because money history earns the long hold.

The cutover as anticlimax. A Sunday morning, a checklist, reads flipped, watched, writes flipped, watched. Customers noticed nothing, which was the entire specification. The legacy system was retired with a screenshot rather than a wake — and gone pear-shaped precisely zero times, because everything that could go pear-shaped had already done so harmlessly inside the parallel run.

The result

Zero downtime, zero data-loss incidents, and a business that never stopped moving money while its foundation was replaced beneath it. The deeper result is cultural: the phrase “we can’t touch that system” left the vocabulary. Once a team has watched a live migration land safely, the next one gets planned instead of feared — the method transfers, and it did, to the factory-floor version of the same problem.

Specific record counts and volume figures stay with the client, per this site’s standing rule — no number ships unless it survives a reference call, and the reference call is available to serious prospects instead.

What it means for you

“We can’t migrate, it’s live” is a solved problem. The solution costs discipline, not downtime: inventory, bridge, verified backfill, parallel run, anticlimactic Sunday. It works on ledgers, on ERPs, on any system where history matters and stopping isn’t an option — and the first step is a delivery engagement that maps your specific version of the problem and tells you honestly how long the road is. Fear of the legacy system is rational right up until there’s a checklist; then it’s just a project with a start date.

The three rules that made it boring

Retreat-ability is the courage budget. Every step — dual-writes, backfill batches, read flips — sat behind a switch that could be reversed in seconds. Teams take brave steps when retreat is cheap and freeze entirely when it isn’t; engineering the retreat is engineering the advance.

Verify in units small enough to be wrong safely. The batch that surfaced the 2019 data quirk cost an afternoon because it was one batch among a thousand. The same quirk inside a bulk migration would have cost the weekend, the rollback, and — the real price — the organisation’s appetite for ever trying again.

Let the diff make the decision. Cutover dates chosen by calendars slip or gamble; cutover earned by diff-zero-for-two-weeks is neither. The comparator was the project’s real manager — silent, unbribable, and the only stakeholder whose sign-off meant the thing was actually done. Every migration since has kept that manager on staff.

/blog/zero-downtime-migration — the method, step by step · /work/erp-migration — the same discipline on a factory floor · /work/ledger-rebuild — migration meeting reconstruction · /services/delivery — the practice

Scared of your own legacy system?

That fear has a checklist. /contact — bring the system everyone’s afraid to touch, and the first call returns a map of the road out.