Knowledge Guide
HomeSystem DesignScalable Systems (Advanced Topics)

hard DDL Locking & Online Schema Change

Why a one-line ALTER can take your database down

A schema change runs inside a transaction and, like any statement, must take a lock on the object it touches. What decides whether that ALTER is invisible or catastrophic is which lock level it needs and how long it holds it. In PostgreSQL most ALTER TABLE forms grab an ACCESS EXCLUSIVE lock — the strongest lock there is, conflicting with every other lock including plain reads. If the change is metadata-only it holds that lock for microseconds and nobody notices; if it rewrites the table it holds it for the whole rewrite and the table is unusable the entire time. Same syntax, wildly different blast radius. The general principle is engine-independent: know the lock level and the duration before you run DDL in production.

The cheap changes: metadata-only

Since PostgreSQL 11, ADD COLUMN with no default or a constant (non-volatile) default is a pure catalog change. The engine records the default as a single "missing" attribute value attached to the column definition; existing rows are not touched, and reads synthesize the value on the fly. Adding a column to a billion-row table this way finishes in milliseconds.

Before PG 11 this was a famous foot-gun: ADD COLUMN … DEFAULT 0 rewrote every row. Many teams still carry the old habit of adding the column and back-filling the default separately — unnecessary now for a constant default, still necessary for a volatile one.

The expensive changes: full rewrites and index builds

Two categories hold a strong lock for a long time:

ADD COLUMN with a constant default is a metadata-only catalog change; with a volatile default it rewrites every row under an exclusive lock
ADD COLUMN with a constant default is a metadata-only catalog change; with a volatile default it rewrites every row under an exclusive lock

CREATE INDEX CONCURRENTLY — build without blocking writes

CREATE INDEX CONCURRENTLY takes only a SHARE UPDATE EXCLUSIVE lock, which does not conflict with reads or writes — the table stays fully writable while the index builds. It buys that by working harder:

So the trade is explicit: CONCURRENTLY is slower and more fragile in exchange for not blocking writes.

The queued-lock pitfall — the one that surprises people

Even a fast metadata-only ALTER must still acquire ACCESS EXCLUSIVE, and that acquisition is where production incidents come from. PostgreSQL grants locks roughly first-come-first-served. If a long query is holding a weak lock, your ALTER cannot get ACCESS EXCLUSIVE, so it waits at the head of the lock queue. The trap: every request that arrives after it — even ordinary reads and writes that would never have conflicted with the running query — now queues behind the pending ALTER. One 30-second report plus one blocked ALTER freezes the entire table.

A long-running SELECT blocks a pending ALTER TABLE; every later query queues behind the ALTER, freezing the whole table
A long-running SELECT blocks a pending ALTER TABLE; every later query queues behind the ALTER, freezing the whole table

Traced: what actually happens, second by second

tEventState of table orders
0sAnalytics SELECT starts (holds ACCESS SHARE)Reads + writes flowing normally
2sALTER TABLE … ADD COLUMN … DEFAULT gen_random_uuid() requests ACCESS EXCLUSIVEALTER blocked; enters queue head
2.1sApp INSERT (ROW EXCLUSIVE) arrivesBlocked — queued behind the ALTER, though it never conflicted with the SELECT
2s–30sAll new reads/writes pile upTable effectively down
30sSELECT finishes → ALTER runs → but it rewrites every row…Still locked for the whole rewrite

The fix is a short lock timeout plus a retry loop, so the DDL fails fast instead of holding the queue open:

SET lock_timeout = '2s';
-- if the ALTER can't get its lock within 2s it errors out (55P03),
-- releasing the queue; a wrapper retries with backoff.
ALTER TABLE orders ADD COLUMN status int NOT NULL DEFAULT 0;

The expand/contract (parallel-change) pattern

The professional way to make a breaking schema change with zero downtime is to never break compatibility in a single step. Split it into additive, individually-safe deploys where old and new code both work against the intermediate schema:

  1. Expand. Add the new structure additively — a new nullable column, or a new table — using only cheap/online operations. Old code ignores it.
  2. Migrate. Deploy code that writes to both old and new, then back-fill the new column in batches (not one giant UPDATE). Now both shapes are correct.
  3. Contract. Once all readers use the new shape, drop the old column/constraint. Each step is independently deployable and reversible.

To add a genuinely NOT NULL column with a volatile value without a rewrite: expand (add nullable) → migrate (back-fill in batches) → add a NOT VALID check constraint, then VALIDATE CONSTRAINT (which takes only SHARE UPDATE EXCLUSIVE, not ACCESS EXCLUSIVE) — contracting the window to nearly nothing.

Pitfalls

Judgment — online/concurrent vs offline

The real decision is can you afford a maintenance window, and how big is the table?

Offline (plain DDL / maintenance window)Online (CONCURRENTLY, expand/contract, gh-ost, pt-online-schema-change)
SpeedFast — one pass, no snapshot jugglingSlow — extra scans, waits, batched back-fill
AvailabilityDowntime / table lockedNo downtime, table stays writable
Complexity & riskSimple, one stepComplex — multi-deploy, invalid-index cleanup, triggers/shadow tables

Choose offline when a window is acceptable and the table is small enough that the rewrite fits the window (dev/internal systems, batch platforms, a 10k-row config table) — the speed and simplicity are worth more than avoiding a 5-second blip. Choose online/concurrent for large, always-on OLTP tables where any lock is an outage; you accept the slower, more fragile, multi-step process because downtime is not on the menu.

Named alternative for MySQL: where PostgreSQL uses CONCURRENTLY + expand/contract, MySQL/InnoDB has native ALGORITHM=INPLACE online DDL, but many changes still take a metadata lock or fall back to a copy; teams reach for gh-ost (GitHub) or pt-online-schema-change (Percona), which build a shadow table, sync it via triggers or the binlog, and atomically rename. Trade-off vs native concurrent DDL: shadow-table tools give finer throttling and are pausable/resumable, at the cost of doubling storage during the migration and heavy write amplification.

Takeaways


Synthesized from the PostgreSQL documentation (Explicit Locking, ALTER TABLE, CREATE INDEX), the "Zero-Downtime Migrations" literature (Braintree, GitHub gh-ost, Percona pt-online-schema-change), and Kleppmann, Designing Data-Intensive Applications (schema evolution). Re-authored and diagrams hand-authored (SVG) for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on DDL Locking & Online Schema Change? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **DDL Locking & Online Schema Change** (System Design) and want to truly understand it. Explain DDL Locking & Online Schema Change from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **DDL Locking & Online Schema Change** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **DDL Locking & Online Schema Change** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **DDL Locking & Online Schema Change** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes