Database Migration | Planning & Rollback Guide
Execute database changes safely with proper planning, testing, and rollback strategies. GitScrum tracks migration tasks and coordinates deployment safely.
9 min read
Database migrations require careful planning and execution. GitScrum helps track migration tasks and coordinate deployments safely.
Migration Planning
Migration Task Structure
DATABASE MIGRATION STORY:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DB-123: Add user_preferences Table β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β PURPOSE: β
β Store user notification and display preferences β
β Required for: USER-456 (Notification settings feature) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β MIGRATION SCRIPT: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β CREATE TABLE user_preferences ( ββ
β β id BIGSERIAL PRIMARY KEY, ββ
β β user_id BIGINT NOT NULL REFERENCES users(id), ββ
β β preference_key VARCHAR(100) NOT NULL, ββ
β β preference_value JSONB NOT NULL DEFAULT '{}', ββ
β β created_at TIMESTAMP DEFAULT NOW(), ββ
β β updated_at TIMESTAMP DEFAULT NOW(), ββ
β β UNIQUE(user_id, preference_key) ββ
β β ); ββ
β β ββ
β β CREATE INDEX idx_user_prefs_user ON ββ
β β user_preferences(user_id); ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β ROLLBACK SCRIPT: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β DROP TABLE IF EXISTS user_preferences; ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β CHECKLIST: β
β β Migration script reviewed β
β β Rollback script tested β
β β Run on staging successfully β
β β Performance impact assessed β
β β No table locks on large tables β
β β Deployment window scheduled β
β β Monitoring in place β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Risk Assessment
MIGRATION RISK LEVELS:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β LOW RISK (Green light): β
β β’ Adding new table β
β β’ Adding nullable column β
β β’ Adding index (if table small) β
β β Can run anytime β
β β
β MEDIUM RISK (Careful): β
β β’ Adding index to large table β
β β’ Modifying column type (compatible) β
β β’ Adding NOT NULL with default β
β β Run during low traffic β
β β Monitor performance β
β β
β HIGH RISK (Extra caution): β
β β’ Modifying column type (incompatible) β
β β’ Removing column β
β β’ Removing table β
β β’ Large data migrations β
β β Maintenance window required β
β β Team standby for issues β
β β Detailed rollback plan β
β β
β CRITICAL RISK (Full preparation): β
β β’ Schema changes on huge tables (100M+ rows) β
β β’ Foreign key changes β
β β’ Primary key modifications β
β β Extended maintenance window β
β β Full backup before β
β β Executive approval β
β β War room during execution β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Safe Migration Patterns
Backward-Compatible Changes
SAFE MIGRATION PATTERNS:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β PATTERN 1: EXPAND-CONTRACT β
β β
β GOAL: Rename column "name" to "full_name" β
β β
β β RISKY (one step): β
β ALTER TABLE users RENAME name TO full_name; β
β β Breaks old code immediately β
β β
β β
SAFE (expand-contract): β
β β
β Step 1 (Expand): Add new column β
β ALTER TABLE users ADD COLUMN full_name VARCHAR(255); β
β β
β Step 2: Dual-write in code β
β Write to both name and full_name β
β β
β Step 3: Backfill data β
β UPDATE users SET full_name = name WHERE full_name IS NULL;β
β β
β Step 4: Switch reads β
β Read from full_name β
β β
β Step 5 (Contract): Remove old column (later release) β
β ALTER TABLE users DROP COLUMN name; β
β β
β Each step is reversible and non-breaking β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Adding NOT NULL Columns
SAFE NOT NULL COLUMN ADDITION:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β GOAL: Add required "status" column to users β
β β
β β RISKY: β
β ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL; β
β β Fails if table has existing rows β
β β
β β
SAFE APPROACH: β
β β
β Step 1: Add nullable column with default β
β ALTER TABLE users ADD COLUMN status VARCHAR(20) β
β DEFAULT 'active'; β
β β
β Step 2: Backfill existing rows (in batches) β
β UPDATE users SET status = 'active' β
β WHERE status IS NULL β
β AND id BETWEEN 1 AND 10000; β
β β
β Step 3: Add NOT NULL constraint β
β ALTER TABLE users ALTER COLUMN status SET NOT NULL; β
β β
β Step 4: Optional - remove default β
β ALTER TABLE users ALTER COLUMN status DROP DEFAULT; β
β β
β WHY BATCHES: β
β Large UPDATE locks table β
β Batches of 10k-50k rows minimize lock time β
β Run during low-traffic periods β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Large Table Migrations
Batched Operations
HANDLING LARGE TABLES:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β PROBLEM: β
β Table: events (500M rows) β
β Need to: Update format of event_data column β
β β
β β BAD: One giant UPDATE β
β UPDATE events SET event_data = transform(event_data); β
β β Locks table for hours β
β β Transaction log explodes β
β β Application times out β
β β
β β
GOOD: Batched updates β
β β
β APPROACH: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β -- Run in loop, 10k rows at a time ββ
β β UPDATE events ββ
β β SET event_data = transform(event_data) ββ
β β WHERE id IN ( ββ
β β SELECT id FROM events ββ
β β WHERE needs_migration = true ββ
β β LIMIT 10000 ββ
β β ); ββ
β β ββ
β β -- Sleep between batches ββ
β β SELECT pg_sleep(0.5); ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β MONITORING: β
β β’ Rows processed: 150,000 / 500,000,000 β
β β’ Estimated time: 8 hours β
β β’ Database load: Normal β
β β’ Errors: 0 β
β β
β RUN AS: Background job, not deployment β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Zero-Downtime Index Creation
CREATING INDEXES SAFELY:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β PROBLEM: β
β Need index on large table (100M rows) β
β CREATE INDEX locks table during creation β
β β
β β BAD: β
β CREATE INDEX idx_users_email ON users(email); β
β β Locks table for minutes to hours β
β β
β β
GOOD (PostgreSQL): β
β CREATE INDEX CONCURRENTLY idx_users_email ON users(email);β
β β Builds in background β
β β No lock on table β
β β Slightly slower, but safe β
β β
β β
GOOD (MySQL 5.6+): β
β ALTER TABLE users ADD INDEX idx_email(email), β
β ALGORITHM=INPLACE, LOCK=NONE; β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β CAUTIONS: β
β β
β β’ CONCURRENTLY doesn't work in transactions β
β β’ If it fails, index is invalid - must be dropped β
β β’ Monitor disk space (builds index alongside) β
β β’ May take longer than regular CREATE INDEX β
β β
β GITSCRUM TASK: β
β Labels: database, low-risk β
β Note: Using CONCURRENTLY, no downtime expected β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Rollback Planning
Every Migration Needs Rollback
ROLLBACK REQUIREMENTS:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β RULE: NO MIGRATION WITHOUT ROLLBACK β
β β
β MIGRATION FILE STRUCTURE: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β migrations/ ββ
β β βββ 2024_01_15_120000_add_preferences_table.sql ββ
β β βββ up.sql (forward migration) ββ
β β βββ down.sql (rollback) ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β EXAMPLE: β
β β
β up.sql: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β CREATE TABLE user_preferences (...); ββ
β β CREATE INDEX idx_prefs_user ON user_preferences(...); ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β down.sql: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β DROP INDEX IF EXISTS idx_prefs_user; ββ
β β DROP TABLE IF EXISTS user_preferences; ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β TESTING ROLLBACK: β
β 1. Run migration β
β 2. Verify success β
β 3. Run rollback β
β 4. Verify clean state β
β 5. Run migration again β
β 6. Must work cleanly β
β β
β If rollback is impossible (data loss), document it β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Deployment Coordination
Migration Deployment Flow
MIGRATION DEPLOYMENT PROCESS:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β PRE-DEPLOYMENT: β
β β
β β Migration tested on staging β
β β Rollback tested on staging β
β β Performance impact measured β
β β Deployment window scheduled β
β β Team notified β
β β Backup confirmed β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β DEPLOYMENT ORDER: β
β β
β 1. BACKUP β
β Snapshot database before migration β
β β
β 2. MIGRATE β
β Run migration on production β
β β
β 3. VERIFY β
β Confirm migration successful β
β Check table structure β
β Verify data if applicable β
β β
β 4. DEPLOY CODE β
β Deploy application that uses new schema β
β β
β 5. MONITOR β
β Watch for errors β
β Check query performance β
β Verify application behavior β
β β
β 6. MARK COMPLETE β
β Update GitScrum task β
β Document any issues β
β β
β IF PROBLEMS: β
β β Roll back application β
β β Roll back migration β
β β Investigate in staging β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