Contact Us

Technology

Legacy modernization without downtime: when the two systems disagree

Routing traffic to a new system is the easy half. The hard half is the day the old system and the new one return different numbers, and deciding in advance which one you believe.

Engineer at a workstation working on a system migration

The short version

  • The routing half of a staged migration is solved and well documented. The data half, keeping two live systems telling the same truth, is where programmes actually stall.
  • Decide the system of record per capability, in writing, before you route a single request. On the first day the numbers differ, finance, support and your auditor will each ask the same question.
  • Set the parity threshold and the observation window before you see the first comparison. A threshold chosen afterwards is a negotiation with yourself, and it resolves in favour of the date.
  • Rollback is a decision, not a capability. Name the trigger, the metric and the one person who can call it, and know the moment past which reversing is no longer possible.
  • A facade only intercepts requests. Batch jobs, month-end close, file drops, partner integrations and webhooks registered against old URLs go around it, and each needs its own migration.

Everyone stops at the same place

The advice on replacing a system that cannot go offline is remarkably consistent. Do not attempt a single switchover weekend. Put a facade in front of the old system, route one capability at a time to the new one, and let the old system shrink until there is nothing left of it.

That advice is correct, and it is the easy half. It describes how requests find their way to the right code. It does not describe what happens for the months in the middle, when both systems are live, both are being written to, and one Tuesday morning the finance lead notices that the two of them do not agree.

In July 2026 we read eleven pages that search engines returned for queries about modernizing legacy systems without downtime, including the reference architecture pages from two major cloud providers. Seven describe the routing pattern. One gives any mechanics for rolling back. None of the eleven states what to do when the old and new systems return different answers. Not one names a resolution rule, a tolerance, or a person who decides.

That gap is not academic. It is the thing that turns a six month migration into a two year one, because a team that cannot agree on which system is right cannot cut over, and a parallel run that nobody is allowed to end becomes permanent. This article is about the half nobody writes down.

What the strangler pattern actually gets you

The pattern has a name and a canonical description. Martin Fowler named it in 2004 after strangler figs he had seen in Queensland three years earlier, plants that seed in the branches of a host tree and work their way down until they root in the soil and the original tree is gone. His current write-up supersedes that original. Both major cloud providers publish reference architectures for it: Microsoft as the strangler fig pattern and Amazon in its prescriptive guidance.

The mechanism is simple. A facade sits in front of the legacy system and intercepts incoming requests. At first it forwards everything to the old code. As each capability is rebuilt, the facade starts routing that capability to the new implementation instead. The callers never change. The old system loses responsibilities one at a time until it holds nothing and can be switched off.

How a staged migration is wired while both systems are liveRequests arrive at a facade, which routes each capability to either the old system or the new one. Data replicates between the two systems in both directions. A reconciliation process reads both and is the only place a disagreement between them is detected. Some traffic reaches the old system without passing through the facade at all.RequestsFacaderoutes per capabilityOld systemsystem of record, for nowNew systemone capability at a timeReplicationboth waysReconciliationwhere disagreement surfacesBatch jobs, reports, direct database accessnever pass through the facadeWhen they disagree,which one do you believe?Decided in advance, or decided in an incident
A staged migration while both systems are live. The facade is the easy half: it routes each capability to whichever system now owns it. The hard half is on the right, where reconciliation is the only place a disagreement between the two ever surfaces, and along the top, where batch jobs, reports and direct database access reach the old system without passing through the facade at all.

What it does not address is the state underneath. Two implementations of the same capability read and write data. For as long as both are live, that data has to be in two places, or in one place understood two ways, and something has to keep them honest. Amazon is candid about this in its own guidance, describing the redundancy and eventual consistency between two data stores as something to treat as a tactical position rather than a destination. It is the right caveat. The rest of this article is what to do about it.

One thing worth settling before any of it: the goal is not to reproduce what the old system does. Thoughtworks put rebuilding to feature parity on hold in its own technology radar, and the reasoning is worth quoting because it is easy to forget mid-programme. Current business processes are in large part a consequence of the workarounds and compromises the legacy technology required. Rebuilding those faithfully is waste, and it spends the one opportunity you get to build what the business actually needs now.

