Migrations Without Downtime
Schema changes are the riskiest database operation. Most can be done with no downtime if you know which operations lock tables.
Migrations as code
PandaDev uses Alembic through Flask-Migrate:
flask db migrate -m "add super_admin to users" # generate from model changes
# REVIEW the generated file: autogenerate misses things
flask db upgrade # apply
flask db downgrade # roll back one step
Every migration is committed to Git and runs in the same order everywhere. Never edit a production schema by hand.
Operations and their locks
| Operation | Safe on a busy table? |
|---|---|
| Add a nullable column | yes, instant |
| Add a column with a constant default (PG 11+) | yes, instant |
Add NOT NULL to an existing column |
scans the table while locked: use the pattern below |
| Create an index | only with CONCURRENTLY |
| Add a foreign key | use NOT VALID, then VALIDATE separately |
| Change a column's type | usually rewrites the table: avoid, add a new column |
| Rename a column or table | instant, but breaks running code: use expand and contract |
Always set a lock timeout
A migration waiting for a lock blocks every query queued behind it. Fail fast instead:
SET lock_timeout = '5s';
Adding NOT NULL safely
ALTER TABLE users ADD CONSTRAINT users_country_nn CHECK (country IS NOT NULL) NOT VALID;
-- backfill in batches, then:
ALTER TABLE users VALIDATE CONSTRAINT users_country_nn; -- no write lock
ALTER TABLE users ALTER COLUMN country SET NOT NULL; -- PG 12+ uses the valid constraint, no scan
ALTER TABLE users DROP CONSTRAINT users_country_nn;
Expand and contract
To rename fullname to display_name without breaking the running app:
- Expand: add
display_name(nullable). Deploy code that writes both columns. - Backfill: copy old values in batches of a few thousand rows.
- Switch reads: deploy code that reads
display_name. - Contract: once nothing uses
fullname, drop it in a later release.
Each step is backward compatible with the code running beside it, so deploys and rollbacks are always safe.
Batch backfills
UPDATE users SET display_name = fullname
WHERE id IN (SELECT id FROM users WHERE display_name IS NULL LIMIT 5000);
Repeat until zero rows are updated, pausing briefly between batches. A single huge UPDATE holds locks and creates massive bloat.
Before every production migration
- [ ] Tested on a recent copy of production data
- [ ] Timed: you know how long it runs
- [ ]
lock_timeoutset - [ ] Backup or PITR point confirmed
- [ ] Rollback plan written
Check yourself
- Why is a lock timeout essential?
- Describe the four steps of expand and contract.
- Why backfill in batches?
Seu progresso fica salvo apenas neste navegador.
Enviar para revisão (10 pontos)