Safe recipes
Add a NOT NULL column
Unsafe: ADD COLUMN x text NOT NULL DEFAULT 'a' on an old engine rewrites the table.
Safe:
- Add the column nullable, no default.
- Deploy code that writes it on every new row.
- Backfill existing rows in batches.
- Add the
NOT NULLconstraint, validated separately where the engine allows.
Rename a column
Never rename in place while code is running.
- Add the new column.
- Deploy code that writes both and reads the new one, falling back to the old.
- Backfill.
- Deploy code that only uses the new one.
- Drop the old column, in a later release.
Drop a column
- Deploy code that never reads or writes it. Confirm in production over some days.
- Drop it.
Never in one step. The old running instances will still be selecting it.
Add an index
Use the concurrent form where available.
sql
CREATE INDEX CONCURRENTLY idx_name ON table (col);It cannot run inside a transaction, takes longer, and can leave an invalid index if it fails. Check validity afterwards and drop and retry if needed.
Change a column type
Usually a table rewrite. Treat it as a rename:
- Add a new column of the new type.
- Dual-write.
- Backfill in batches.
- Switch reads.
- Drop the old column later.
Add a foreign key
Adding it validated locks both tables while it checks every row.
- Add the constraint
NOT VALID. It applies to new rows only, and takes a brief lock. VALIDATE CONSTRAINTseparately. This takes a weaker lock and can run for a while.
Delete a lot of rows
Never one statement.
sql
-- Repeat until zero rows affected, pausing between batches.DELETE FROM t WHERE id IN ( SELECT id FROM t WHERE <predicate> LIMIT 5000);Watch replication lag between batches, and stop if it grows.