Two systems, one truth

Dual writes fail in the middle

The obvious approach is to write to both systems on every change. Stripe published the canonical version of this sequence: dual write to old and new, move reads across once you trust them, then move writes, then remove the old data. It is a sound plan and it is the one to follow.

It is also worth understanding exactly how the naive version breaks, which Martin Kleppmann set out in why dual writes are a bad idea. Two failure modes matter. The first is partial failure: the second write fails after the first has committed, there is no transaction spanning both, and nothing in the code knows the two have diverged. The second is subtler and worse. Two clients write concurrently, the two systems receive their requests in different orders, and both end up internally consistent, permanently different from each other, and completely silent about it.

The fix in both cases is to stop treating the second write as part of the request. Commit once, and in the same transaction record that a change happened, then let a separate process apply it to the other system and retry until it succeeds. The write and the intent to replicate it commit together or not at all, which is the only guarantee available without a distributed transaction. Change data capture, reading the database log rather than the application, achieves the same ordering guarantee from the other direction and is usually the better option when you cannot modify the legacy code at all.

Backfill is a project, not a step

Replication handles changes from the moment you turn it on. Everything that existed before then has to be copied across separately, while the system stays live. That work is routinely written into a plan as one line and it deserves several. It has to run in chunks small enough not to starve the production database, it has to be throttled against real load, it has to be restartable from where it stopped rather than from the beginning, and it has to cope with rows changing underneath it while it copies them.

The order matters too. Backfill first and replicate second and you will miss everything that changed in between. Replicate first and backfill second, with the backfill written so that a replicated change always wins over a copied one, and the two meet cleanly.

What to do when they disagree

They will disagree. Two implementations written years apart, by different people, against different assumptions, will not produce identical output on every record, and the first time it happens should not be the first time anyone has thought about it. These are the decisions to make before you route a single request, and every one of them is easier to make in advance than in the room where the numbers are already wrong.

  1. Decide the system of record, per capability, in writing

    While both run, exactly one of them is the answer of record for each capability. Everyone from the finance lead to the support desk needs to know which, and so does whoever signs your accounts. This is not a philosophical question. It is what determines whose number goes on the invoice on the first day the two differ.

  2. Separate the differences that matter from the ones that never will

    A naive comparison lights up on timestamp precision, floating point rounding, sort order and sequence numbers that were never going to match. Publish the exclusion list and the tolerance per field before you start. Otherwise the team spends its first fortnight investigating noise and learns, exactly in time for the difference that matters, to ignore the alert.

  3. Compare outputs, not screens

    Two interfaces can look identical and be computed differently. Compare what the business actually consumes: the export, the ledger entry, the invoice total, the file the regulator reads. Run both systems against the same input and diff the artefact.

  4. Set the parity threshold and the observation window before you see a single result

    Decide what proportion of records must match, across which entities, sustained for how long. A threshold set after the results are in is a negotiation with yourself, and it always resolves in favour of the date that has already been announced.

  5. Quarantine, do not auto-correct

    When a record diverges, hold it and raise it. Never let either system silently overwrite the other. Auto-correction destroys the evidence needed to find the cause, and a migration that quietly repairs its own symptoms keeps every bug it had and loses the trail to all of them.

  6. Give one named person the authority to stop the cutover

    Not a committee, and not the delivery lead whose incentive is the date. Someone who can say that parity is not good enough this week and be listened to. A rollback plan nobody is empowered to trigger is a document, not a control.

  7. Rank the entities by what a wrong answer costs

    A rounding difference on an internal dashboard is noise. The same difference on a tax filing is not. Rank by consequence, then let that ranking drive both how hard you compare each entity and the order in which you migrate them. Start with the capability where being wrong matters least, because that is where you learn the process.

Rollback is a decision, not a capability

Almost every migration plan says there is a rollback plan. Very few say what would trigger it, who calls it, or when it expires. Those three omissions are why teams sit at ninety percent parity for months: rolling back feels like failure, going forward feels unsafe, and with no pre-agreed trigger there is no way to decide.

The trigger should be written down as a metric, a threshold and a window, in the same document as the parity target. The expiry matters just as much, because reversibility has a shelf life and it is shorter than people assume.

