im·a·cto

From the log

Back to the log

The Migration Is Not a Diff

Your whole delivery system is a machine for making mistakes cheap to take back. It has exactly one blind spot, and a fleet now walks through it a dozen times a day.

The pull request is twelve lines. It adds a column, backfills it from an existing one, and drops the old one. The fleet wrote it, the tests are green, the description is clear, and it looks exactly like the forty other clean diffs you approved this week. You merge it Thursday afternoon. Nine minutes later the error rate goes vertical, and you do the thing you have trained your entire org to do without thinking. You revert. The deploy rolls back in ninety seconds. The column is still gone.

Everything in your pipeline rests on a promise that you can go back. Git, the merge train, blue-green, the canary, the flag you wrapped the feature in. All of it is the same bet placed over and over: whatever we just did, we can undo. That bet is good on every artifact in your repository except one.

revert is a word about source code

Revert restores a file to a previous shape, and it works because a file has no memory of having been the other shape. A database is the opposite kind of object. It is nothing but memory.

Your code is a statement about what should happen next. Your data is the record of what already did. You can regenerate the first from almost nothing now. There is no regenerating the second.

That distinction has always been true and it mostly did not matter, because the schema was protected by friction. Migrations were rare because writing one was real work, and the person writing it was usually the person who would be awake at 3am if it went badly. Call that discipline if you like. It was friction doing the job of judgment, and it held up for twenty years because it was expensive to skip.

the friction is gone and nothing replaced it

A fleet will write the feature, the migration that feature needs, the backfill, and the cleanup that drops the old column, in one pass, on one branch, under one title. Every line of it is plausible. Every line goes through the same review, at the same speed, with the same posture. Nothing in that diff raises its hand and says: this line is the one that does not come back.

I have written before about reading the decisions instead of the diff, and this is the sharpest case for it I know. The decision to drop a column does not look bigger on screen than the decision to rename a variable. It occupies one line. It is the cheapest-looking thing in the change.

It helps to look at what the industry already pays to do this safely. GitHub built gh-ost because altering a table on a live MySQL system is not the same category of act as shipping code. It reads the binary log stream, applies changes to a ghost table off to the side, throttles itself when the primary gets unhappy, and lets an operator pause the whole thing in flight. That is purpose-built infrastructure, running at GitHub’s size, to make a single ALTER survivable. Nobody builds that for a code change.

The pattern for the change itself has been written down for over a decade. Danilo Sato called it parallel change on Martin Fowler’s site: expand, migrate, contract. Add the new shape while the old one still works. Move readers and writers across. Only then remove what nobody uses. Three phases, three releases, and the entire value sits in the boring middle, where both shapes are valid at once and you are still free to change your mind.

Now look at that pattern through a fleet’s eyes. Expand, migrate and contract are all obviously part of the same change, so an agent asked to rename a column will do all three, correctly, in one PR. It is being thorough, not careless. The safe version of that work is the one that ships looking unfinished, and “looks unfinished” is not a property any model optimizes for, or one your review process has ever rewarded.

what I actually bought with twenty years of this

I have spent my career making changes cheap to take back. At CommercialTribe I moved us onto GCP and Kubernetes and turned a release cadence measured in weeks into one measured in hours, and the reason that mattered was never raw speed. A short path back is what makes a team brave. At GigSmart I built the developer experience and the release path before there was much product to release on them, for the same reason.

So I want to be precise about what all that work purchased. It purchased recoverable deploys. It did not purchase recoverable state, and I am not sure I ever said that out loud clearly enough.

It gets sharper if you take my own advice. I tell people to default to Postgres and keep defaulting to it far longer than they think they can, and I still believe that. One engine you understand deeply beats five you each half-operate. But consolidation concentrates risk exactly as much as it removes it. One database is one blast radius, which makes the schema the highest-leverage object in the entire system. The thing I like most about that architecture is the same thing that makes this particular failure expensive.

direction of travel

Here is the change I would make in how you read a diff.

Stop asking first whether the change is correct. Your fleet is good at correct, your tests are good at correct, and correct was rarely the thing that hurt you. Ask which direction the change travels.

Almost everything you ship is round trip. It goes out, and if it is wrong it comes home. A small minority is one way: dropping a column, deleting rows, overwriting a value in place, collapsing two fields into one. Those are not bigger changes. They are a different kind of change, and your pipeline has no idea the category exists.

A revert is not an undo.
It rewinds your code. Your data was awake the whole time.

Once you sort changes that way, the fix stops being “review more carefully,” which is advice nobody has ever successfully followed at fleet volume. It becomes a routing problem, and routing problems have real answers.

  1. Migrations ship alone. Own PR, own deploy, own review posture. If a schema change cannot go out by itself, it is not ready to go out. This also makes the round trip real: the feature can come home, because the feature is a separate thing that did not take the schema with it.
  2. Additive is routine, destructive is an event. Adding a column, a table, an index: let all of that move at fleet speed, because it does come home. Anything that removes or overwrites gets a human name on it and a written reason, every time.
  3. Let the dead column sit there. An unused column costs you close to nothing. Reclaiming it costs you everything on the day you were wrong about “unused.” Contract is a scheduled decision, not a tidiness reflex, and the gap between migrate and contract should feel uncomfortably long.
  4. Gate on the irreversible, not on the large. Your CI is full of checks asking whether the code is right. Add one that asks what this change costs if it is wrong. A rule that spots a drop, a delete, or an in-place update inside a migration and pulls it out of the autonomous lane is about an afternoon of work. That is the whole distance between autonomy with guardrails and autonomy with optimism.

None of that slows the fleet down in a way anyone will feel. The overwhelming majority of what it generates is round trip and should keep moving at exactly the speed it moves today. This is a narrow carve-out, which is precisely why it is easy to skip: the failure is rare, and when it lands it is total.

So go back to Thursday. The deploy rolled back in ninety seconds and the pipeline did everything it ever promised. That part worked. It was never the part worth worrying about. The question I would want someone on that team to have asked out loud before the merge is the one nobody asks anymore, because the diff was twelve lines and looked like all the others: if we are wrong about this, what brings it back?

Jason Waldrip is a fractional CTO and CAIO through The Bushido Collective, working with founders drowning in AI-generated code and teams scaling past the leadership that got them here. If your fleet is shipping migrations at the same speed it ships features, that is the conversation to have. Work with me.

A note on how this was made: I wrote this with AI assistance. The frame, the calls on what matters, and the correction about what a fast release path actually buys are mine. The model did most of the drafting. Worth saying plainly in a piece about handing work to machines.