The Database Migration Tax Nobody Budgets For
The Database Migration Tax Nobody Budgets For
When a team decides to change databases, the estimate usually centres on moving the data. How many tables, how many rows, how long does the transfer take, what does the cutover window look like.
That part is a project with a known shape. Tooling exists. People have done it many times and written about it.
The part that actually costs you is somewhere else entirely, and it does not appear in the estimate because it is not visible in any inventory of tables.
Application code absorbs the database
Over time, code written against a particular database quietly encodes that database's semantics. Not deliberately — it happens through ordinary development, one reasonable decision at a time.
Transaction boundaries. Code written against a store with real transactions groups related writes and assumes they succeed or fail together. Code written against a store without them contains compensating logic — retries, reconciliation passes, idempotency keys — built to handle partial failure.
Consistency assumptions. Code written against a strongly consistent store reads its own writes immediately and assumes it will see them. Code written against an eventually consistent store contains defensive patterns: polling loops, staleness tolerances, deliberate delays before verification.
Query patterns. Code written against a database with good joins fetches related data in one query. Code written against one without them does multiple round trips and assembles the result in application memory, with caching layered on to make it acceptable.
Error handling. Every database has its own vocabulary of failures — deadlocks, serialisation conflicts, throttling, partition unavailability. Application code learns to catch, retry, and reason about those specific errors.
None of this is documented. It is diffused through the codebase in thousands of small decisions made over years by people who have since moved on.
Why migrating in one direction is worse
The two directions are not symmetric, and the asymmetry matters.
Moving from a weaker consistency model to a stronger one is inconvenient. All that defensive code becomes dead weight — retry loops that never trigger, reconciliation jobs with nothing to reconcile, idempotency machinery protecting against a failure mode that no longer exists. Wasteful, confusing to newcomers, and harmless.
Moving from a stronger consistency model to a weaker one is dangerous.
Code written assuming it can read its own writes does not fail loudly when that assumption stops holding. It fails occasionally, under load, in ways that do not reproduce in testing and pass every test you have. A user updates a setting, the next page load reads a replica that has not caught up, and the setting appears not to have saved. Intermittently. For some users. Under certain conditions.
That is the worst failure profile a system can have, because it survives review and testing and surfaces as a slow accumulation of support tickets that nobody can reproduce.
Two practices that reduce the exposure
Neither makes migration cheap. Both make it survivable.
Keep database-specific logic behind a repository boundary. Not a full abstraction layer attempting database independence — those tend to produce a lowest-common-denominator interface that gives up the useful features of whatever you chose. Just a consistent boundary where queries and transaction handling live, so the semantics are concentrated in one reviewable place rather than diffused through business logic.
Write down your consistency assumptions explicitly. When code depends on reading its own write, or on two operations being atomic, or on a particular isolation level, say so in a comment or an architecture note. An undocumented assumption cannot be checked during a migration, and finding these by running the code in production is how the subtle failures happen.
The real conclusion
The point is not that migration is impossible. Teams do it successfully.
The point is that the difficulty of reversing the decision should raise the effort you put into making it. Not months of evaluation — an afternoon spent writing down what the system genuinely cannot tolerate, rather than ten minutes with a ranked list of databases.
And when nothing in that list of intolerances rules out the well-supported default, take the default. For most systems in 2026 that is PostgreSQL, and most systems that choose it never need to revisit the decision at all.
Which is a far better outcome than migrating well.
Frequently Asked Questions
Why is moving the data the easy part?
Because it is a bounded project with mature tooling and well-documented practice. The unbounded part is application code that absorbed the old engine's transaction, consistency, query, and error-handling semantics over years.
Which migration direction is riskier?
Moving to a weaker consistency model. Code assuming it can read its own writes fails intermittently under load rather than loudly, passes all tests, and surfaces as irreproducible support tickets.
Should we build a database abstraction layer?
Generally not a full one — they tend to produce a lowest-common-denominator interface that discards the features you chose the database for. A repository boundary that concentrates queries and transaction handling in one place is the better tradeoff.
How do you document consistency assumptions?
Note them where the dependency exists: where code reads its own write, requires atomicity across operations, or relies on a specific isolation level. An assumption nobody wrote down cannot be verified during a migration.
What is the practical takeaway?
Spend a little more effort on the initial choice because reversal is expensive. Write down what the system cannot tolerate; if nothing rules out the well-supported default, take it and move on.
Full guide: Best Database Software in 2026: A CTO Selection Guide
TechCirkle builds custom software and data-intensive platforms.

Qué verdad lo del código defensivo que queda como peso muerto, sobre todo esos bucles de reintento que ya no hacen falta tras la migración. No es lo mismo mover tablas que limpiar años de mañas metidas en la aplicación, la diferencia se nota. Súper práctico para tenerlo en cuenta en los presupuestos.