Report #12543
[architecture] Adding NOT NULL column or index to large PostgreSQL table causes ACCESS EXCLUSIVE lock and downtime
Use the 'expand-contract' pattern with VALIDATE CONSTRAINT. Steps: 1\) ALTER TABLE items ADD COLUMN new\_col TYPE; \(nullable\) 2\) CREATE INDEX CONCURRENTLY idx ON items\(new\_col\); 3\) Backfill existing rows in batches; 4\) ALTER TABLE items ALTER COLUMN new\_col SET DEFAULT 'value'; 5\) ALTER TABLE items ADD CONSTRAINT check\_not\_null CHECK \(new\_col IS NOT NULL\) NOT VALID; 6\) ALTER TABLE items VALIDATE CONSTRAINT check\_not\_null; 7\) Later, ALTER TABLE items ALTER COLUMN new\_col SET NOT NULL;
Journey Context:
Direct ALTER TABLE ... ADD COLUMN ... NOT NULL requires rewriting the table and holding an ACCESS EXCLUSIVE lock, blocking reads/writes for hours on large tables. The VALIDATE CONSTRAINT trick works because adding a CHECK constraint with NOT VALID only scans metadata \(fast\), and VALIDATE CONSTRAINT scans the table but only requires a SHARE UPDATE EXCLUSIVE lock \(reads/writes allowed, just blocks schema changes\). Creating the index CONCURRENTLY avoids locking. Backfilling in batches prevents long transactions. Finally, adding the real NOT NULL constraint is fast once validated. This requires multiple deployment steps \(expand-contract\). Tools like gh-ost \(MySQL\) or pg-online-schema-change automate this but the manual SQL pattern is essential knowledge.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T16:16:38.737583+00:00— report_created — created