Why You Will Never Write Another "Down" Migration

Why You Will Never Write Another "Down" Migration

If you've used frameworks like Rails, Django, or even Hasura, you are probably familiar with the concept of "up" migrations and "down" migrations. These might also be called forwards and backwards, or in flyway they are called "undo" migrations.

For the unfamiliar, the migrations we are talking about here are a set of schema changes for a relational database such as Postgres, MySQL, etc. Sometimes these are generated by an ORM based on models, and sometimes they are written by developers. They're almost always (and should be) committed to version control systems, and must be executed in a particular order.

Our "up" migrations might create some tables, alter some columns, set constraints, and so forth. This is normally a straight-forward affair. Developers do this day in and day out, we test these on our local before committing them, and if we're going to run alterations on existing tables, we usually test them out on prod-like data to measure impact. We're confident about up migrations.

My case against down migrations starts rather predictably; when do you run them? Hardly ever. How confident are you that every down migration in source control was tested before it was executed? Was it tested after prod-like data was inserted into the table?

I know the answer to both those questions is "Oh s#%T!" or an amused but panicked chuckle to yourself. The fact is that when it comes time to fix a schema problem in production, those down migrations you've been saving for a rainy day? There is NEVER a situation where they are the right solution.

Teams sometimes laugh nervously amongst themselves that their disaster recovery plans are an entirely unknown quantity because they've never tested them. Down migrations are like hundreds of untested disaster recovery plans, only much much worse.

Putting down migrations in source control, and then deploying them to production, is like enjoying a TV dinner with unexploded ordnance on the coffee table. At some point, someone is going to mistakenly run "down" in prod thinking it was dev/local/etc. At least you've practiced your disaster recovery though, right? Hey how often do your automated backups run and when did you last check them? Go, I'll be here when you get back!

...Oh you're back? Are you okay? You look stressed! Anyway, even if you do successfully run a down migration at some point and it proves useful... now what? Your migration history no longer makes sense. The fact that X, Y, or Z was run in production to change the schema in some particular way and unpick some mess, is lost to history.

I propose you forget all about down migrations. Delete them all from source control right now, I promise you no one will miss them.

What happens if you do make a change you want to rollback in production? Ah yes, you carefully figure out how to roll forward! You write a new "up" migration to clean up your mistake. That's right, you write your drop table or alter column statements in an up migration. You prepare a run sheet, you test, and you go through your normal SDLC for deploying schema changes (You do run migrations via CD right?) Now your changes are in source control. They're part of history, as they should be, because it happened, and all future migrations will safely run on top of that history.

So what do you think? Are you going to delete your down migrations from source control? I'd love to hear about it if you do! Come back and tell us how it went in the comments!

Photo by Avery Nielsen-Webb from Pexels