Database Migrations Best Practices for Growing Teams
Learn safe database migration practices for schema changes, rollbacks, zero-downtime releases, large tables, indexes, backfills, and team review.
Database migrations change the ground under the app
A database migration may add a column, change an index, split a table, backfill data, or enforce a new constraint. These changes can affect performance, availability, deployments, and rollback plans. As teams grow, migrations become more dangerous because more services, jobs, dashboards, and integrations depend on the same data.
Safe migration practice is about reducing surprise. The application and database should move through compatible states so users do not experience errors while code and schema are changing. This matters especially for global products where there may be no quiet hour.
Use expand and contract changes
A common zero-downtime pattern is expand and contract. First, add new schema elements in a backward-compatible way. Then deploy application code that writes or reads both old and new shapes as needed. After data is backfilled and old code is gone, remove the old schema in a later release.
This is slower than one big migration, but it is much safer. It allows rollback during intermediate steps and prevents old application instances from crashing when they see a schema they no longer understand.
- Separate schema changes, code changes, and data backfills when risk is high.
- Create large indexes with production-safe options where supported.
- Test migrations against realistic data volume.
- Keep rollback plans honest and documented.
Large tables need special care
Adding constraints, changing column types, rewriting rows, or creating indexes on large tables can lock resources and slow production traffic. Developers should know how their database handles each operation. A migration that is instant in development may take hours in production.
Backfills should usually be batched, monitored, and restartable. They should avoid overwhelming replicas, queues, and downstream consumers. If a backfill changes business-critical data, include validation queries and a way to pause or roll back safely.
Review migrations like production code
Migration review should include lock behavior, runtime estimate, index impact, rollback plan, compatibility with old code, and monitoring. Teams should avoid merging mysterious migration files nobody understands. Generated migrations are helpful, but generated does not mean safe.
Ownership matters. If several services use the same database, coordinate schema changes with all affected teams. The database is a shared contract, and changing it without communication creates outages that look like application bugs.
Make migration outcomes visible
After deployment, check whether the migration completed, whether queries changed plans, whether errors rose, and whether backfills are keeping pace. A migration is not done when the file is merged. It is done when production data is safely in the intended shape and dependent systems are healthy.
Write migrations for future operators
Someone may need to understand the migration months later during an incident or audit. Clear names, comments for unusual operations, linked tickets, and documented validation queries make that easier. Database migrations become part of the historical record of how the product changed, so they should be understandable after the original author has moved on.