Change Management Basics
8 examples to get you started with Postgres change management - 5 basic and 3 intermediate.
Search across all documentation pages
8 examples to get you started with Postgres change management - 5 basic and 3 intermediate.
# Migrations run through your standard tool with review
flyway -url="$DATABASE_URL" info
# or
liquibase --changelog-file=db/changelog.xml statusdocs/ or runbooks/.Assign every migration a tier before merge.
| Tier | Examples | Approval | Window |
|---|---|---|---|
| Low | CREATE INDEX CONCURRENTLY, add nullable column | PR review | Anytime |
| Medium | NOT NULL backfill, new FK with NOT VALID | Team lead + DBA | Business hours |
| High | ALTER TYPE, table rewrite, partition attach | DBA + CAB | Maintenance window |
| Sev-worthy | pg_upgrade, major storage migration | Exec + DR sign-off | Dedicated window |
-- risk: medium in header comments.Related: Change Management Best Practices - enforcement checklist
Prevent migrations from waiting forever on locks.
-- Set at start of migration session (Flyway callback or Liquibase pre-sql)
SET lock_timeout = '5s';
SET statement_timeout = '30min';
SET idle_in_transaction_session_timeout = '5min';
ALTER TABLE orders ADD COLUMN IF NOT EXISTS priority smallint;Safe first step of expand/contract.
-- Expand: add column (instant in PostgreSQL 11+ for nullable default-null)
ALTER TABLE customers ADD COLUMN IF NOT EXISTS tier text;
-- App deploy writes tier on new rows only (feature flag)
-- Backfill in batches
UPDATE customers SET tier = 'standard' WHERE tier IS NULL AND id BETWEEN 1 AND 10000;NOT NULL without default in one step on large tables.UPDATE with primary key ranges to avoid long locks.Only after code no longer reads the column.
-- Verify zero reads in pg_stat_user_tables (approximate) and app metrics
SELECT relname, seq_scan, idx_scan FROM pg_stat_user_tables WHERE relname = 'customers';
-- Contract
ALTER TABLE customers DROP COLUMN IF EXISTS legacy_code;Every production DDL links evidence.
## CHG-2044 Add orders.priority
- Risk tier: Medium
- Migration: V2044__orders_priority.sql
- Blast radius: orders table (42M rows), checkout service
- Rollback: DROP COLUMN priority (after app revert)
- Lock test: passed on staging 2026-07-08
- On-call aware: yesEXPLAIN for any new index.Related: Maintenance Windows - when to schedule
Add enforcement without blocking writes on existing rows.
ALTER TABLE line_items
ADD CONSTRAINT line_items_order_id_fkey
FOREIGN KEY (order_id) REFERENCES orders(id)
NOT VALID;
-- Validate later in smaller window (still takes ShareUpdateExclusiveLock)
ALTER TABLE line_items VALIDATE CONSTRAINT line_items_order_id_fkey;NOT VALID adds metadata only; existing bad rows remain until validate.VALIDATE CONSTRAINT.Concurrent build for OLTP tables.
CREATE INDEX CONCURRENTLY IF NOT EXISTS orders_customer_id_idx
ON orders (customer_id);
-- Flyway: disable transaction wrapper for this script# Verify index valid
psql -c "SELECT indisvalid FROM pg_index WHERE indexrelid = 'orders_customer_id_idx'::regclass;"REINDEX CONCURRENTLY (see troubleshooting).CREATE INDEX CONCURRENTLY in a transaction block.Treat pg_upgrade as a product launch, not a patch.
## Pre-flight
- [ ] Logical backup + PITR checkpoint verified
- [ ] Extension compatibility matrix (pgvector, PostGIS)
- [ ] Rollback = keep old cluster stopped, DNS revert
- [ ] App connection string tested on PG 18.4 stagingpg_upgrade --check on clone first.Related: Before/After: Major Version Upgrade - narrative reference
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.
Reviewed by Chris St. John·Last updated Jul 16, 2026