Engineering

How to plan a migration you cannot pause

Editorial · Reveneau · October 3, 2026

How to plan a migration you cannot pause

Most migration plans we are shown have a single moment in them where everything changes. The old system is switched off, the new one is switched on, and a team sits watching dashboards at two in the morning.

It is a popular plan because it is easy to describe and easy to put in a calendar. It also concentrates every unknown into the one hour where you have the least time, the least sleep, and a rollback procedure that has never actually been run.

There is a slower approach that is safer, and its main advantage is that it tells you whether the migration works before you are committed to it.

Run both, and compare on real traffic

The existing system keeps serving users. The new one receives the same requests, produces its own answers, and those answers are only written to a log.

Then compare them. Same input, two outputs, and every difference is a question worth answering.

The reason this matters more than any amount of testing is that production data is not like test data. Staging holds records somebody created on purpose, which means they are well-formed and reasonable. Production holds everything that accumulated: the account created in 2019 under rules that no longer exist, the record where a field is null that the current schema says cannot be null, the customer who has been in a partially-completed state since a deploy went wrong two years ago.

Those are the cases that break migrations, and they are the ones you cannot invent, because if you could imagine them you would have handled them already.

One caution when setting this up. The new path must not repeat side effects. If it sends the email, charges the card, or writes to the shared store, it is affecting real people. Checking for side effects before switching on the comparison is real work, and it is easy to miss one in a system with background jobs.

Fix differences until they are rare and explained

This is where the migration actually happens, and it is the phase that gets shortened when the date starts to be delayed.

Every difference falls into one of three groups. The new system is wrong, which is what the exercise is for. The old system is wrong, and it has been for years, and someone now has to decide whether to preserve the bug because customers depend on it. Or both are acceptable and the difference does not matter, in which case write down why, because the same difference will be rediscovered by someone else in three weeks.

The standard for moving forward is that differences are rare and every remaining category is explained. Some differences are legitimate, so zero is not the goal. Explained means nobody is looking at a difference and hoping it is fine.

An unexplained difference means the two systems disagree about something you do not yet understand. Teams that proceed past that point see the same difference later, as an incident that affects a customer.

Move traffic in small steps

One percent. Then five. Then twenty-five. Then half.

The first share should be small enough that a serious problem affects few people while still being large enough to see in monitoring. The exact number matters less than having a detection method and a reversal that takes seconds.

Watch business outcomes alongside technical ones. This is the part teams get wrong: a system can return successful responses at normal latency while producing wrong numbers. Error rate and response time will both look excellent. Completion rates, totals, and support contacts are what reveal a correct-looking answer containing an incorrect value.

Stay at each step for at least one full day, because overnight batch work behaves differently from daytime traffic and often runs code nobody has looked at recently.

Decide in advance what would make you stop, and write it down before you start moving traffic. An error rate above some figure, a drop in completions, a specific type of support ticket appearing. The reason to set this early is that judgment gets worse under pressure: at the point where the numbers look wrong and a deadline is close, a team without a pre-agreed threshold will usually find a reason to continue. A number written down a week earlier, by the same people, is much harder to ignore.

Keep the way back open, and rehearse it

At every point there must be a way back that someone has actually performed.

Rehearse it. Time it. Most importantly, work out what happens to data written since the switch, because that is the question these plans usually fail on. Moving traffic back to the old system is usually easy. Removing the records the new system created, in the new format, frequently is not.

This is why the data format tends to decide the strategy. If both systems can write to a shared store in a compatible format, reversal stays cheap and the whole migration gets easier. If the new system writes something the old one cannot read, the change cannot be reversed after that point, and you should know exactly when you reach it.

Wait for a full business cycle

Do not shut down the old system just because the new one has worked through two quiet weeks.

Month-end runs code that runs once a month. Quarter-end runs code that runs four times a year. The annual report, the renewal cycle, the seasonal peak: each runs parts of the code that ordinary weeks never use, and each is a genuinely different test.

Keeping the old system available is a real cost. Turning it off before quarter-end is a larger one, and it is paid at the worst possible moment.

