Database Migrations
Why It Matters
Database schema changes are code changes. Migrations make them repeatable, reviewable, and deployable across developer machines, CI, staging, and production.
Core Concepts
- A migration is an ordered change to database structure or seed/reference data.
- Migrations are usually tracked in a table so each runs once.
- Forward migrations are more important than perfect down migrations in production.
- Large table changes may need expand-and-contract deployment.
- Data backfills should be batched and observable.
Flow to Remember
A developer writes a migration, tests it locally, CI runs it against a test database, deployment applies it before or alongside application code, and the migration history records success.
Syntax and Examples
-- 202606160001_create_tasks.sql
CREATE TABLE tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
completed boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX tasks_created_at_idx ON tasks (created_at DESC);
-- Later application code can safely query tasks after this migration runs.Use Cases and Tradeoffs
- Use migrations for tables, columns, indexes, constraints, enums, and reference data.
- Use expand-and-contract when removing or renaming fields used by running application versions.
- Use separate backfill jobs for huge data updates.
- Review migrations for locks, table rewrites, and index build strategy.
Common Mistakes
- Manually changing production schema without recording it.
- Combining risky data backfills with unrelated schema changes.
- Dropping a column before all deployed code stops reading it.
- Running slow migrations during peak traffic without testing lock impact.
Practical Challenge
Plan a safe migration to rename users.full_name to users.display_name while old and new application versions may run during deployment.
Recap
- Migrations are version control for database shape.
- Deployment order matters.
- Large production changes need compatibility and observability.