Database Migration Toolkit
Stop dreading database migrations. This toolkit provides production-tested frameworks for schema changes, data migrations, zero-downtime deployments, and rollback procedures across PostgreSQL, MySQL, and MongoDB. Every script follows the expand-contract pattern so you can migrate safely even under heavy traffic — no maintenance windows required.
Key Features
- Schema migration framework with numbered, idempotent migration files and a version tracking table
- Zero-downtime migration patterns using expand-contract (add column, backfill, swap, drop) for live systems
- Data migration scripts for bulk transformations with checkpointing, so interrupted jobs resume where they left off
-
Rollback procedures for every migration type — tested
DOWNscripts, not afterthoughts -
Cross-engine support covering PostgreSQL (
psql), MySQL (mysql), and MongoDB (mongosh) with engine-specific idioms - Pre-flight validation checks that verify disk space, lock contention, replication lag, and FK constraints before executing
-
Large table migration helpers using
pg_repack,pt-online-schema-change, and batchedUPDATEstrategies - CI/CD integration templates for GitHub Actions and GitLab CI migration pipelines
Quick Start
unzip database-migration-toolkit.zip
cd database-migration-toolkit/
# Initialize the migration tracking table
psql -h localhost -U admin -d myapp -f src/init_migration_table.sql
# Run a migration
psql -h localhost -U admin -d myapp -f migrations/001_add_orders_table.sql
# Verify migration status
psql -h localhost -U admin -d myapp \
-c "SELECT * FROM schema_migrations ORDER BY applied_at DESC LIMIT 5;"
The migration tracking table:
CREATE TABLE IF NOT EXISTS schema_migrations (
id SERIAL PRIMARY KEY,
version VARCHAR(14) NOT NULL UNIQUE, -- e.g., '20260315_0001'
name VARCHAR(255) NOT NULL,
applied_at TIMESTAMPTZ DEFAULT now(),
execution_ms INTEGER,
checksum VARCHAR(64), -- SHA-256 of migration file
rolled_back BOOLEAN DEFAULT FALSE
);
Architecture / How It Works
database-migration-toolkit/
├── src/
│ ├── init_migration_table.sql # Bootstrap the tracking table
│ ├── migrate.sh # Shell runner: apply pending migrations
│ ├── rollback.sh # Shell runner: revert last N migrations
│ ├── preflight_check.sh # Pre-migration safety checks
│ └── helpers/
│ ├── backfill_batched.sql # Batched UPDATE for large tables
│ ├── pg_repack_wrapper.sh # Zero-downtime table rewrite
│ └── checksum.sh # Verify migration file integrity
├── migrations/ # Example numbered migrations
│ ├── 001_add_orders_table.sql
│ ├── 001_add_orders_table.DOWN.sql
│ ├── 002_add_customer_email_index.sql
│ └── 002_add_customer_email_index.DOWN.sql
├── examples/
│ ├── expand_contract_example.sql
│ ├── mysql_pt_osc_example.sh
│ └── mongodb_migration.js
└── config.example.yaml
Each migration has a paired .DOWN.sql rollback file. The shell runner applies them in order and records checksums to detect tampering.
Usage Examples
Expand-contract pattern for renaming a column with zero downtime:
-- Step 1: EXPAND — add new column alongside old one
ALTER TABLE customers ADD COLUMN full_name VARCHAR(200);
-- Step 2: BACKFILL — copy data in batches
UPDATE customers SET full_name = name
WHERE full_name IS NULL
AND id BETWEEN 1 AND 100000; -- repeat in batches
-- Step 3: SWAP — point application code to new column, then:
ALTER TABLE customers ALTER COLUMN full_name SET NOT NULL;
-- Step 4: CONTRACT — drop old column after all readers migrated
ALTER TABLE customers DROP COLUMN name;
Batched backfill with checkpoint (handles interruptions):
-- Create checkpoint table
CREATE TABLE IF NOT EXISTS migration_checkpoint (
migration_id VARCHAR(50) PRIMARY KEY,
last_processed_id BIGINT DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Batched update with throttling
DO $$
DECLARE
batch_size INT := 10000;
start_id BIGINT;
rows_updated INT;
BEGIN
SELECT COALESCE(last_processed_id, 0) INTO start_id
FROM migration_checkpoint WHERE migration_id = 'backfill_fullname';
LOOP
UPDATE customers
SET full_name = name
WHERE id > start_id AND id <= start_id + batch_size
AND full_name IS NULL;
GET DIAGNOSTICS rows_updated = ROW_COUNT;
EXIT WHEN rows_updated = 0;
start_id := start_id + batch_size;
INSERT INTO migration_checkpoint
VALUES ('backfill_fullname', start_id, now())
ON CONFLICT (migration_id)
DO UPDATE SET last_processed_id = start_id, updated_at = now();
COMMIT;
PERFORM pg_sleep(0.1); -- throttle to reduce replication lag
END LOOP;
END $$;
MySQL large-table migration with pt-online-schema-change:
pt-online-schema-change \
--alter "ADD COLUMN full_name VARCHAR(200) AFTER name" \
--host 127.0.0.1 --user admin --ask-pass \
--chunk-size 5000 \
--max-lag 2s \
--critical-load "Threads_running=50" \
D=myapp,t=customers \
--execute
Configuration
# config.example.yaml
migration:
engine: postgresql # postgresql | mysql | mongodb
migrations_dir: ./migrations
tracking_table: schema_migrations
checksum_verify: true # fail if migration file changed after apply
safety:
require_down_file: true # every UP must have a matching DOWN
max_lock_wait: "5s" # abort if table lock exceeds this
preflight_checks:
- disk_space_gb: 10 # minimum free disk
- replication_lag_ms: 500 # max replica lag before proceeding
- active_locks: 5 # max concurrent locks allowed
batching:
default_batch_size: 10000
throttle_ms: 100 # pause between batches
checkpoint: true # enable resume-from-checkpoint
Best Practices
- Never mix schema and data changes in the same migration file. Schema DDL auto-commits in MySQL, making rollback impossible if the data step fails.
-
Test rollbacks in staging before every production deploy. An untested
DOWNscript is worse than no rollback at all. -
Use
SET lock_timeoutin PostgreSQL migrations to fail fast instead of blocking all queries behind a long-heldACCESS EXCLUSIVElock. -
Monitor replication lag during backfills. Throttle batch size or add
pg_sleep()between batches to keep replicas healthy. - Checksum your migration files. If someone edits a migration after it was applied, your environments will silently diverge.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Migration hangs indefinitely |
ALTER TABLE waiting for ACCESS EXCLUSIVE lock |
Add SET lock_timeout = '5s'; at top of migration |
| Rollback fails with FK violation | DOWN script drops table still referenced | Drop the FK constraint first, then the table |
| Checksum mismatch error | Migration file edited after being applied | If intentional, update checksum in schema_migrations; if not, investigate |
| Replication lag spikes during backfill | Batch size too large | Reduce batch_size to 5000 and increase throttle_ms to 200 |
This is 1 of 9 resources in the Database Admin Pro toolkit. Get the complete [Database Migration Toolkit] with all files, templates, and documentation for $39.
Or grab the entire Database Admin Pro bundle (9 products) for $109 — save 30%.
Top comments (0)