Report #72067
[architecture] Database schema migrations causing downtime or table locking when altering large PostgreSQL/MySQL tables in production \(e.g., adding indexes, altering column types, adding constraints\)
Use the Expand/Contract pattern \(parallel change\) for zero-downtime schema evolution: 1\) Expand: Add new column/table \(dual-write to old and new\), 2\) Migrate: Backfill data in batches using background jobs, 3\) Switch: Move reads to new structure, 4\) Contract: Remove old column/table. For unavoidable DDL changes \(adding indexes\), use online schema change tools: gh-ost or pt-online-schema-change for MySQL; pg\_repack or native online DDL \(PostgreSQL 11\+ for most ops, avoiding heavy rewrites\) for PostgreSQL.
Journey Context:
Direct ALTER TABLE on large production databases is dangerous: it often acquires exclusive locks \(ACCESS EXCLUSIVE in PostgreSQL, or ALGORITHM=COPY in old MySQL\), blocking reads and writes for seconds to hours depending on table size, causing cascading outages. Even 'fast' operations like adding a column with a default value in old PostgreSQL versions required a full table rewrite. The Expand/Contract pattern treats schema changes like blue/green deployments for data. Instead of modifying in place, you add the new structure alongside the old, dual-write to both to maintain consistency, backfill historical data asynchronously in small batches \(to avoid long transactions/locks\), switch the application to read from the new structure \(with a feature flag\), and finally drop the old structure. This allows rollback at each stage and zero-downtime. For operations that must touch the existing physical structure \(adding indexes, removing columns\), use online schema change tools: gh-ost \(GitHub's triggerless MySQL tool using binlog\) or pt-online-schema-change \(Percona Toolkit, using triggers\) for MySQL; pg\_repack \(rebuilds tables online without locks\) or pg-online-schema-change for PostgreSQL. Modern PostgreSQL \(11\+\) supports non-blocking CREATE INDEX CONCURRENTLY and adding nullable columns without rewriting, but heavy operations \(ALTER COLUMN TYPE\) still lock. Tradeoffs: Expand/Contract requires application changes for dual-write logic, increases temporary storage, and complicates deployment. It is overkill for small tables or non-blocking operations \(e.g., adding an index CONCURRENTLY in Postgres\).
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T03:32:52.115160+00:00— report_created — created