Status: AcceptedArea: BackendDate: 2026-07-02
Context
Two things make migrations risky during a deploy:- Locks. A blocking
ALTERor an unbatchedUPDATEon a hot table takes anACCESS EXCLUSIVElock, which can stall live API traffic until it finishes. - Deploy order. The API deploys before the workers. If a migration ships in the same PR as the code that needs it, that code can go live before its schema exists.
Decision
- Generate migrations with
alembic revision --autogenerate. Every migration inheritsSET LOCAL lock_timeout = '5s'from the template. Raise it for a busy table. - Add a NOT NULL column across separate PRs: add it nullable, backfill with a batched script
(
run_batched_update), then enforce NOT NULL. - The enforce migration runs an unconditional
UPDATE ... WHERE col IS NULLimmediately beforeSET NOT NULL. The batched script does the real work first, so by the time the migration runs thisUPDATEmatches nothing and rewrites nothing. - Keep migration PRs isolated from application code.
UPDATE stays because the script misses two things: environments where nobody ran it, and
rows written NULL between the backfill and the deploy. Either one fails SET NOT NULL and
strands the schema on the previous revision.
Consequences
- A lock-holding migration fails fast at the lock timeout instead of taking the database down.
- Large backfills run outside the deploy path, in controlled batches.
- Code never ships ahead of the schema it needs.
- CI enforces this: a “Migration Isolation Check” gates which files travel together, and
alembic checkcatches model-versus-migration drift. - The batched script stays mandatory. The migration’s
UPDATEis only cheap because the script has already left it nothing to do.
Alternatives considered
- Add NOT NULL in one step and let the migration’s
UPDATEbackfill every row: rewrites the table under lock during deploy. The batched script runs first precisely so the migration’sUPDATEhas nothing left to do. - Ship a migration in the same PR as the code that depends on it: breaks the API-before-workers ordering.
References
server/migrations/script.py.mako,server/migrations/env.py,server/migrations/README.md.server/scripts/helper.py(run_batched_update) and theserver/scripts/backfill_*.pyscripts. CI:.github/workflows/test_server.yaml(migration-check).

