r/SQL • u/ClickOk5811 • 5h ago
PostgreSQL PostgreSQL recipe: renaming a column without downtime (expand and contract)
A plain ALTER TABLE ... RENAME COLUMN is instant, but it breaks any running old version of the app during a rolling deploy. The usual workaround is to never have a moment where only one name exists. Here’s the sequence, with Postgres commands. Test it on a scratch database first.
1. Expand: add the new column
ALTER TABLE orders ADD COLUMN customer_ref text;
A nullable column with no default is a metadata-only change, so it’s fast.
2. Deploy the app writing both columns, still reading the old one.
The app sets customer_id_old and customer_ref on every insert and update. (A trigger can do this instead, but then the logic lives in the database. Decide which you prefer.)
3. Backfill in batches
UPDATE orders
SET customer_ref = customer_id_old
WHERE id BETWEEN 1 AND 10000
AND customer_ref IS DISTINCT FROM customer_id_old;
Repeat for the next id range. Small batches avoid long locks and big bursts of WAL and replication lag. Run VACUUM as needed afterwards, since each update leaves dead rows.
4. Verify
SELECT count(*)
FROM orders
WHERE customer_ref IS DISTINCT FROM customer_id_old;
IS DISTINCT FROM treats NULLs properly, so you expect 0.
5. Deploy the app reading the new column (still writing both).
6. Deploy the app that no longer writes the old column.
7. Contract: drop the old column in a separate, later migration
ALTER TABLE orders DROP COLUMN customer_id_old;
This is also quick, but it’s irreversible without a backup, so check first that nothing reads it: views, functions, reports, other services, replicas used for analytics.
If the new column needs a constraint or index:
- Use
CREATE INDEX CONCURRENTLYso you don’t block writes. - For
NOT NULL, adding aCHECK (customer_ref IS NOT NULL) NOT VALIDand thenVALIDATE CONSTRAINTavoids a long lock. Check the docs for your version, since behavior has changed across releases.
When I’d skip all this: a table with no other readers, where a short maintenance window is acceptable. Then a plain rename is simpler.
What do you do differently, for example triggers versus dual writes in the app?