What can still be reversed at each stage of a cutover, and what closes the door.
What you are reversingStill reversible whileWhat closes the door
Read trafficThe legacy path still exists and is still being fedVery little. This is the easy direction and it is why reads move first
Write traffic, before the new system holds anything uniqueEvery write is still reaching the legacy system tooThe first write that lands only in the new system
Write traffic, after the new system holds unique dataYou built and tested a reverse path that can replay new records back into legacyNo reverse path was built, which is the usual case, because nobody budgeted for a migration that runs backwards
A schema changeThe old column or table still exists beside the new shapeThe contract step ran and the old shape was dropped
A whole capabilityThe legacy code, tables and scheduled jobs are still deployedLegacy objects were deleted to tidy up before the window had actually closed

The row that catches people is the third. Reversing reads is trivial and everyone plans for it. Reversing writes after the new system has accepted records the old one never saw requires a reverse migration that has to be built and tested in advance, and almost nobody does. If you are not going to build it, say so explicitly, and treat the first unique write as the moment the decision becomes permanent. Fowler is honest about this limit too, noting that even with blue-green deployment there remains the problem of transactions the new environment accepted while it was live.

The practice that separates teams who cut over calmly from teams who do not is rehearsal. When GitHub built gh-ost for online schema changes, the design let them run a migration against a replica, swap the tables and swap them back, and they reported completing thousands of migrations that way before running one against a production primary. The cutover itself was deliberately deferred so a human chose the moment. Rehearse the reversal, not only the change.

What a facade never reaches

A facade intercepts requests. Anything that reaches your legacy system by another route goes straight past it, and every one of these is a separate migration with its own parallel run.

The surfaces that bypass a routing facade, and what each one needs instead.
SurfaceWhy routing misses itWhat it needs instead
Nightly batch and ETLIt never makes a request through your proxyTreat each job as its own migration stage, with its own comparison and its own cutover
Month-end close and scheduled reportsIt runs on a calendar, so a defect can hide for four weeksRun at least one full close on both systems, in full, before cutting it over
Partner integrations you do not controlThey post to a legacy hostname you cannot make them changeKeep the old endpoint alive as a thin forwarder and give partners a dated window to move
File drops over SFTP or EDIThere is no request to intercept, only a file appearingProduce the file from both systems and diff the output before switching the producer
Webhooks registered against old URLsThe registration lives inside somebody else systemsInventory every registered callback before cutover, and assume you will not find them all
Anything behind an IP allowlistThe new host has a different addressRaise the allowlist change weeks ahead. It is routinely the slowest item on the plan

The last two are the ones that surface on cutover night. A webhook registered years ago by someone who has left, pointing at a hostname you are about to retire, is not discoverable from your own codebase, and an allowlist sits with a third party whose change process runs on its own timetable rather than yours.

The legacy system does not stop changing while you migrate

A migration that runs for months runs alongside a business that still needs things. The legacy system will get a new field, a changed constraint, a fix to a calculation. If your replication and your comparison assume a fixed shape on both sides, each of those changes breaks something quietly.

The discipline that makes schema change survivable has a name, parallel change, documented by Danilo Sato and more often called expand and contract. Add the new shape alongside the old one, migrate every reader and writer across, and only then remove the old. Every step is individually reversible, which is exactly the property you want during a period when reversibility is your main safety net. Fowler makes the same point about deployment order in blue green deployment: change the schema so it supports both the old and the new version of the application first, deploy that, confirm it works so you have a rollback point, and only then deploy the new application. A schema change and an application change in the same deployment is one of the more reliable ways to produce an outage you cannot reverse.

The organisational half of this is a freeze policy that people actually accept. Not a freeze on all legacy change, which no business will agree to, but a rule about which tables are in scope for the current stage and a route for urgent changes that lands them in both systems at once.

Decide in advance when both systems stop running

A parallel run costs real money and real attention. Two systems to operate, two sets of alerts, and somebody on call who has to understand both well enough to tell which one is wrong at three in the morning. That cost is acceptable for a defined window and corrosive when the window has no end.

