Skip to content
Rivl
29 October 2026Decisions11 min

How to migrate from a legacy system without downtime

The plan that causes downtime is the one with a cutover weekend in it. The plan that avoids it runs both systems at once for longer than anybody wants, and the cost of that is the thing nobody budgets for.

Nobody asks for a migration. They ask for the thing the old system will not do, and a migration is what the answer turns out to be. Then somebody proposes a weekend: freeze the old system on Friday, move the data, open the new one on Monday. That plan is where the downtime comes from, and where the rollbacks come from, because a weekend is long enough to move data and far too short to find out whether it moved correctly.

The approach that avoids downtime is not a better weekend. It is a decision to run both systems side by side, move one slice at a time, and accept that the migration takes months rather than days. That cost is worth naming first, because projects fail at this by choosing the gradual approach and then budgeting for the weekend.

The sections of this note in order: whether this is a migration at all, putting a router in front of both systems, why the data is the hard half, what stays reversible and for how long, the translation layer between old and new, when the gradual approach is the wrong one, where this touches the rest of what you run, and the honest limits.
How this note is organised.

First, check that this is a migration and not a rewrite

These get confused constantly and they are different projects. A rewrite replaces the whole system and ends with one switch. A migration moves functionality out of the old system piece by piece and ends with the old system having nothing left to do. Only the second one can be done without downtime, and not every system is a candidate for it.

If the honest answer is that the old system is small enough to replace outright, replace it. The gradual route costs more and buys you risk reduction you do not need. The prior decision, whether to touch the thing at all, is a separate question covered in when to rebuild vs refactor legacy software, and it is worth settling before reading on.

The router goes in before anything moves

The mechanism here has a name and published documentation, which is useful when you need to explain to a non-technical sponsor why month one produces no visible features. Microsoft documents it as the Strangler Fig pattern, described as a way to "incrementally migrate a legacy system by gradually replacing specific pieces of functionality with new applications and services".

The first piece of work is a facade, or proxy, that sits between your users and both systems. Every request goes through it. On day one it routes essentially everything to the old system, which means it changes nothing a user can see, and that is the point: you have proved the routing works while the old system is still doing all the work. Then each slice you migrate is a routing change rather than a release.

The documentation is clear about the four phases: introduce the facade, shift requests across incrementally, decommission the legacy system once nothing depends on it, and finally remove the facade so clients talk to the new system directly. That last step is easy to forget and worth putting on the plan, because a permanent facade is a permanent thing to operate.

Two warnings in the same document are the ones that bite in practice. Make sure the facade "doesn't become a single point of failure or a performance bottleneck", and make sure it "keeps up with the migration". Every request in your business now goes through a component somebody wrote in the first month of the project, which is a good reason not to let the junior build it alone.

The data is the hard half, and the only half that cannot be undone

Routing is reversible: flip a rule and traffic goes back. Data is not reversible the same way, which is why the data plan deserves more care than the application plan.

The documented approach for a shared monolithic database is to extract one domain at a time. Microsoft's own example runs it in three steps: the new service first reads and writes the old database, then an isolated domain database is created and loaded with an extract, transform and load pass, with a change data capture process keeping it in sync while both systems run. Only after you "validate consistency between both databases before cutover" does the new database become the system of record for that domain.

That middle period, where two databases hold the same truth and something is reconciling them, is the part that gets cut when the schedule slips. It should be the last thing cut. It is the only window in which you can discover that your migration has been quietly corrupting a field for three weeks and still fix it cheaply.

What stays reversible, and when that stops

Write this table down for your own system before you start, because the moment the rollback path closes is a decision somebody makes without noticing.

PhaseWhat is reversibleWhat ends it
Facade in, no traffic movedEverythingNothing yet, this is the safe phase
Slice reading new, writing oldRouting, instantlyNothing, old data is still authoritative
Both databases in sync via CDCRouting and dataValidation gaps you have not checked
New database is system of recordRouting only, with data replayRemoving the old tables and sync
Legacy objects deletedNothing cheaplyThis is the one-way door
The table from this note: five migration phases set against what remains reversible in each and the event that closes the rollback path, ending with deleting the legacy objects as the one-way door.
The rollback path closes once, and usually quietly.

