Database migration
A versioned script that changes your database schema in a fixed order, so every environment ends up with exactly the same structure.
See it
What it is
A migration is a small, numbered script that moves your database from one schema version to the next: add a column, create an index, backfill a value. They are committed to the repo, applied in order, and recorded in a tracking table so each one runs exactly once per database. That record is what lets a fresh dev machine, CI, and production all end up with the same schema. Prisma Migrate, Drizzle Kit, Rails, Alembic, and Flyway all work this way. Versioning and ordering buy you consistency, not safety: a migration that drops a column drops it in every environment, faithfully and on schedule. Not losing data is something you design into the migration, not something the tool grants you.
The mental shift: your code and your schema deploy at different moments, and for a window of seconds or minutes both old and new code talk to the same database. So migrations must be backward compatible. The safe pattern is expand and contract: add the new column as nullable, deploy code that writes to both old and new, backfill in batches, switch reads over, then in a later release drop the old column.
Gotchas: renaming or dropping a column in one shot breaks every instance still running the old build, and rollback will not bring the data back. On Postgres, an unqualified ALTER TABLE or a CREATE INDEX without CONCURRENTLY can take a lock that stalls every query on a big table, so set a lock timeout. Backfills belong in batched background jobs, not inside the migration that is holding the deploy hostage. And test the down migration, or admit out loud that you do not have one.
Ask AI for it
Write a database migration for this schema change. Start by inspecting the repo for the database engine and the migration framework already in use, and follow that tool's conventions rather than assuming one. Make the change backward compatible with the currently deployed code using the expand-and-contract pattern: add new columns as nullable or with defaults, never rename or drop in the same release, and split the removal into a follow-up migration. Put any data backfill in a separate batched job rather than the migration itself. For index creation, use a dedicated non-transactional migration with CREATE INDEX CONCURRENTLY where the engine and the tool both support it (a concurrent build cannot run inside the transaction most migration runners wrap around each file), and otherwise use the safest native equivalent. Set a short lock timeout and avoid rewriting the whole table. Include a down migration only where reversal is genuinely safe; where it is not, say so plainly and document the forward-recovery steps instead. Finish with the exact deploy order of code and migration.