So write the exit criteria at the start, next to the parity threshold, and make them specific: the entities that must match, the proportion, the number of consecutive days or closes, and the sign-off required. Then hold to them. The most common failure in a staged migration is not a technical one. It is a parallel run that becomes the permanent architecture because no one ever defined what finishing looked like.

The people who operate the system daily should sign off each stage, not only the engineering team. They are the ones who will notice that a report is subtly wrong, and they are the ones carrying the cost if it is.

What waiting costs, and which numbers to distrust

The case for starting is usually made with statistics, and most of the statistics in this field do not survive being checked. Two that do come from government audit bodies, which have no service to sell.

The US Government Accountability Office reviewed the federal legacy estate in July 2025 and reported that about 79 percent of planned federal IT spending for that year was going to operations and maintenance rather than new capability. Of the eleven systems it singled out as most in need of modernization, ranging from roughly twenty-three to sixty years old, eight ran on outdated languages and seven were operating with known security vulnerabilities that could not be remediated without modernizing. Of ten systems it had flagged as critical six years earlier, three had been modernized.

The UK National Audit Office put the same problem in cost terms across five large digital programmes, which between them ran about 26 percent over their original forecasts and accumulated at least twenty-nine years of delay, with the auditor attributing part of that to continuing to operate legacy systems for longer than planned. In a separate review it found that departments did not have fully funded plans to remediate roughly half of their legacy estate. Its plainest sentence is the one worth carrying: continuing to use legacy services is risky and commits organisations to even higher future costs.

When a staged migration is the wrong choice

Staged replacement is not free, and there are cases where the single cutover is genuinely the better decision. All of them assume you are replacing the system with something you build; if a product might cover the ground instead, that is a different decision with a different shape. The cases where staging is the wrong tool:

  • The system is small enough to rebuild and cut over in one short window. The parallel-run machinery costs more than the risk it removes.
  • You cannot intercept requests and cannot modify the legacy code. With no facade and no hook for replication, there is nowhere to stand.
  • The data model is so entangled that no capability can be carved out without carrying most of the schema with it. Fix that first, or accept a full rebuild.
  • Nobody on your side can own the comparison. Every rule in this article needs a person, and without them a staged migration just runs two broken systems instead of one.

Where staging is right, which for a business-critical system it usually is, the work that decides the outcome happens before any code is routed: naming the system of record, agreeing the tolerances, building the reverse path or admitting you will not, and writing down who can stop it. That is the same discipline that decides whether an ERP switchover lands, and it is the reason we plan the going-live before we plan the building.

Questions about migrating without downtime

How do you modernize a legacy system without downtime?

Put a facade in front of the legacy system, rebuild one capability at a time, and route traffic to each replacement as it is proven, so the old system shrinks rather than being switched off in one event.

The routing is the well-documented part. What decides whether it works is the data underneath: how changes are replicated between the two systems, how existing records are backfilled while the system stays live, how you compare the two, and what you do when they disagree.

What is the strangler fig pattern?

A migration pattern where a new system grows around an old one, taking over its responsibilities incrementally until the old system can be retired. Martin Fowler named it after the strangler fig, and both Microsoft and Amazon publish reference architectures for it.

Practically it means putting a facade in front of the legacy system that can route each capability to either the old or the new implementation, so callers never change while the implementation moves underneath them.

Big bang or phased migration: which is safer?

Phased is safer for any system the business depends on daily, because it lets you prove each piece with real traffic and keeps a way back. A single cutover concentrates every risk on one date, and that date tends to move.

Big bang is defensible when the system is small, the data model is simple, and a short planned outage is genuinely acceptable. Below a certain size the machinery a phased migration requires costs more than the risk it removes.

What happens when the old and new systems disagree?

You quarantine the record, you do not let either system silently overwrite the other, and you resolve it against whichever system was declared the system of record for that capability before the migration started.

That declaration is the part to get right in advance. Along with it: a published list of differences that do not count, such as timestamp precision and rounding, a parity threshold and observation window agreed before any results are seen, and one named person with the authority to stop the cutover.

How long should you run both systems in parallel?

Long enough to cover a full business cycle for the capability in question, which for anything touching finance means at least one complete month-end close, and long enough to hit a pre-agreed parity threshold on consecutive runs.

