How to Replace a Legacy Upstream System Without Stopping Operations

Every mid-size operator has one: the system they want off of. P2, Enertia, WellEz, a homegrown Access database somebody’s predecessor built in 2011, a custom SQL Server reporting stack that only one person understands. The license is expensive, the vendor is unresponsive, the data model fights you, and everybody agrees it has to go.

Then you look at what it would take to leave, and nothing happens. The system runs monthly close. It feeds the accounting handoff. It holds ten years of history nobody has validated. You cannot turn it off for a weekend to swap it out, because the business runs through it every single month, and the month does not stop.

So the project stalls. Or worse, somebody greenlights a big-bang cutover, sets a go-live date, and discovers three days before close that the new system’s numbers don’t tie to the old one’s.

There’s a better way to think about this, and it isn’t ours. Martin Fowler’s team wrote it down.


You don’t replace legacy systems, you displace them

The core idea in “Patterns of Legacy Displacement”[1] is a shift in the verb. You are not replacing the system in one motion. You are displacing it, piece by piece, until there is nothing left that the old system does that the new one doesn’t already do better. When that’s true, retiring the old system is an anticlimax instead of an event.

This matters more upstream than almost anywhere, because the thing you’re trying to replace is load-bearing. A production accounting system isn’t a reporting convenience. Revenue distribution runs through it. The JIB run depends on it. The reserves database pulls from it. Cut it over wrong and you don’t get a bug, you get a restatement.

Incremental displacement lets you move one capability at a time, validate it against the system still in production, and keep a working fallback the whole way. The old system stays authoritative until the new one has earned the handoff for that specific function. Nothing goes dark on a Friday hoping it comes back Monday.

Two of Fowler’s patterns do most of the work in the upstream case. One decides how data flows during the transition. The other decides where the historical data comes from.


Divert the Flow: intercept at the integration point

Most legacy upstream systems aren’t isolated. They sit in the middle of a set of integrations: a nightly file drop to accounting, an API the land system calls, a SCADA feed coming in, a set of allocation results going out. Those integration points are your way in.

“Divert the Flow” means you find the point where data crosses into or out of the legacy system and you insert yourself there. New writes get routed to the new system. Reads can be served from either. For a while, both systems process the same inputs and you compare what comes out.

A concrete version for a production accounting migration: the daily volumes and the OCC filings that feed the legacy system also get routed into the new pipeline. Both run allocation. Both produce a monthly production number by well. You diff them. When the diffs are explainable (rounding, a known allocation-method difference, a mapping you haven’t finished), you’re close. When they’re zero for a full cycle, the new system is ready to be the one accounting reads from.

The reason this works is that you never ask the new system to be trusted before it has proven itself against reality. The old system is the reference implementation. You are not guessing whether the new allocation logic is right. You are watching it match, or not match, the numbers the business already accepts, on live data, every day, before anyone depends on it.

If you’re building that interception layer, this is the same medallion and staging discipline we describe in Building a Production Data Pipeline on PPDM with Airflow and DuckDB. The new system lands raw inputs, transforms them in its own model, and the comparison happens in a layer that touches neither system’s source of truth.


Revert to Source: rebuild the data, don’t migrate it

The instinct with legacy data is to migrate it. Export the old tables, transform them, load them into the new schema. For upstream data this is usually a trap, and Fowler’s “Revert to Source” pattern says why.

The data in the legacy system is not the original data. It’s the original data after fifteen years of manual corrections, one-off fixes, a schema that got extended sideways three times, and business logic that lives in stored procedures nobody wants to open. Migrate that and you inherit every accumulated distortion, plus you have to reverse-engineer the transformations to know what the numbers even mean.

Revert to Source says: go back to where the data actually came from and rebuild it in the new model. Production volumes came from run tickets, meter readings, and OCC filings. Well identity came from permits and completions. Land data came from the original leases and division orders. Those sources still exist. Rebuild from them and the new system’s history is clean by construction, derived by logic you wrote and can test, not inherited from logic you can’t see.

You won’t do this for everything. Some data has no recoverable source (a hand-keyed adjustment from 2014 with no supporting document is just a number now). For those, you migrate the value and flag it as unverified, the way we treat any figure we can’t trace in the 42 Gallons series on lineage and provenance. But for the data that has a source, rebuilding from it is how you avoid carrying twenty years of somebody else’s shortcuts into the system that’s supposed to fix them.

