Expand-and-contract migration
Change a live schema safely: add the new shape, write both, backfill, switch reads, then remove the old shape in a later release.
What it is
An expand-and-contract migration changes a live schema in compatible stages. First expand it by adding the new column or table. Deploy code that writes both old and new shapes, backfill existing rows, and switch reads only after the new data is complete. In a later release, contract the schema by removing the old field and the dual write. Danilo Sato wrote the same idea up on martinfowler.com as ParallelChange, which is why you also see the phases called expand, migrate, contract.
Reach for it whenever old and new application versions can overlap, which includes rolling deploys, cached browser clients, background workers, and ordinary rollback windows. The extra releases buy you a period where either application version can use the database safely.
Gotcha: doing every phase in one deployment defeats the pattern. A failed backfill needs to be restartable, dual writes need monitoring, and the old column cannot be dropped until reads, writes, queued jobs, and rollback plans no longer depend on it. To code that is still running, a rename looks like a drop plus an add, which is why it needs these stages rather than one ALTER TABLE RENAME COLUMN.
Ask AI for it
Rewrite this PostgreSQL schema change as an expand-and-contract migration across separate releases. First add the new nullable column and create its index with CREATE INDEX CONCURRENTLY outside a transaction. Next deploy idempotent dual writes, backfill rows in primary-key batches with progress checkpoints, and compare old and new values before switching reads behind a feature flag. Only after one full rollback window should a final migration remove the old column and dual-write code. Set lock_timeout for DDL and document the deploy and rollback order for every phase.