← Back to search results

Zero-Downtime Schema Changes with Expand-Contract

A migration that renames a column and a deploy that starts using the new name can't both land in the same release without a brief window where the old application version is running against a schema it doesn't understand, which during a rolling deploy is guaranteed to happen for at least a few requests. Expand-contract solves this by splitting what looks like one change into three deploys. First, expand: add the new column alongside the old one, and start dual-writing both from the application (old code paths still only read the old column, so nothing breaks). Second, backfill the new column for existing rows, and once backfilled, switch reads to the new column while still dual-writing. Third, contract: once every instance of the old application version is confirmed gone, stop writing to the old column and drop it. This turns one risky migration into three boring ones, each independently safe to roll back, at the cost of more deploys and some temporary complexity in the write path. The same pattern applies beyond renames: changing a column's type, splitting a table, or changing a foreign key's target all decompose the same way — never make the new schema and the new code assumption land in the same atomic step.

Related documents

Rotating a database password or API key is simple in isolation. Doing it without an outage across every process holding the old credential in memory is the actual hard part.

Teams that route all three through one flag system tend to end up with flags nobody's sure are safe to delete. Each has a different lifecycle and deserves different tooling.

Team size and deploy friction are better predictors of when to split a service than technical elegance. Splitting too early adds distributed-systems cost before the org is big enough to need it.