Skip to content
All articles

The legacy migration playbook: waves, parallel runs, and identical row counts and checksums

Every company has a system nobody wants to touch: an old database, a monolith, two tools that must stay in sync. Big-bang migrations of such systems are where weekends and reputations go to die. The approach that works is boring on purpose: map first, move in waves, run old and new side by side, and switch only when the numbers are identical. This is the playbook I use, with the projects where it held.

11 min read

1. Why big-bang migrations fail

A big-bang migration bets everything on one night: all data, all features, all users switch at once. If something is wrong, you find out in production with no way back that doesn't lose the work done since. The old system also stops getting changes for months while the new one is built, so the business waits.

The projects on this site that went well all avoided that bet. An ERP and a CRM stayed in daily use with the same data while they were connected in both directions. Sensitive health records moved between two public systems with none lost. Tenant databases moved into a Kubernetes cluster with identical row counts and checksums. Different systems, the same pattern.

2. Map the old system first, with AI reading the code

Before any plan, make one map: which components exist, which data they own, which interfaces connect them to the outside, which jobs run at night, and which people and teams depend on what. Old systems hide their real behaviour in places nobody remembers: a stored procedure, a cron job, a CSV export someone opens every Monday.

This is where AI changed the work most. A coding agent can read a large old codebase, list entry points and data flows, and draft the map in days of review instead of weeks of reading. I still verify every important finding against the running system, but the first draft no longer takes the longest. Ask the users too: the export nobody documented is the one that stops payroll.

3. Pick a strategy per component, not one for everything

Not every part deserves a rewrite. AWS's migration guidance lists the common strategies: retire, retain, rehost, relocate, replatform, repurchase and refactor. Use them per component: the reporting module nobody uses can be retired, the stable batch job can be rehosted as it is, the database can be replatformed to a managed service, and only the part that blocks the business gets refactored.

This keeps the expensive work where it pays back. In an automotive project a regulated application moved to Azure Kubernetes Service fully in Terraform, without loosening a single control, and cloud costs came out about 25% lower. The savings came from choosing carefully what to change, not from changing everything.

4. Waves by business capability, behind a façade

Cut the move into waves that make sense to the business: invoicing, then customer accounts, then reporting. Each wave is small enough to test properly and to roll back on its own. Avoid cutting by technology layer ("first the database, then the backend"): a half-moved feature is the hardest state to run.

Put a façade in front, the strangler fig pattern from the Azure Architecture Center: users and other systems talk to the façade, and it routes each feature to the old or the new system. Moving a feature becomes a routing change you can reverse in minutes. Over the waves the old system shrinks until nothing routes to it any more.

5. Moving data without losing a record

Data moves in two phases: a bulk copy of everything, then change data capture that keeps the new side current until cut-over. Managed services such as the Azure Database Migration Service and AWS DMS do both for common databases. Where two systems must stay in step for longer, put a queue between them.

That is how the health-data middleware worked: serverless functions on Azure, with Azure Queue Storage between the two public systems. If one side was unavailable, records waited in the queue instead of getting lost, and were delivered when it came back. Retries, idempotent processing and a dead-letter queue someone actually watches turn "we hope nothing got lost" into "we can show nothing did".

One wave of a migration: map, route through a façade, move the data, compare, switch. Click to open full size.
Legacy migration in waves: AI-assisted analysis reads the legacy application; users and other systems go through a façade that routes features not yet moved to the legacy application and moved features to the new services; data moves from the legacy database through a bulk copy plus change data capture, is checked with row counts and checksums per table and per wave, and reaches the new database only if identical.

6. Parallel run, row counts and checksums

Before a wave is switched for good, both systems process the same input and you compare the results: totals, invoices, reports, the numbers people actually use. For the data itself, compare per table and per wave: the row count, and a checksum over the content in a fixed order. AWS DMS has built-in validation that compares source and target row by row; for anything else, a query like the one below on both sides does the job.

Agree on the rule beforehand: if counts or checksums differ, the wave does not switch, no matter how close the date is. When tenant databases moved into a Kubernetes cluster with CloudNativePG in the FinOps project, the row counts and checksums were identical before a single tenant was switched.

sql
-- Run on the old and the new database, per table and per wave (PostgreSQL)
SELECT 'invoices'                                            AS table_name,
       count(*)                                              AS row_count,
       md5(string_agg(md5(t::text), '' ORDER BY t.id))       AS checksum
FROM invoices t;
-- Same result on both sides, or the wave does not switch.

7. Cut-over and a rollback you have tested

The cut-over of a wave is a short, rehearsed routine: freeze writes for the wave's data, let the last changes arrive, compare counts and checksums one final time, switch the façade, watch the business metrics. Keep the old side running read-only for a while, so you can look things up and so a rollback is possible.

A rollback plan that was never executed is not a plan. Rehearse it on a copy: switch the façade back, replay the changes made on the new side, check the numbers again. When everyone has seen it work once, cut-over day is calm instead of heroic.

8. Finish the job: decommission and keep it as code

A migration is done when the old system is gone, not when the new one is live. Archive what you must keep for legal reasons, switch the old side off, cancel its licences and contracts, and delete what isn't needed. Otherwise you pay for two systems and the old one keeps a security risk alive.

And build the new side as code from day one, so the next change is a pull request instead of another migration. In the automotive project everything went in as Terraform, with policies that apply to every team: the platform you migrate to should be easier to change than the one you left.

Takeaway

Migrate like an accountant, not a hero: map first, move in waves behind a façade, run both sides in parallel and switch only when row counts and checksums are identical.

More articles

    Get in touch

    Tell me what you're building. I'll reply within 1–2 business days.

    Dominik Heinrich
    Company
    MADO Consulting

    Available worldwide, remote. On-site on request, depending on the country.

    I reply within 1–2 business days. Free slots: see the calendar

    Opens Calendly's booking calendar here. Loading it sends data to Calendly (see Impressum).