Set the exit criteria at the start rather than the end. A parallel run with no defined finish quietly becomes the permanent architecture, and you carry the cost of operating two systems indefinitely.

Can you roll back after go-live?

Reads, almost always. Writes, only until the new system holds records the legacy system never received, and after that only if you built and tested a reverse path in advance. Most teams do not.

Treat the first write that lands only in the new system as the moment the decision becomes effectively permanent, and decide before that point whether you are funding a reverse migration or accepting that forward is the only direction.

What about batch jobs and scheduled reports?

They bypass the facade entirely, because they never make a request through it, so each one needs its own migration stage with its own comparison and cutover.

Scheduled work is also where defects hide longest. A nightly job shows a problem the next day, but a month-end process can carry a fault for four weeks before anyone sees it, which is why at least one full close should run on both systems before that job moves.

How long does a legacy modernization take?

It depends far more on how entangled the data model is than on how much code there is. A system whose capabilities can be cleanly separated moves in stages of weeks; one where every table touches every other has to be untangled before any staging is possible, and that untangling is the real project.

The useful early question is not how long the whole programme will take, but how small the first genuinely shippable stage can be. If no capability can be carved out and cut over on its own, that is the finding that should reshape the plan.

Method and sources

The claims about what published guidance does and does not cover come from our own review, so here is the method. In July 2026 we read eleven pages that search engines returned for queries about modernizing legacy systems without downtime, including the strangler-fig reference pages from two major cloud providers, and recorded which techniques each named and which it omitted. Three further pages appeared in results but could not be retrieved, and nothing above describes them. Search-result ordering is not a measured ranking position and we make no claim about where any page ranks. Everything else here is judgement from our own migration work, and is written as judgement rather than as data.

  1. StranglerFigApplicationMartin Fowler. The canonical description of the pattern, by the person who named it
  2. Strangler Fig patternMicrosoft Azure Architecture Center. Reference architecture, including the phase-scoped rollback window during a database strangler
  3. Strangler fig patternAWS Prescriptive Guidance. Reference architecture, and the source of the caution about data redundancy and eventual consistency between two stores
  4. Online migrations at scaleJacqueline Xu, Stripe, 2017. The four-phase dual write, backfill, verify and cut over sequence, described by the engineers who ran it
  5. Using logs to build a solid data infrastructure, or why dual writes are a bad ideaMartin Kleppmann, 2015. The two failure modes of naive dual writes, and why an ordered log is the safer substrate
  6. Parallel ChangeDanilo Sato, martinfowler.com, 2014. Expand, migrate, contract. Frequently misattributed to Fowler himself
  7. Blue Green DeploymentMartin Fowler, 2010. Separating schema deployment from application deployment to preserve a rollback point, and the honest caveat about transactions accepted after the switch
  8. Feature ParityCartwright, Horn and Lewis, Thoughtworks, 2021. Why rebuilding to parity is the wrong goal. Thoughtworks sells modernization consulting, and put this pattern on hold in its own technology radar
  9. gh-ost: GitHub’s online schema migration tool for MySQLShlomi Noach, GitHub, 2016. Rehearsing migrations against replicas, and deferring the cutover so a human chooses the moment
  10. Information Technology: Agencies Need to Plan for Modernizing Critical Decades-Old Legacy SystemsUS Government Accountability Office, GAO-25-107795, July 2025. The federal legacy estate, maintenance share of IT spending, and modernization progress since 2019
  11. Government’s approach to technology suppliers: addressing the challengesUK National Audit Office, HC 543, January 2025. Cost and schedule outcomes across five large digital programmes, and the cost of running legacy systems for longer than planned

The author

Mayursinh Jadeja

Founder · 13+ years in software

Mayursinh Jadeja founded Redlio Labs in 2014 and has led it since. He works from Ahmedabad, India on ERP and custom software delivery for organisations across the US, UK, Europe, Australia, and the UAE, and on the scoping conversations that decide what a programme will cost. He writes the articles on this site and traces every figure in them to a primary source.

  • Custom software delivery
  • ERP systems
  • Product design and UX
  • Next.js, React, and Node
  • Project scoping
Full profile