Back to Blog
migrationsbest-practicesdevops

The Myth of Down Migrations: Why Migrata Doesn't Need Them

Nathan OrtegaNathan Ortega
3 min read

There is a file type that exists in almost every mature backend codebase. It is diligently maintained, carefully reviewed in pull requests, committed alongside every schema change, and almost never executed.

The .down.sql file.

When I was building Migrata, one of the earliest design questions was whether to support down migrations at all. The answer I kept coming back to was no. Not because rollbacks are unimportant, but because the down file model fundamentally misrepresents how rollbacks actually work in practice.

Here is what I mean.

When a migration fails mid-execution, the database may be partially migrated. The down script has no way of knowing this. It was written before the fact, against an assumed final state that may no longer exist. Running it against a partially applied schema does not undo the damage; it layers a new class of errors on top of it.

The down file is a script written for the scenario where it is least likely to be needed and least likely to work.

Code rollbacks are safe because the old version of the code does not contain or own any state. You swap out the binary, and the system returns to the previous behavior.

Database schema rollbacks are different. The down migration for a ADD COLUMN is a DROP COLUMN. That statement does not remove a structural definition; it destroys the column's data. Any records written between the up and down migration are gone permanently. Re-running the up migration creates an empty column, not a restored one.

This is why, in practice, most teams instinctively avoid reverting database changes during an incident. The schema rollback trades one problem for a guaranteed second one: data loss.

There is an underappreciated timing problem with down files. They are written in the same commit as the up migration, which means they are part of a future version of your codebase. When a deployment fails and you need to roll back to a previous stable commit, that commit predates the down files by definition.

The down scripts you need are sitting in a future git commit you are actively trying to escape. The only way to use them is to manually cherry-pick or copy them out of the newer commit, at which point you have broken your reproducible deployment process and are doing manual database surgery during an incident.

This is not a theoretical edge case. It is the standard failure mode.


The Migrata Way: From Scripts to State#

When I built Migrata, I wanted to move away from the fragility of sequential migration files. Migrata brings the Infrastructure as Code philosophy to the database. Instead of a series of bash-like scripts, Migrata treats your schema as a desired state.

In Migrata, there are no .down.sql files to maintain. Because Migrata is state-based, it doesn't care about the history of how you got to your current schema. It only cares about the Diff between your existing database and your target SQL files.

If a deployment fails and you need to revert the database, you don't hunt for a script. You use your version control.

  1. Step back in Git: git checkout <previous_stable_commit>
  2. Generate the Diff: migrata diff --from <db_url> --to ./schema

Migrata inspects your live database, compares it to the SQL schema in your previous commit, and calculates the exact set of statements needed to return to that state. It’s automated, it’s computed at runtime based on the actual state of the database, and it’s correct every time.

Migrata doesn't just blindly execute DROP statements. With features like the --safe-cast flag, Migrata can detect risky operations and break them into safer, multi-step plans. Every migration plan generated by Migrata requires explicit approval, ensuring that a human eyes the potential impact before a single byte is deleted.

The database should be treated like any other part of your infrastructure. You don't write manual "un-terraform" scripts to manage your cloud resources; you update your configuration and let the tool handle the transition.

By moving away from manual down scripts and embracing a declarative, state-based approach, it makes deployments more predictable, rollbacks more reliable, and production environments significantly safer.

Stop writing scripts you'll never use. Start trusting the diff.

Nathan Ortega

Nathan Ortega

Author