Database migrations are more than just code changes—they’re fundamental shifts in your data architecture, with a lot of risk. Years and years of hard-won experience has taught me that migrations should live in their own Pull Requests. Tools like Cased can even automatically detect and help split these changes into their own PRs, but whether you’re using automation or doing it manually, here’s why this practice transforms your development process.
The Hidden Complexity of Combined Changes
When you bundle a migration with application code changes, you’re actually managing two distinct types of risk. The migration might have unexpected performance implications on your production database, while the application changes could introduce behavioral bugs. Back when I worked at GitHub, for example, we were rigorous about this practice— every migration, no matter how small, got its own PR. By combining them, you’re making it harder to identify and address either type of problem effectively.
The Benefits of Migration-Only PRs
Splitting your migrations into dedicated PRs offers several powerful advantages:
Focused Review Process
Database changes require a different kind of scrutiny than application code. When reviewers look at a migration-only PR, they can focus entirely on data integrity, performance implications, and rollback strategies. They’re not distracted by business logic or UI changes. This focus leads to higher quality reviews and better safety guarantees.
Controlled Deployment Timing
Migrations often need to run during off-peak hours or require careful coordination with other system changes. With a separate PR, you can deploy the migration when database load is low, verify its completion, and then safely roll out the corresponding application changes later. This staging dramatically reduces your operational risk.
Clearer Rollback Paths
If something goes wrong, having your migration separate makes it much easier to understand your options. Should you roll back just the application code? Just the migration? Both? When these changes are bundled together, these decisions become more complex and time-sensitive, exactly when you need clarity the most.
The Implementation Pattern
Here’s how to implement this approach effectively:
-
Create a migration PR that includes:
- The migration itself
- Any necessary data backfills
- Detailed testing instructions
- Performance impact analysis
-
Once that’s merged and deployed, follow up with your application PR containing:
- The actual code changes
- Updated models/schemas
- New features or behavior
- Tests that rely on the migrated structure
The “But It Takes Longer” Myth
Some teams resist this pattern because it seems like it will slow down development. In my experience leading engineering teams, the opposite is true. While you do need two PRs instead of one, the review process for each becomes faster and more focused. More importantly, you dramatically reduce the risk of deployment issues that could cost you hours or days of debugging time.
And Cased further kills the myth of it taking longer—because we do it all for you. When you open a PR that includes database changes, Cased will automatically detect the migrations and split it into a migration and application PR.
Better Debugging, Better Recovery
When problems do occur, having separate PRs makes it much easier to understand what went wrong. You can quickly determine if the issue stems from the migration itself or from how your application is using the new structure. This clarity is invaluable during incidents when every minute counts.
A Cultural Shift Worth Making
Adopting this pattern requires a slight shift in how teams think about changes, but the benefits far outweigh the initial adjustment period. Many major tech companies have made this their standard practice precisely because it scales so well with complexity. Your future self (and your on-call team) will thank you for taking the time to separate these concerns—and your databases will be all the safer for it.