← Back to Blog
Database Engineering Published: 2026-08-07

Zero-Downtime PostgreSQL Migrations: Adding Indexes & Constraints on 100M+ Row Tables Safely

Avoid blocking production traffic: battle-tested SQL strategies for adding indexes, foreign keys, and NOT NULL constraints on multi-gigabyte PostgreSQL tables.

Anas Rhimi
Anas Rhimi August 2026 • 8 min read

Zero-Downtime PostgreSQL Migrations: Adding Indexes & Constraints on 100M+ Row Tables Safely

Running a standard ALTER TABLE orders ADD COLUMN status VARCHAR NOT NULL DEFAULT 'pending'; on a table with 50 million rows takes an ACCESS EXCLUSIVE lock. This blocks all incoming read and write transactions, creating a cascading queue of blocked HTTP requests that will crash your web application in seconds.

Here are the non-blocking SQL patterns required to perform zero-downtime database migrations at scale.

Safe Non-Blocking Index Creation

-- BAD: Locks table for writes during the entire index build
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- GOOD: Builds index in background without locking table writes
SET lock_timeout = '2s';
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);

Frequently Asked Questions

Why does standard CREATE INDEX lock PostgreSQL tables?

A standard CREATE INDEX acquires a SHARE lock that blocks all incoming write queries. Using CREATE INDEX CONCURRENTLY avoids write locks entirely.

How do you add a NOT NULL column to a 50M row table safely?

Add the column with a default value without NOT NULL, backfill in batches, add a CHECK constraint with NOT VALID, validate it concurrently, and then enforce NOT NULL.

Subscribe to the Technical Newsletter

Get deep-dives into DevOps, Kubernetes, Linux performance, and self-hosted AI architecture.

Hire Me