Skip to main content
pgschema picks migration strategies that minimize downtime and blocking when making schema changes to production PostgreSQL databases.

Index Operations

Adding Indexes

When adding new indexes, pgschema automatically uses CREATE INDEX CONCURRENTLY to avoid blocking table writes:
The pgschema:wait directive blocks the schema migration execution, polls the database to monitor progress, displays the progress to the user, and automatically continues when the operation completes. It tracks:
  • Completion status: Whether the index is valid and ready
  • Progress percentage: Based on pg_stat_progress_create_index

Modifying Indexes

When changing existing indexes, pgschema uses a safe “create new, drop old” pattern:
This approach ensures:
  • No downtime during index creation
  • Query performance is maintained throughout the migration
  • Rollback is possible until the old index is dropped

Constraint Operations

Supported constraint types:
  • Check constraints: Data validation rules
  • Foreign key constraints: Referential integrity with full options
  • NOT NULL constraints: Column nullability (via check constraint pattern)
For all constraint types below, pgschema executes the ADD ... NOT VALID and VALIDATE CONSTRAINT statements in separate transactions. This matters: the lock taken by ADD CONSTRAINT is released before validation starts, so the long table scan performed by VALIDATE runs under a weaker lock that does not block reads or writes.

CHECK Constraints

Constraints are added using PostgreSQL’s NOT VALID pattern:

Foreign Key Constraints

Foreign keys use the same NOT VALID pattern:

NOT NULL Constraints

On PostgreSQL 18+, pgschema uses the native invalid NOT NULL constraint support:
The constraint name matches what PostgreSQL generates for a NOT NULL column in CREATE TABLE, so the migrated table converges with a freshly created one. If the VALIDATE step never ran (an interrupted apply, or a NOT VALID constraint added by hand), the column already reads as NOT NULL in the catalog, but existing rows are unchecked. pgschema detects the unvalidated constraint on the next plan and emits just the VALIDATE CONSTRAINT step, so the migration can be finished by re-applying. On PostgreSQL 14-17, adding NOT NULL uses a check constraint based process:

Examples

Adding a Multi-Column Index

Schema change:
Generated migration:

Adding a Foreign Key Constraint

Schema change:
Generated migration:
For more examples, see the test cases in testdata/diff/online which demonstrate online DDL patterns for various PostgreSQL schema operations.