When the new system was largely generated

Two adjustments if the replacement was written mostly by a model.

The comparison step becomes more valuable. Generated code is uniform and plausible, so it does not show its weak points the way hand-written code does through messy code. Reading it will not tell you where it differs from the old behaviour. Running it against real traffic will.

And do not assume the old system's behaviour was captured just because the new code looks complete. Veracode's Spring 2026 testing across more than 150 models found generated code compiling and running correctly over 95 percent of the time while passing security checks in roughly 55 percent, with the weakest types being mistakes spread across lines that each look reasonable: cross-site scripting at 15 percent and log injection at 13 percent. Something that runs correctly and handles an unusual case differently is exactly what the comparison is built to catch, and exactly what a review will not.

Related reading is in how to take over a codebase you did not write and adding evals to an existing codebase.

What this costs

Longer. More infrastructure while both run. More coordination. A period where the team maintains two systems, which nobody enjoys.

What you get in return is a series of small, reversible steps in place of one risky moment, and that you learn whether the migration works while you still have the option of stopping. Compared against one incident affecting customers during a cutover, it is usually the cheaper plan, and it is much easier to explain to the people who will be awake for it.

Thanks to the teams who let us look at migrations that went badly and were honest about the sequence. The pattern was consistent: the plan had a comparison phase in it, and the comparison phase was the thing that got cut when the date came under pressure.

A migration is finished when the old system has been unnecessary for a full cycle and nobody noticed.

Related guide: Maintaining custom software.

Sources

Common questions

Why is a single cutover risky for a live system?

Because it concentrates all the uncertainty into one moment, and that moment is usually late at night with a tired team and a rollback nobody has rehearsed. Every difference between the two systems arrives at once, so diagnosis is hard exactly when time pressure is highest.

What does running both systems in parallel mean?

The existing system continues serving users while the new one receives the same requests and its responses are recorded rather than returned. That gives you a direct comparison on real traffic without affecting users, which is the only way to see how the new system behaves against genuine data.

Why is production data different from staging data in ways that matter?

Because staging holds records someone created deliberately, while production holds everything that accumulated over years, including accounts from old versions, partially completed records, and values that a current constraint would reject. Those are precisely the cases that break a migration.

How do you compare two systems safely without affecting users?

Send each request to both, return only the existing system's answer, and record both for comparison. Take care that the new path does not repeat side effects such as sending an email, charging a card, or writing to a shared store, since those would affect real people outside the test.

How small should the first share of real traffic be?

Small enough that a serious problem affects few people and is still visible in your monitoring, often around one percent for a high-volume system. The number matters less than the ability to detect a problem at that volume and to reverse it quickly.

What should you watch while shifting traffic?

Business outcomes alongside technical measures, because a system can return successful responses at normal speed while producing wrong values. Watch completion rates, totals, and support contacts, since a correct-looking response containing an incorrect number will pass every technical check and only shows up in what users do next.

How long should the old system stay available?

Until the new one has been through a complete business cycle, including month-end, quarter-end, and any seasonal peak. Those periods run parts of the code that are used rarely, so a migration that has only run through ordinary weeks has not yet been tested against the hardest days.

What makes a rollback plan real rather than theoretical?

Having actually performed it, at least in a rehearsal, and knowing how long it takes and what happens to data written since the switch. The second question is the one most plans fail on, because reverting the code is simple and reverting the data written by the new system frequently is not.

What is the hardest part of a migration to reverse?

Data written in the new format, since moving traffic back is easy and removing written records is not. This is why the format of the data usually decides the migration strategy, and why writing to both systems during the transition is often worth the extra work.

How does this apply when the new system was largely AI-generated?

The comparison step becomes more valuable, because generated code tends to be uniform and plausible and will not show its weak points through messy code. Running it against real traffic finds behavioural differences that reading the code would not reveal.

What should you do when the comparison shows differences you cannot explain?

Stop and understand them before moving any traffic, because an unexplained difference means the two systems disagree about something and you do not yet know what. Teams that proceed past this point usually see the same difference later as an incident that affects a customer.

Start a project