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.

change a database column without downtimeadd the new field before removing the old onedual write during a migrationrename a column safely in productionold and new schema at the same timechange the database without breaking the old codeparallel change migrationexpand and contrcat migration

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.

You might have meant

database migrationbackward compatible deploymentzero downtime deployschema migrationrollback