A database migration is a versioned change to your database schema or data — added columns, renamed tables, backfilled values.
A database migration is a versioned, reproducible change to your database schema or data — adding a column, renaming a table, backfilling values, changing a constraint. Unlike application code deploys, migrations are stateful: rolling back means either running a reverse migration (rare, usually incomplete) or restoring from backup (slow, disruptive). The asymmetry between deploy and rollback is why database changes deserve more care than application changes, not less.
The standard technique for zero-downtime schema changes is expand-contract (also called parallel change). To rename a column: (1) Expand — add the new column, dual-write from application code so both old and new stay in sync. (2) Migrate — backfill historical data into the new column. (3) Verify — reads and writes go against the new column, but old still populated. (4) Contract — remove writes to the old column, then remove the old column. Each step is independently deployable and reversible. Doing all steps in one migration is where outages come from.
Any migration that acquires a table lock for more than a few seconds is dangerous on a live database. Postgres examples: ALTER TABLE ADD COLUMN with a default value (in older versions) rewrites the whole table; adding an index without CONCURRENTLY blocks writes; changing a column type often rewrites. Rules that prevent most outages: never run a schema change that locks a large table in a single transaction; use CREATE INDEX CONCURRENTLY; batch data migrations into 1000-10000 row chunks with commits between; run heavy migrations off-peak with monitoring.
Every mature stack has migration tooling — Alembic (Python), ActiveRecord (Rails), Prisma Migrate (Node), Flyway (JVM), Supabase migrations (Postgres). Requirements for any tool: (a) migrations are files in version control, reviewed via PR. (b) applied in strict order, tracked in a metadata table. (c) reproducible against a fresh database. (d) previewable (generate SQL without executing). Rolling your own migration mechanism is a common early mistake that becomes expensive to replace once you have production data.
Migrations should be tested against realistic data volumes, not empty local databases. A migration that runs in 200ms locally may take 40 minutes on production data. Common approach: maintain a staging database that's a scrubbed snapshot of production, run migrations there first with timing measured, then apply to production. Never apply a migration to production that hasn't run successfully in a staging environment with similar data volume.
Dropping columns and tables is irreversible in practice — even with backups, the operational cost of restore is high, and any writes since the drop are lost. Best practice: never drop in the same PR that stops using; wait one full deploy cycle (or one week, whichever is longer) between 'application no longer reads this' and 'schema drops this.' The gap gives you room to notice mistakes when the cost of recovery is a code revert, not a database restore.
Investor directory · Fundraising library · Articles A–Z · Company funding database