The documentation says this plainly about the final step. You can roll back while the domain tables and synchronisation still exist, but afterwards "you must restore those objects and replay data changes", and that "significantly increases effort and risk". The guidance is to "treat the removal of legacy objects as a deliberate final step for each domain". In practice that means it needs a named person and a date, not a cleanup ticket.

The translation layer nobody puts in the estimate

While both systems are live they have to talk to each other, and they will not agree about anything. The old system has its own field names, its own idea of what a customer is, and probably a few decisions that made sense in 2014. If the new system calls it directly, those decisions become the new system's decisions too.

The documented answer is an Anti-corruption Layer, a translation component between the two. Microsoft's framing is the useful part: without it, maintaining access between new and legacy "corrupts what might otherwise be a cleanly designed modern application". The layer holds all the translation logic so neither side has to understand the other.

It is not free, and the documentation lists the costs rather than hiding them: it "adds latency to calls between the two systems" and "adds an extra service that you must manage and maintain". There is also a scope warning worth repeating to whoever builds it, which is to keep it to translation and "avoid placing business rules or orchestration in the layer". That is exactly where business rules end up when the deadline is close, and it is how a temporary adapter becomes permanent.

One decision to make early: is this layer temporary or not? The documentation raises it directly. Both answers are fine. Not deciding is what leaves you operating three systems instead of one.

When the gradual approach is the wrong one

This is the part most migration advice leaves out, and the documentation is specific. The pattern might not be suitable when requests to the back-end system cannot be intercepted, when you cannot access the legacy source and it has no other integration point you can change, when the system is small enough that replacing the whole thing is simple, or when you need to decommission the original quickly.

The second one is the common blocker in older business software. If you cannot modify the legacy system at all, you cannot disable the features you have migrated, so both systems keep writing and you have two sources of truth rather than a migration. Sometimes the database is the way in: where logic sits in stored procedures, the documentation notes the database itself can message new services, or change data capture can read the transaction log instead. If neither is available, a gradual migration is not on the table and the honest plan involves a cutover.

Where this touches the rest of what you run

A migration is rarely one system. The old thing has integrations, and each has to keep working through the move or be moved with it. What that costs is covered in API integration between two systems, and what it really costs, and the estimate should include one of those per integration rather than a single line item.

If what you are migrating away from is a set of spreadsheets rather than an application, the shape is different and simpler, and it is covered separately in migrate from spreadsheets to a database without losing the history. The history part is the bit people regret skipping.

One thing that shows up a few months in: the new system is handling a growing share of traffic on a schema designed when it handled none of it. Queries that were instant during the pilot are the ones that time out once the slice carries real volume, and why does my web app get slower as data grows is the companion to that. Designing for it costs less now than retrofitting it later.

The honest limits

There is no figure here for how much longer a gradual migration takes than a cutover, or what share of migrations overrun, because we do not have one worth citing. The numbers in circulation come from vendor material without a published method or a definition of what counted as a migration. A number like that becomes a target in a steering meeting, which is worse than no number at all.

What is defensible is mechanical rather than statistical. Downtime comes from doing many things in one window. Running both systems removes the window, and charges you the cost of operating two systems plus a router plus a translation layer for as long as it takes. If that cost is not in the budget, you have not chosen the no-downtime approach, you have only chosen its name.

Last caveat, and it decides most of these projects. The hardest part is not the routing or the data, it is that nobody wants to own the old system during the months it keeps running. Features are still requested of it, bugs are still found in it, and the team wants to work on the new thing. Deciding up front who maintains it until its last slice is gone is the difference between a migration that finishes and one that stalls at seventy percent for two years.

Describe it. We build it.

Seven or twelve days, pay on delivery, a year of maintenance included. Bring the problem, not a spec.

Book a meeting

Read next