Migration Patterns Skill
Project Autopilot - Safe migration strategies
Copyright (c) 2026 Jeremy McSpadden jeremy@fluxlabs.net
Comprehensive patterns for safe, reliable migrations.
Migration Principles
The Migration Pyramid
┌─────────┐
╱ ╲
╱ VERIFY ╲
╱ ╲
├─────────────────┤
╱ ╲
╱ EXECUTE ╲
╱ ╲
├─────────────────────────┤
╱ ╲
╱ PREPARE ╲
╱ ╲
└───────────────────────────────┘
Prepare (Foundation)
- Full backups
- Rollback procedures
- Impact analysis
Execute (Implementation)
- Incremental changes
- Continuous testing
- Monitoring
Verify (Validation)
- Integrity checks
- Performance benchmarks
- Functionality testing
Database Migration Patterns
Expand-Contract Pattern
Safe schema evolution in production.
Phase 1: Expand
-- Add new column (nullable)
ALTER TABLE users ADD COLUMN email_verified BOOLEAN;
-- Add new index concurrently
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
Phase 2: Migrate Data
-- Backfill existing data
UPDATE users SET email_verified = false WHERE email_verified IS NULL;
Phase 3: Contract
-- Add constraint (after all code updated)
ALTER TABLE users ALTER COLUMN email_verified SET NOT NULL;
-- Drop old column (after deprecation period)
ALTER TABLE users DROP COLUMN legacy_field;
Parallel Change Pattern
Run old and new simultaneously.
// 1. Write to both
async function updateUser(id: string, data: UserData) {
// Write to new schema
await newSchema.update(id, data);
// Write to old schema (for rollback)
await oldSchema.update(id, data);
}
// 2. Compare results
async function getUser(id: string) {
const [oldResult, newResult] = await Promise.all([
oldSchema.get(id),
newSchema.get(id),
]);
if (!deepEqual(oldResult, newResult)) {
logger.warn('Schema mismatch', { id, old: oldResult, new: newResult });
}
return newResult;
}
// 3. Switch reads to new
// 4. Stop writing to old
// 5. Remove old schema
Zero-Downtime Migration
-- Create new table
CREATE TABLE users_v2 (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create trigger to sync writes
CREATE OR REPLACE FUNCTION sync_users_v2()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO users_v2 (id, email, created_at)
VALUES (NEW.id, NEW.email, NEW.created_at)
ON CONFLICT (id) DO UPDATE SET
email = EXCLUDED.email;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER users_sync_trigger
AFTER INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION sync_users_v2();
-- Backfill historical data
INSERT INTO users_v2 (id, email, created_at)
SELECT id, email, created_at FROM users
ON CONFLICT DO NOTHING;
-- Switch application to new table
-- Remove trigger and old table
Framework Migration Patterns
Strangler Fig Pattern
Gradually replace legacy system.
// Facade that routes to old or new
class AuthFacade {
async authenticate(credentials: Credentials): Promise<User> {
// Check if user should use new system
if (this.shouldUseNew(credentials)) {
return this.newAuth.authenticate(credentials);
}
return this.legacyAuth.authenticate(credentials);
}
private shouldUseNew(credentials: Credentials): boolean {
// Gradual rollout based on email domain
const domain = credentials.email.split('@')[1];
return this.enabledDomains.includes(domain);
}
}
Branch by Abstraction
Create abstraction, implement new, switch.
// 1. Create interface
interface PaymentProcessor {
charge(amount: number, source: string): Promise<ChargeResult>;
refund(chargeId: string): Promise<RefundResult>;
}
// 2. Implement for legacy
class LegacyPaymentProcessor implements PaymentProcessor {
async charge(amount: number, source: string) {
return this.legacySDK.processPayment(amount, source);
}
}
// 3. Implement for new
class NewPaymentProcessor implements PaymentProcessor {
async charge(amount: number, source: string) {
return this.newSDK.createCharge({ amount, source });
}
}
// 4. Use factory to switch
class PaymentProcessorFactory {
create(): PaymentProcessor {
if (config.useNewPayments) {
return new NewPaymentProcessor();
}
return new LegacyPaymentProcessor();
}
}
Incremental TypeScript Migration
// tsconfig.json - Start lenient
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false,
"noImplicitAny": false
}
}
// Phase 1: Rename .js to .ts (no changes)
// Phase 2: Add types to new files
// Phase 3: Enable noImplicitAny per-directory
// Phase 4: Enable strict per-directory
// Phase 5: Full strict mode
API Migration Patterns
Versioned API
// Version in URL
app.get('/api/v1/users', v1UserController);
app.get('/api/v2/users', v2UserController);
// Version in header
app.get('/api/users', (req, res, next) => {
const version = req.header('API-Version') || '1';
if (version === '2') {
return v2UserController(req, res, next);
}
return v1UserController(req, res, next);
});
Deprecation Pattern
// Add deprecation warnings
app.get('/api/v1/users', (req, res, next) => {
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
// Log deprecation usage
logger.info('Deprecated API called', {
endpoint: '/api/v1/users',
client: req.headers['user-agent'],
});
return v1UserController(req, res, next);
});
Consumer-Driven Contract
// Maintain contracts for consumers
const consumerContracts = {
'mobile-app': {
endpoints: ['/api/users', '/api/orders'],
fields: ['id', 'name', 'email'],
},
'admin-dashboard': {
endpoints: ['/api/users', '/api/analytics'],
fields: ['id', 'name', 'email', 'role', 'lastLogin'],
},
};
// Verify contracts before release
function verifyContracts(newApi: OpenAPISpec): ContractResult[] {
return Object.entries(consumerContracts).map(([consumer, contract]) => ({
consumer,
result: validateContract(newApi, contract),
}));
}
Rollback Strategies
Instant Rollback (Feature Flags)
// Disable feature instantly
await featureFlags.set('new-checkout', false);
// All traffic immediately uses old implementation
Blue-Green Rollback
# Switch traffic back to blue
kubectl patch service myapp -p '{"spec":{"selector":{"version":"blue"}}}'
Database Rollback
-- Point-in-time recovery
pg_restore -d mydb --target-time="2026-01-29 14:30:00" backup.dump
-- Or restore from backup
pg_restore -d mydb backup_before_migration.dump
Git Rollback
# Revert specific commit
git revert abc123
# Revert merge commit
git revert -m 1 merge_commit_hash
# Reset to previous state (caution!)
git reset --hard HEAD~1
Migration Checklist
Before Migration
- Impact analysis complete
- All affected systems identified
- Rollback plan documented
- Rollback plan tested
- Full backup taken
- Backup verified
- Team notified
- Monitoring in place
- Maintenance window scheduled (if needed)
During Migration
- Execute steps in order
- Verify each step before proceeding
- Monitor error rates
- Monitor performance
- Be ready to rollback
After Migration
- Full functionality test
- Performance benchmarks
- Data integrity checks
- Remove deprecated code/data
- Update documentation
- Notify stakeholders
- Post-mortem if issues occurred