Whetstone
0day streak

Schema Migrations

Changing a live schema without an outage.

11

Questions

4/1/6

Easy / Med / Hard

Your accuracy

A migration is a deploy where the data has to survive. The rule that makes it safe: never require a schema change and a code change to land at the same instant, because they cannot.

Expand and contract (also called parallel change) is the pattern. Suppose you are renaming a column:

1. Expand — add the new column, nullable, changing nothing else. Old code is unaffected. 2. Dual-write — deploy code that writes both columns and still reads the old one. 3. Backfill — copy existing rows in batches, throttled so you do not saturate the database. 4. Switch reads — deploy code that reads the new column. Now verify. 5. Contract — stop writing the old column, then drop it in a later deploy.

Every step is independently deployable and independently revertible, which is the entire point. A single migration that renames the column breaks every running instance of the old code the moment it lands.

Know which operations lock. Adding a nullable column is usually cheap. Adding a NOT NULL column with a default rewrote the whole table on older Postgres versions and is cheap on newer ones — the version matters. Creating an index locks writes unless you build it concurrently. Changing a column type generally rewrites the table. On a big table, any of these is an outage in the shape of a migration.

Backfills are jobs, not statements. A single UPDATE across ten million rows holds locks, bloats the write-ahead log, and blocks replication. Batch it, commit between batches, and make it resumable.

Every migration needs a reverse. Not always a literal down-migration — dropping a column cannot be undone — but a rehearsed answer to "we deployed this and it is wrong". Usually that means the destructive step is last and separate.