Goldfish

opinion

Why "Lift and Shift" Is a Lie for Heterogeneous Migrations

The phrase was invented for moving the same database to new hardware. Using it for Oracle-to-Postgres is how budgets die.

Matt Yonkovit · 4 min read

“Lift and shift.” It sounds so clean. Pick the thing up, set it down somewhere else, done. And for some migrations, it’s honest — moving a PostgreSQL instance from an on-prem box to RDS is genuinely close to lift-and-shift, because the engine doesn’t change. Same dialect, same behavior, same everything; you’re swapping the floor under the furniture.

The phrase gets dangerous the moment someone uses it for a heterogeneous migration — Oracle to PostgreSQL, Sybase to Postgres, SQL Server to anything. Because now you’re not moving the furniture to a new room. You’re translating it into a different language and hoping the jokes still land. They don’t, automatically, and “lift and shift” is the phrase that convinces a steering committee otherwise right up until the demo breaks.

What actually changes when the engine changes

Here’s what “lift and shift” quietly assumes stays constant, and what actually doesn’t:

The dialect changes. CONNECT BY, ROWNUM, TOP, FETCH FIRST, backtick identifiers, DECODE — every source database speaks its own SQL, and a meaningful slice of your queries are written in it. Those have to be translated, not moved.

The types change, and not one-to-one. Oracle’s NUMBER, SQL Server’s MONEY and DATETIME2, MySQL’s ENUM and unsigned ints — they map to PostgreSQL types, but the mapping has opinions, and some of those opinions affect precision, range, and rounding.

The behavior changes, which is the killer I keep coming back to. Empty-string-is-NULL, case-insensitive collation, LEN() trimming spaces, DECIMAL division scale, blank-padded CHAR comparison. The SQL ports clean and then returns different answers. No lift-and-shift tool catches this, because it’s not in the structure — it’s in the runtime.

And the transaction and concurrency semantics change. Isolation-level defaults differ. Locking behavior differs (PostgreSQL’s MVCC means readers don’t block writers, which quietly invalidates a decade of NOLOCK and WITH UR workarounds). Code that was tuned around one engine’s locking model behaves differently under another’s.

None of that is “lift.” All of it is “translate, verify, and re-test.” Calling it lift-and-shift doesn’t make the work disappear; it just means nobody budgeted for it.

Why the lie is so seductive

I get why it sticks. The schema does lift and shift, mostly. You can run a DDL converter, watch the tables and indexes come across in an afternoon, and feel like you’re 80% done. You’re not. You’re done with the 20% that was always going to be easy and visible, and you’ve not yet touched the application code, the embedded SQL, the stored procedures, or the behavioral landmines.

The schema converting smoothly is the most dangerous moment in a heterogeneous migration, because it generates false confidence at exactly the wrong time. The slide says “database migrated: ✅.” The reality is the database is the part that was never hard.

What to say instead of “lift and shift”

I’m not going to pretend a phrase fixes a project, but framing shapes budgets, and budgets shape outcomes. So when someone says “we’ll just lift and shift off Oracle,” the correction I’d push is: this is a translation project with a verification problem, not a relocation.

That reframe does real work. A relocation gets estimated by data volume. A translation-with-verification gets estimated by how much application code touches the database, how much of it is in the source dialect, and how many behavioral traps are hiding in it — which is exactly the thing you can’t know until you assess. So the honest sequence is: assess first to size the real work, then estimate, then move. Not “lift and shift, we’ll deal with the code later.” The code is the migration. The bytes were never the problem.

If you take one thing from this: be suspicious of any plan, pitch, or tool that uses “lift and shift” for a cross-engine move. Either they don’t understand what changes when the engine changes, or they’re hoping you don’t. Both should make you reach for an assessment before you reach for a timeline.


Swordfish is an open-source (Apache-2.0) assessment harness for migrating Oracle, MySQL, SQL Server, Sybase, and DB2 to PostgreSQL — it shows you what’s in your codebase, what needs to change, and hands scoped tasks to the copilot you already use. Source: github.com/EnterpriseDB/swordfish-migrations