Warming up the neural circuits...
By the end of this chapter you will:
Schema changes are inevitable. The only question is whether they are controlled.
Without migration discipline, teams end up with:
Alembic gives you a versioned schema timeline that can be reviewed, tested, and executed in .
Migration quality is backend reliability quality.
Each migration file is a node with:
Together these files a directed graph from old schema states to current .
alembic init migrationsIn migrations/env.py, point Alembic at your model metadata:
from app.db.models import Base
target_metadata = Base.metadataSet database URL from environment or settings module for consistency across local and CI runs.
alembic revision --autogenerate -m "add users email unique constraint"Autogenerate is a draft, not a guarantee. Review for:
alembic upgrade head
alembic downgrade -1
alembic current
alembic history --verbosePractice downgrades locally before claiming rollback readiness in production.
Schema migration changes structure: tables, columns, indexes, constraints.
Data migration changes contents: backfills, passes, value transformations.
Example migration pattern:
from alembic import op
import sqlalchemy as sa
def upgrade() -> None:
op.add_column("users", sa.Column("is_active", sa.Boolean(), server_default=sa.true(), nullable=
For large backfills, prefer operational scripts or chunked jobs over giant transactional migration steps.
If two heads exist, create merge revision:
alembic heads
alembic merge -m "merge feature heads" <head_a> <head_b>Never edit already-applied migrations in shared environments. Add a corrective migration instead.
Treat migration scripts like production code. Review them, test them, and run them with the same rigor as application releases.
| Scenario | Better move | Why |
|---|---|---|
| New nullable column | Add column first, then app write/read rollout | Minimizes deployment coupling |
| Large backfill needed | Incremental backfill job | Avoids long lock windows |
| Rename column | Add new column + dual-write + backfill + remove old later | Safer than hard rename in one step |
| Parallel branch migrations | Merge revision | Keeps migration graph linearized for deploy |
| Emergency rollback | Revert app + run tested downgrade path if safe | Controlled recovery sequence |
| Mistake | Why it hurts | Better move |
|---|---|---|
| Blind trust in autogenerate | Missed constraints and risky operations | Review generated SQL intent manually |
| Editing old applied migration files | Environment divergence and audit breakage | Create a new corrective migration |
| Mixing heavy data backfill in schema step | Long-running locks and timeout risk | Separate data migration execution |
| No downgrade testing | False rollback confidence | Test downgrade path in staging/local |
| Manual production SQL outside Alembic | Untracked drift | Version every schema change |
Existing SQLAlchemy Base metadataWorking alembic revision generationNew SQLAlchemy modelRevision with create_table opProduction table with dataNo write outage during deployTwo revision head idsSingle merged migration pathStaging database snapshotMeasured rollback confidence and caveatsBeginner:
"Why are migrations necessary if models already define schema?"
Models define desired shape; migrations define versioned, executable transitions across real environments.
"What does alembic revision --autogenerate actually do?"
It compares model metadata to database state and drafts migration operations that you must review.
Senior:
"How do you design zero-downtime schema migrations for large tables?"
Use expand-contract sequencing, avoid long locks, and separate backfills from structural changes.
"What is your policy on editing applied migration files?"
Never edit applied revisions in shared environments; issue a corrective migration to preserve history integrity.
Model change -> autogenerate draft -> review -> migrate -> verify
Schema migration != data migration
Never rewrite applied migrations
Practice rollback before production needs itWhat is the best interpretation of alembic revision --autogenerate output?