Skip to main content
Databases

Zero-downtime database migrations: schema changes without the 3am rollback

Use expand–migrate–contract to ship database changes across mixed application versions without blocking writes or betting production on one irreversible deploy.

Thien Nguyen
By Thien Nguyen
Updated May 5, 2026 · 2 min read

The unsafe migration is the one that changes the schema and application contract in the same deploy. During a rolling release, old and new code run together; your database has to speak both versions for a while.

Short answer: expand first, migrate data and reads/writes while both shapes work, verify, then contract later. This “parallel change” approach is deliberately boring because rollback stays possible. Martin Fowler's parallel-change pattern captures the essential sequence.

Rename full_name without breaking old pods

PhaseDatabaseApplication
ExpandAdd nullable display_nameContinue reading full_name
Dual writeKeep both columnsWrite both fields
BackfillFill old rows in bounded batchesRead new field with old fallback
Cut overValidate parityRead only display_name
ContractDrop old column in a later releaseRemove compatibility code
UPDATE users
SET display_name = full_name
WHERE id > :last_id AND id <= :next_id
  AND display_name IS NULL;

Batching makes the backfill observable and restartable. One enormous UPDATE is how a harmless column rename turns into lock contention, replica lag, or a page at 3am.

Changes that need special suspicion

  • New NOT NULL columns: add nullable, backfill, enforce the constraint later.
  • New indexes: use the database's online/concurrent index capability where available; do not assume DDL is non-blocking.
  • Type changes: add a new column and transform gradually when the conversion can fail or changes semantics.
  • Destructive drops: treat them as a separate deploy after dashboards show no old-code traffic.

“The migration ran in staging” does not answer whether it blocks a hot production table. Test against production-like volume and inspect the database-specific lock behaviour.

Zero downtime is a compatibility problem first and a SQL problem second. Give yourself a period where either app version can succeed, and the scary rollback becomes a routine rollback.

Cover photo by panumas nikhomkhai on Pexels.

References

Primary documentation and specifications checked when this article was last updated.

DatabasesDeploymentsReliability

Related articles

All articles