Skip to content
Build in Public

The migration that nearly nuked my bookings table

Yesterday I was tired. Today I was scared, which — strangely — is a more useful feeling.

SD
Shubham Datarkar
· 3 min read
Updated

Yesterday I was tired. Today I was scared, which -- strangely -- is a more useful feeling. Fear at least wakes you up.

Here's what happened. I needed to add a new column to the bookings table. Routine stuff. As the product grows, the data it stores grows with it, and every so often you have to reshape the table to hold something new. This is called a migration -- a controlled change to the shape of your database. I'd done it before. I did not expect today's to try to take the whole table down with it.

I wrote the change, ran it, and started seeing something that turns your blood cold: booking inserts failing in the logs. New bookings -- the single most important thing my product does -- refusing to save. Errors piling up. And bookings are the bookings table. This isn't a cosmetic feature breaking. This is the heart.

The cold sweat is real and specific. For a few minutes, before I understood what was happening, my brain went straight to the worst place: did I just corrupt or lose booking data? On a product whose entire job is to reliably hold people's bookings and their money, "the bookings table is rejecting writes" is about as bad a sentence as I can imagine reading in my own logs. I've talked a lot in this series about catching bugs before real customers hit them. This was a rehearsal for the nightmare where I don't.

The cause, once I stopped panicking enough to actually read it: I'd added the new column as required -- every row must have a value -- but with no default value and existing rows already in the table. So the database looked at all the bookings already sitting there, each now missing a mandatory field, and essentially said "these are invalid, and I won't accept new ones that don't satisfy the rule either." A required field with nothing to fill it and no instructions for the existing data is a contradiction, and the database resolves contradictions by refusing to cooperate. Correctly, even. It was protecting the data's integrity. I was the one who wrote the impossible rule.

There was also a nastier wrinkle underneath, specific to how Supabase caches the shape of your schema -- there can be a lag between changing the structure and everything recognizing the change, which made the failures flicker in a way that muddied the diagnosis for a bit. That lag pushed me toward a defensive habit I've now fully adopted: writing my code so that it doesn't blindly assume a brand-new column is already there everywhere -- a fallback, so a schema that's mid-change doesn't crash the app.

The recovery was straightforward once I understood it. But the lesson is the real output of today, and I've written it into my rules in blood: every migration is additive first, then backfill, then tighten. Add the new column as optional, with a safe default. Fill in the existing rows properly. Then, once every row is valid, make it required if you need to. Never drop an impossible constraint onto a table that's already full of data and already serving live traffic. You don't renovate a bridge by removing it while cars are on it. You build the new lane alongside, move the cars over, then close the old one.

I wrote the whole discipline down -- the exact order, the fallback pattern, the "assume nothing about a fresh column" rule -- because the only thing worse than a scary bug is a scary bug you get to have twice. Fear that teaches you something is cheap at the price. Fear you don't learn from is just trauma with extra steps.

So: the bookings table lives. Nothing was actually lost -- it was rejecting new writes, not destroying old ones, which is the merciful version of this disaster. My hands were shaking a little for an hour. But I came out with a permanent rule and a table that's exactly as intact as it was this morning.

And then, right after the scare, something happened that yanked me straight back up -- a message from a customer that reminded me why the table is worth protecting in the first place. That's the next post, and it's a good one.

Explore more from Shubham — discover curated reads at BookASloth, or check out Rajmudra Media for media & marketing solutions.

Explore more from Shubham — discover curated reads at Book AS loth, or check out Rajmudra Media for media & marketing solutions.

by Shubham DatarkarBuild in Public

Reactions

How was this article?

Newsletter

Never miss the next one

Get the latest playbooks, build logs, and the occasional unpublished idea straight to your inbox. One signal a week — no noise.