Book a 30-min callBook a call

Zero-downtime database migrations: expand and contract, backfills and locks

How to change a PostgreSQL schema without downtime: expand and contract, safe recipes for columns and indexes, batched backfills, lock_timeout and rollback.

Two table cards side by side, an old column and a new column joined by a double arrow, small batch blocks moving between them, and a padlock with a clock above.
Written by
BYD Tech
Published
Topic
Deep dives
Reading time
14 min read

Questions people ask

Does adding a column lock the table in PostgreSQL?

Briefly. It takes an ACCESS EXCLUSIVE lock, but with no default or a constant default it only changes the catalog, so it is fast since PostgreSQL 11. The risk is waiting for that lock behind a long transaction, so set lock_timeout and retry.

Can CREATE INDEX CONCURRENTLY run inside a transaction?

No. PostgreSQL rejects it inside a transaction block, so a migration tool that wraps every migration in a transaction needs its opt-out for that step. If the build fails it leaves an invalid index, which you drop before trying again.

What is the expand and contract pattern?

A way to make a breaking change in backward-compatible steps: add the new structure beside the old, move reads and writes over with dual writes and a backfill, then remove the old structure. Danilo Sato describes it as ParallelChange on Martin Fowler’s site.

How big should backfill batches be?

Small enough that each transaction commits in well under a second; start with a few thousand rows and adjust. Walk the primary key in ranges, pause between batches, and slow down when replica lag or query latency rises.

Should migrations have down scripts?

They help in development, but in production a schema rollback often loses data. With expand and contract you roll back by redeploying the previous code, leave the expanded schema in place, and delay destructive steps until the new path has proven itself.

Building something like this?