Zero-downtime Migrations
Apply schema changes — index builds, column splits, safe renames — without taking the database offline. Real locks, real progress views, real workload running throughout.
Every production database eventually needs a schema change while traffic keeps flowing. This course teaches the tools and patterns that make that possible: CREATE INDEX CONCURRENTLY to build indexes without blocking writes, DROP INDEX CONCURRENTLY to remove them safely, and the expand/contract pattern to split or rename a column across a rolling deploy. We work on a real two-million-row shop database with a live workload script running throughout, so every claim about locks and blocked writers is observable, not theoretical. The course closes with a bad monolithic migration that we pull apart into three safe steps, each guarded by lock_timeout and statement_timeout, each carrying a rollback note.
What you'll build
- Build an index without blocking writes using CREATE INDEX CONCURRENTLY
- Spot and clean up an INVALID index left by an interrupted concurrent build
- Drop an index safely with DROP INDEX CONCURRENTLY
- Split a column with the expand/contract pattern: add, backfill, validate, drop
- Explain why RENAME COLUMN is unsafe during a rolling deploy
- Guard every migration step with lock_timeout and statement_timeout
- Break a bad monolithic migration into safe independent deploy steps
Contents
- A database that can't afford downtime
- Start it
- Connect with psql
- The users table — with a deliberate flaw
- The orders table — two million rows
- Create the tables
- Seed the users, repeatably
- Seed two million orders
- Load the data
- Fresh statistics and a version check
- Simulate a live workload
- A uniform way to apply migrations
- The blocking index build
- Where to see the queue building up
- Apply the blocking index
- The non-blocking alternative
- Watch the concurrent build in real time
- Apply the concurrent index
- The INVALID index trap
- Create and cancel a concurrent build to get an INVALID index
- Drop the invalid index safely
- Clean up the invalid index
- Why CONCURRENTLY cannot run inside a transaction block
- Phase one: expand — add the new columns
- Add first_name and last_name
- Monitor the backfill progress
- Confirm all rows are unfilled before the backfill starts
- Phase two: backfill in small batches
- Run the backfill
- Phase three: validate — lock in the constraint
- Add NOT NULL to the new columns
- Phase four: contract — drop the old column
- Remove full_name
- Why RENAME COLUMN is not the answer
- The bad monolithic migration
- Safe step one: add the column
- Safe step two: create the index concurrently
- Safe step three: expand for the rename
- Apply all three safe steps under workload
- The final state of orders
- Clean up the stand