Schema Version Control Basics
Treat database schema changes as immutable, versioned artifacts. Ad-hoc DDL execution introduces state drift, breaks CI/CD pipelines, and eliminates auditability. This guide establishes a deterministic workflow for tracking state, enforcing backward compatibility, and guaranteeing safe rollbacks across distributed systems. Align your team on foundational Database Migration Fundamentals & Tool Selection before implementing tooling to ensure atomic execution and state tracking.
Phase 1: Repository Initialization & Baseline Capture
Initialize a dedicated migrations/ directory at the repository root. Commit a deterministic baseline snapshot of the production schema using a cryptographic hash or sequential version identifier. Store all subsequent DDL/DML as discrete, timestamped files. Manual production edits are strictly prohibited; all changes must route through version control.
Directory Structure & Baseline Command
mkdir -p migrations/{forward,rollback,config}
# Capture baseline schema hash (PostgreSQL example)
pg_dump -s -h prod-db-host -U admin -d app_db | sha256sum > migrations/config/baseline.sha256
Environment Context Differentiation
| Context | State Table Behavior | Access Control | Data Handling |
|---|---|---|---|
| Dev | Auto-reset on container teardown | Local admin | Synthetic seeds |
| Staging | Locked during PR validation | CI service account | Anonymized prod dump |
| Prod | Immutable audit ledger | Read-only CI, DBA override | Live traffic, read replicas |
Execution Path & Compatibility Window
- Forward: Commit baseline, initialize
schema_migrationstable, lock version000. - Rollback:
DROP TABLE schema_migrations;(only if baseline is corrupted before first deployment). - Compatibility Window: N/A (baseline establishment). Subsequent migrations must target
version >= 001.
Phase 2: Migration Authoring & Versioning
Write forward-only, backward-compatible operations. Decouple schema evolution from application releases using the expand/contract pattern. Never drop columns or tables in a single deployment cycle. Implement Idempotent Script Design to guarantee zero side effects on re-execution.
Naming Convention & Transaction Boundary
-- V20231115143000__add_user_status_column.sql
BEGIN;
ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(32) DEFAULT 'active';
-- Application code must handle NULL/DEFAULT gracefully during rollout
COMMIT;
Environment Context Differentiation
- Dev: Run migrations on every container start. Fail fast on syntax errors.
- Staging: Validate against production-scale data volumes. Enforce PR merge gates.
- Prod: Apply only after staging sign-off. Scripts are immutable post-merge.
Execution Path & Compatibility Window
- Forward: Execute
ALTER TABLE ... ADD COLUMN. Deploy app versionv2.1that reads/writes the new column. - Rollback:
ALTER TABLE users DROP COLUMN IF EXISTS status;(requires appv2.0to be deployed first). - Compatibility Window: Minimum 2 release cycles. New columns must remain nullable until all consumers are updated.
Phase 3: Pre-Deployment Validation & Safety Checks
Validate migration integrity in isolated staging environments before promotion. Run automated linting to detect syntax errors, missing indexes, or lock-prone operations. Apply Environment Parity Strategies to mirror production configuration, connection pooling, and replication topology.
Dry-Run & Session Safety Configuration
# Flyway/Liquibase equivalent dry-run
flyway info -dryRunOutput=preview.sql
# Apply session-level safety limits (PostgreSQL)
psql -c "SET lock_timeout = '500ms'; SET statement_timeout = '30s';"
Validation Matrix
| Check | Tool/Command | Failure Action |
|---|---|---|
| Syntax & Lock Analysis | pg_lint, EXPLAIN (ANALYZE, BUFFERS) |
Abort pipeline |
| State Table Sync | SELECT version FROM schema_migrations ORDER BY applied_at DESC LIMIT 1; |
Halt if mismatch |
| Replication Lag | SELECT pg_last_xact_replay_timestamp(); |
Delay execution if >1s |
Execution Path & Compatibility Window
- Forward: Generate
preview.sql, review execution plan, approve PR. - Rollback: Discard generated artifacts. Revert to last known good migration state.
- Compatibility Window: Validation window must not exceed 4 hours. Staging state must match prod schema version exactly.
Phase 4: Execution & Zero-Downtime Rollout
Deploy during low-traffic windows using blue-green or canary routing. Wrap DDL in explicit transaction boundaries for ACID-compliant engines. For non-transactional systems (e.g., MySQL DDL, Cassandra), implement chunked execution with manual checkpointing.
Chunked Execution & Monitoring Thresholds
-- Transactional (PostgreSQL/SQL Server)
BEGIN;
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
COMMIT;
-- Non-Transactional Chunked Backfill (MySQL)
WHILE (SELECT COUNT(*) FROM users WHERE status IS NULL) > 0 DO
UPDATE users SET status = 'active' WHERE status IS NULL LIMIT 1000;
DO SLEEP(0.5); -- Prevent I/O saturation
END WHILE;
Environment Context Differentiation
- Dev: Execute synchronously. Monitor local query logs.
- Staging: Simulate traffic spikes during execution. Validate lock wait thresholds.
- Prod: Execute via CI/CD runner with elevated privileges. Real-time observability dashboards active.
Execution Path & Compatibility Window
- Forward: Run migration, monitor
pg_stat_activity/SHOW ENGINE INNODB STATUS. Verify health checks pass. - Rollback: Trigger inverse DDL immediately if lock duration >500ms or error rate spikes. Execute compensating scripts.
- Compatibility Window: Execution must complete within maintenance window (typically <15 mins). Forward path assumes app
v2.1is already routing to new schema.
Phase 5: Post-Deployment Verification & Rollback Readiness
Confirm schema state matches the expected version hash. Run integration smoke tests against the updated schema to validate query compatibility and index utilization. Archive rollback scripts alongside forward migrations. Maintain strict retention policies for audit compliance.
State Verification & Smoke Test
# Verify applied version
flyway info
# Run integration smoke test
curl -f -s http://api-staging/health | jq '.db_schema_version == "20231115143000"'
Environment Context Differentiation
- Dev: Auto-cleanup after test suite completion.
- Staging: Retain artifacts for 30 days. Document lock metrics and execution duration.
- Prod: Archive migration ledger permanently. Schedule post-deployment review within 24 hours.
Execution Path & Compatibility Window
- Forward: Mark deployment successful. Enable feature flags dependent on new schema.
- Rollback: Delete applied version record from
schema_migrationsonly after inverse scripts complete. Restore from pre-migration snapshot if data corruption occurs. - Compatibility Window: Maintain backward-compatible app version
v2.0in artifact registry for 14 days. Coordinate with Git Branching Strategies for Schema Version Control to prune stale branches after compatibility expires.