English for Database Schema Migrations
Master the vocabulary for discussing migration scripts, rollback plans, and zero-downtime schema changes in English.
Schema migrations sit at a genuinely risky intersection of code and production data, so vague language here has real consequences — “we’re updating the database” says nothing about whether the operation locks a table, whether it’s reversible, or whether it needs to run alongside old application code during a rollout. Precise vocabulary matters most in the design review before a migration ships, not after something has already gone wrong.
Key Vocabulary
Migration A versioned, scripted change to a database schema — such as adding a column or creating an index — designed to be applied and tracked incrementally alongside application code changes. Example: “This migration adds a new column with a default value, but on a table this large, that default backfill could take longer than our deployment window allows.”
Rollback (migration rollback) The reverse operation that undoes a migration’s changes, ideally scripted alongside the forward migration so a bad deploy can be reverted cleanly. Example: “This migration doesn’t have a corresponding rollback script — if we need to revert after deploying, we’d have to write one under pressure during an incident.”
Backward-compatible migration A schema change designed so that both the old and new versions of application code can operate correctly against it during a rolling deployment. Example: “We’re making this a backward-compatible migration by adding the new column as nullable first, so old code that doesn’t know about it still works during the rollout.”
Zero-downtime migration A migration strategy, often broken into multiple sequential steps across separate deploys, designed so the database never needs to be taken offline or locked in a way that blocks application traffic. Example: “A zero-downtime migration for this rename means we add the new column, backfill it, deploy code that writes to both, then drop the old column in a later release — not a single atomic rename.”
Table lock
A restriction that prevents other operations from reading or writing to a table (or a specific row range) while a migration is running, which can cause visible application slowdowns or timeouts.
Example: “Adding this index without the CONCURRENTLY option will hold a table lock for the duration of the build, which would block writes on a table this actively used.”
Backfill The process of populating a new column or table with data for existing rows, often run separately from the schema change itself to avoid locking the table for an extended period. Example: “We’re running the backfill in small batches over several hours rather than in a single transaction, specifically to avoid a long-held lock on this high-traffic table.”
Idempotent migration A migration script designed to be safely run multiple times without causing errors or duplicating changes, useful when a migration might be retried after a partial failure. Example: “We made this migration idempotent by checking whether the column already exists before trying to add it, so a retried deploy doesn’t fail on a duplicate column error.”
Expand-contract pattern A migration strategy for breaking a risky schema change into safe, incremental steps — expanding the schema to support both old and new shapes, migrating usage, then contracting to remove the old shape. Example: “We’re using the expand-contract pattern for this column rename: add the new column, dual-write to both, backfill, switch reads to the new column, then finally drop the old one.”
Common Phrases
In migration review:
- “This migration doesn’t have a rollback script — can we add one before this merges, even if we hope never to need it?”
- “Adding this index will hold a table lock for the full build time — should we use a concurrent index build instead, given how much write traffic this table sees?”
- “This isn’t backward-compatible with the currently deployed application version — during a rolling deploy, some instances would fail against the new schema.”
In standups:
- “Yesterday I split this schema change into an expand-contract sequence across three deploys; today I’m running the backfill in production in small batches.”
- “I’m blocked on a migration that needs to run against a table with several hundred million rows — I need to confirm the lock duration is acceptable before we schedule it.”
- “I finished writing the rollback script for last week’s migration, since we didn’t have one and got lucky that we didn’t need it during the deploy.”
In incident writeups:
- “The migration held a table lock for twelve minutes, during which write requests to this table queued up and eventually timed out.”
- “We didn’t have a rollback script ready, so reverting the bad migration required writing and testing one live during the incident, which extended the outage.”
- “The new column was added as non-nullable without a default, which caused the migration itself to fail against existing rows rather than the deploy failing gracefully.”
Phrases to Avoid
Saying “we’re updating the database” vaguely before a migration ships. Say instead: “this migration adds a nullable column with a backfill running separately, and holds no table lock beyond the initial DDL statement” — the specific mechanics are exactly what a reviewer needs to assess risk.
Saying “it should be fine” without checking lock behavior at scale. Say instead: “let’s confirm the lock duration on a copy of production-sized data before running this against the real table” — assumptions about migration safety that aren’t tested against realistic data volume are a common source of production incidents.
Saying “just rename the column” for a live schema. Say instead: “let’s use the expand-contract pattern here — add the new column, dual-write, backfill, then drop the old one later” — a single atomic rename against a live schema is rarely safe if any code is still deployed against the old name.
Quick Reference
| Term | How to use it |
|---|---|
| migration | ”This migration adds a new column with a default value.” |
| rollback | ”Every migration needs a corresponding rollback script.” |
| backward-compatible | ”Adding the column as nullable keeps this backward-compatible during rollout.” |
| table lock | ”A concurrent index build avoids holding a table lock.” |
| backfill | ”We’re backfilling in small batches to avoid a long lock.” |
| expand-contract pattern | ”We’re using expand-contract to safely rename this column.” |
Key Takeaways
- Always specify a migration’s lock behavior and rollback plan in review, not just what it changes structurally.
- Use the expand-contract pattern by default for any rename or type change on a live table, rather than a single atomic operation.
- Test migration timing against production-scale data before assuming a migration “should be fine” on a small table.
- Make migrations idempotent wherever practical, since retried deploys after a partial failure are common enough to plan for.
- Treat backward compatibility during a rolling deploy as a hard requirement, not an afterthought — old and new application code may run against the schema simultaneously.