Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
Migrations Discipline
How to change a live schema safely: backward-compatible rollout steps, no same-migration drops, and lock-aware changes on large tables.
Mby @markdownersPublished July 12, 2026 · Updated August 21, 2026 · ~2 min read
0 downloads · Used in: SaaS starter context
Every schema change is a migration, and every migration follows the same discipline as any other code change: reviewed, tested, and safe to run against real data. Assumes Database Design Basics@markdowners/database-design-basics; see Postgres Conventions@markdowners/postgres-conventions for engine-specific type choices referenced below.
Migrations are the only way to change schema
- Never make a manual, ad-hoc schema change directly against any shared environment (staging, production) — every change goes through a migration file, checked into version control, applied the same way in every environment.
- Every migration file is immutable once merged/deployed — to fix a mistake, write a new migration that corrects it, never edit a migration that's already been applied anywhere real.
Backward compatibility during rollout
- Assume the old and new application code can both be running against the database simultaneously during a deploy (rolling deploys, multiple instances mid-rollout) — a migration that breaks the currently-running old code before the new code deploys causes an outage during the deploy window itself, not after.
- Add a new nullable column, then backfill, then add
NOT NULLin a later migration — never add aNOT NULLcolumn with no default directly to a table with existing rows in one step; it either fails outright or locks the table for the backfill duration. - When renaming a column, do it as: add new column → dual-write in application code → backfill → switch reads → remove old column, across multiple deploys — never a single-step rename that old code can't tolerate mid-deploy.
Destructive changes
- Never drop a column or table in the same migration that stops using it — deploy the code change that stops using it first, confirm it's live and stable, then drop the now-unused column/table in a follow-up migration.
- Treat every
DROP,TRUNCATE, and irreversible data transformation as requiring a second reviewer's explicit sign-off before it runs against production data.
Locking and large tables
- Be aware that some schema operations take a table-level lock for their duration (adding certain constraints, some column additions on older engine versions) — on a large, high-traffic table this can cause a visible outage; use the engine's documented online/concurrent variants where available.
- Run migrations against a realistic data volume in staging before assuming a migration is "quick" — a migration that's instant on a 500-row dev table can lock a 50-million-row production table for minutes.
Reversibility and testing
- Write a rollback (
down) migration alongside every forward migration where the engine/tooling supports it, and actually test the rollback path, not just the forward path. - Run every migration against a copy of production-shaped data (or a realistic seed) in CI before it's mergeable, not just against an empty local database.
Requires
- Database Design Basics@markdowners/database-design-basics
You might also add
- Postgres Conventionssuggested
Badge
Link back to this module from your own README.
[](https://markdowners.com/m/markdowners/migrations-discipline)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.