Learn about safely using the expand, migrate, and contract pattern to make database schema changes without downtime and data loss.
A common question I often hear is, “Should I make my application code changes before, after, or at the same time as my database schema changes?”
The reality is that neither our application nor database live in a bubble. In almost every case, you should never couple your database schema and application code changes together. While shipping them simultaneously might seem like a great idea, it can often only lead to pain for you and your users. There are five main reasons for this:
- Risk: By deploying changes to two critical systems at once, such as your database and application, you double the risk of something going wrong.
- Deployments: It’s impossible for application code and database schema changes to deploy together atomically. If they are ever dependent on each other going out simultaneously, the application will error briefly until the other catches up.
- Migration time: As data size grows, migrations can take longer. It can go from 30 seconds to a few hours to even more than a day! You don’t want the app deployment blocked for this.
- Blocking the development pipeline: If something goes wrong with the database schema change when coupled together, the deployment of the application is now blocked. A single change can stop the pipeline from going into production until it’s fixed.
- Best practices: Having them separate forces database best practices for ensuring backward compatible changes, which we will discuss in this blog post.
So how should you change your application code when it also requires changes in the database schema?
This blog post will answer this question and break down the steps you need to follow to ensure you are safely making changes to your database and ensuring no downtime or disruptions for your users. A word of advice: This process can feel complex the first time you do it, but after some practice, it gets easy and you’ll be able to move quickly and confidently.
Note: While PlanetScale can help make safe schema changes alongside the pattern, the pattern can apply to any relational database schema changes.
Some of the common types of database schema changes are:
- Adding a table or view
- Adding a column
- Changing an existing column, table, or view
- Removing an existing column, table, or view
Generally, adding a table, column, or view is low-risk and doesn’t require much, other than deploying the schema change before your application code that might use the change. You can read more in the schema change documentation about handling each type of change.
Things are riskier when changing or removing a column or table. This is where backward compatible changes are essential. The most commonly used pattern is expand, migrate, and contract. You might see this pattern under similar names, like parallel or backward compatible changes. I like the “expand, migrate, and contract” name because it visually describes what it is doing. Let’s break that down.
The expand, migrate, and contract pattern#
Backward compatible changes should be used for any operation that touches schema your production application is already using. This ensures that at any step of the process, you can rollback without data loss or significant disruptions to users. This greatly reduces the risk and allows you to move faster and with confidence.
For example, this applies when you are:
- Renaming an existing column or table
- Changing the data type of an existing column
- Splitting and other modifications to the data of an existing column or table
If you only add a column or table that does not affect the existing schema, you do not need to follow this pattern.
Here’s a helpful diagram to help you think about the pattern and where the changes are occurring through the steps:
Let’s break down the pattern.
Expand#
Step 1 – Expand the existing schema#
The first step in the pattern is to add to the schema. You will create a new column or table, depending on the change needed in the application.
As I describe in my previous blog post about safely making schema changes, you should consider making smaller, incremental changes to your database schema to ensure your changes are safe. Big changes are riskier.
In most cases, adding a new column will not affect your existing application if you make the column nullable and/or provide default values. If you don’t do this, when the application creates a new row, you could potentially cause a database error.
You can test locally or with a database branch alongside your application and then deploy the changes.
Note: If you are using PlanetScale branching, you can make the change in a development branch, open a deploy request, and deploy it to your production database, which will increase the safety of each step.
Step 2 – Expand the application code#
The second step in the pattern is to update the application code to write to both the old and new schema. Before this step, your application only wrote to the old column or table.
You want it to write to both the old and new schema because you want to make sure it can safely write to the new schema without error. If you plan on changing how the data is stored — e.g., if you want to store the user ID i