There’s a bonus. Rebuilding from source forces you to actually understand the business logic before you reimplement it. Which is exactly the failure mode we see kill PPDM implementations: teams that reproduce the old model’s structure without understanding why it was that way, and end up with the same problems in a newer database.


The dual-run phase and what it costs

Between “new system exists” and “old system is gone” is the dual-run phase, where both are live. This is the expensive part, and pretending it’s free is how migrations go over budget.

You’re paying for two systems at once: two licenses (or one license plus your build), two sets of infrastructure, and the human cost of somebody reconciling the two outputs every cycle. That last one is the real bill. Somebody has to look at the diffs, decide which are benign, and chase the ones that aren’t. During dual-run, your team’s monthly close effort goes up, not down, because they’re closing on the old system and validating the new one in parallel.

That’s uncomfortable, and it creates pressure to cut it short. Resist that. The dual-run isn’t overhead you’re tolerating. It’s the test suite. Every cycle both systems agree is evidence you can trust the new one. Cutting over after one clean month is cutting over on one data point.

So how do you know when to actually cut over? The signal isn’t a date on a plan. It’s a run of clean cycles. Concretely: the two systems produce matching numbers for every material output across enough consecutive monthly closes that the remaining diffs are all understood and accepted. For a production accounting migration we’d want at least two or three full close cycles with clean, explainable diffs before the new system becomes authoritative, and we’d keep the old one readable (not running, just queryable) for a while after, because the first time someone questions a historical number you’ll want to answer it.

Cut over one capability at a time where you can. Allocation before revenue distribution. Reporting before the accounting handoff. Each capability that moves reduces what the old system is still responsible for, and shrinks the blast radius if the next one has a problem.


The upstream complication: close is a hard wall

Everything above assumes you can pick your moments. Upstream you mostly can’t, because monthly close is a hard deadline that doesn’t move.

Close creates a recurring window where nothing changes. You do not deploy a new allocation method on the 12th when accounting is trying to distribute revenue. You do not repoint the accounting feed mid-close. The pipeline that was working on the 5th needs to still be working, identically, on the 15th, because the people running close have enough to deal with. This constrains when displacement steps can happen down to roughly the back half of each month, after close is done and before the next one spins up.

It also sets the rhythm of the whole project. Your dual-run validation is measured in closes, not weeks, because close is when the numbers that matter get produced. A migration that would take three months of calendar time in a business without a monthly close takes as many months as you need clean closes, and you get exactly one close per month to collect. There’s no speeding that up. You can start earlier and you can run more comparisons per cycle, but you cannot manufacture extra closes.

Plan the cutover for the quietest point in the cycle, which for most operators is right after a clean close, giving the new system a full month as the authoritative source before it has to survive a close on its own. And keep the old system available through the first close the new one runs solo. The cost of leaving it queryable for another sixty days is nothing next to the cost of needing it and having already turned it off.


Where this leaves you

The operators who get off legacy systems successfully aren’t the ones with the biggest budget or the newest platform. They’re the ones who stopped treating it as a replacement and started treating it as a displacement: intercept the flow, rebuild from source instead of migrating scar tissue, run both in parallel until the numbers agree across real closes, then let the old system fade out instead of ripping it out.

It’s slower than the big-bang plan on paper. It’s much faster than the big-bang plan that fails three days before close and sends everyone back to the old system with a quarter of the year gone. If you’re staring at a legacy system you can’t afford to keep and can’t afford to break, that’s the tradeoff worth taking.

This is the harder cousin of the greenfield case. If you’re starting from spreadsheets rather than an entrenched application, the sequencing in From Spreadsheets to a Real Data Stack is a gentler path. And if the thing you’re actually replacing is the plumbing that moves the data rather than the application it lives in, that’s a different (and often parallel) project: see Migrating Legacy ETL to Modern Pipelines and Patterns.


Get in touch


  1. Ian Cartwright, Rob Horn, and James Lewis, “Patterns of Legacy Displacement,” martinfowler.com (2024). https://martinfowler.com/articles/patterns-legacy-displacement/ ↩︎