| name | managing-database-migrations |
| description | AI agent implements safe database migrations with zero-downtime strategies, rollback plans, and expand-contract patterns. Use when creating migrations, deploying schema changes, or implementing zero-downtime database updates. |
| category | databases |
| triggers | ["migration","schema change","database migration","zero downtime","rollback","prisma migrate","flyway"] |
Managing Database Migrations
Purpose
Implement safe, reversible database migrations for production environments:
- Apply expand-contract pattern for zero-downtime changes
- Design rollback strategies for every migration
- Handle large table alterations safely
- Integrate migrations with CI/CD pipelines
- Test migrations before production deployment
Quick Start
npx prisma migrate dev --name add_user_roles
npx prisma migrate deploy
npm run typeorm migration:generate -- -n AddUserRoles
npm run typeorm migration:run
ALTER TABLE "users" ADD COLUMN "role" VARCHAR(50) DEFAULT 'user';
CREATE INDEX "idx_users_role" ON "users"("role");
Features
| Feature | Strategy | When to Use |
|---|
| Add Column | Add nullable, then backfill, then NOT NULL | Always safe |
| Remove Column | Stop using, deploy, then remove | Expand-contract |
| Rename Column | Add new, copy data, remove old | Zero-downtime required |
| Change Type | Add new column, migrate, swap | Data transformation |
| Add Index | CREATE CONCURRENTLY | Large tables (>1M rows) |
| Drop Table | Rename first, drop after verification | Reversible delete |
Common Patterns
Expand-Contract Pattern
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
UPDATE users SET full_name = first_name || ' ' || last_name;
ALTER TABLE users DROP COLUMN first_name;
ALTER TABLE users DROP COLUMN last_name;
Safe Column Addition
ALTER TABLE orders ADD COLUMN shipping_method VARCHAR(50);
UPDATE orders SET shipping_method = 'standard'
WHERE id IN (SELECT id FROM orders WHERE shipping_method IS NULL LIMIT 10000);
ALTER TABLE orders ALTER COLUMN shipping_method SET NOT NULL;
ALTER TABLE orders ALTER COLUMN shipping_method SET DEFAULT 'standard';
Safe Column Removal
ALTER TABLE users ALTER COLUMN legacy_field DROP DEFAULT;
ALTER TABLE users ALTER COLUMN legacy_field DROP NOT NULL;
ALTER TABLE users DROP COLUMN legacy_field;
Safe Index Creation (Large Tables)
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status);
CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status);
Prisma Migration Workflow
npx prisma migrate dev --name add_user_roles
npx prisma migrate diff \
--from-schema-datamodel prisma/schema.prisma \
--to-schema-datamodel prisma/schema.prisma.new \
--script
npx prisma migrate deploy
npx prisma migrate reset
// schema.prisma - Example with relations
model User {
id String @id @default(uuid())
email String @unique
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
enum Role {
USER
ADMIN
}
TypeORM Migration
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserRoles1703936400000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "users" ADD COLUMN "role" VARCHAR(50) DEFAULT 'user'
`);
await queryRunner.query(`
CREATE INDEX "idx_users_role" ON "users"("role")
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "idx_users_role"`);
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "role"`);
}
}
Data Migration Pattern
export class MigrateUserNames1703936500000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const batchSize = 1000;
let offset = 0;
while (true) {
const result = await queryRunner.query(`
UPDATE users
SET full_name = first_name || ' ' || last_name
WHERE full_name IS NULL
LIMIT ${batchSize}
`);
if (result.affectedRows === 0) break;
offset += batchSize;
await new Promise(r => setTimeout(r, 100));
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
console.log('Data migration rollback: manual intervention required');
}
}
Use Cases
- Adding new features requiring schema changes
- Refactoring database structure safely
- Splitting or merging tables
- Changing column data types
- Large-scale data migrations
Best Practices
| Do | Avoid |
|---|
| Test migrations on production-like data | Testing only on empty databases |
| Use expand-contract for breaking changes | Direct column renames in production |
| Create CONCURRENTLY for large table indexes | Blocking index creation on live tables |
| Batch large data updates | Updating millions of rows in one transaction |
| Include rollback in every migration | Forward-only migrations without escape |
| Run migrations in CI before deploy | Manual migration execution |
| Version control all migrations | Modifying applied migrations |
| Separate schema and data migrations | Mixing DDL and large DML in one migration |
Migration Checklist
Pre-Migration:
[ ] Migration tested on staging with production data volume
[ ] Rollback script written and tested
[ ] Estimated execution time documented
[ ] Backup verified
During Migration:
[ ] Monitor database locks and connections
[ ] Check application error rates
[ ] Verify migration progress
Post-Migration:
[ ] Verify data integrity
[ ] Check application functionality
[ ] Monitor performance metrics
[ ] Document completion
CI/CD Integration
- name: Run Migrations
run: |
# Wait for healthy database
until pg_isready -h $DB_HOST; do sleep 1; done
timeout 600 npx prisma migrate deploy
npx prisma migrate status
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Related Skills
See also these related skill documents:
- designing-database-schemas - Schema design principles
- managing-databases - DBA operations and maintenance
- optimizing-databases - Performance tuning