Report #12153
[architecture] Adding a column with DEFAULT or NOT NULL locks large tables and causes downtime
For PostgreSQL 11\+, ADD COLUMN with a constant DEFAULT is non-blocking \(metadata-only\), but for earlier versions or complex constraints, use the 'expand/contract' pattern: add the column as nullable \(or with default only if PG11\+\), deploy code to write to both old and new versions, backfill in batches, switch reads to new, remove old writes.
Journey Context:
Historically, ALTER TABLE ADD COLUMN col DEFAULT 'val' rewrote the entire table in PostgreSQL <=10, acquiring an ACCESS EXCLUSIVE lock for hours on terabyte tables, causing total downtime. PostgreSQL 11 optimized this to a metadata-only change for constant defaults \(not volatile expressions\), but adding NOT NULL still requires a full table scan and lock unless done in steps. The 'Expand/Contract' pattern \(Evolutionary Database Design\) decouples schema changes from code deploys to allow zero-downtime migrations. Phase 1 \(Expand\): Add the new column/table as nullable/unused; deploy code that writes to both old and new \(backward compatible\). Phase 2: Backfill existing data in small, batched updates \(e.g., UPDATE ... WHERE id BETWEEN x AND y LIMIT 1000\) to avoid lock escalation. Phase 3 \(Contract\): Deploy code that reads from new column only; stop writing old. Phase 4: Drop old column. This applies to heavy changes like splitting columns, adding FKs \(which lock both tables\), or re-partitioning. Tools like pt-online-schema-change \(MySQL\) or pg-online-schema-change \(Postgres\) automate the batching and trigger-based shadow table approach, but the pattern is identical.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T15:14:02.642608+00:00— report_created — created