# 1. The problem and the plan [Next: 2. The expand migration →](02-the-expand-migration.md) ## The problem `customers` has a column called `email`. You want it called `email_address` — maybe because a second `contact_email` table is coming and the naming needs to be consistent, maybe because "email" collided with a reserved word in a tool you just adopted. The reason doesn't matter. What matters is that this table has rows in it, right now, in production, and something is reading and writing that column while you work. The naive fix is one migration: ```sql ALTER TABLE customers RENAME COLUMN email TO email_address; ``` That statement is correct and it is also a production outage. The instant it commits, every currently-running copy of your application — the ones you have not redeployed yet, because a rolling deploy takes minutes, not zero seconds — starts issuing SQL against a column that no longer exists. `INSERT INTO customers(name, email) VALUES (?, ?)` becomes a 500 on every single request, on every replica that hasn't restarted yet, until the rollout finishes. You have coupled a **schema change** to a **code deploy**, and the two of them do not happen atomically across a fleet. ## The plan: expand, migrate, contract Expand-contract (sometimes "parallel change") solves this by never letting the schema and the code disagree about what's safe. Instead of one migration and one deploy, it's four: 1. **Expand** — add the new column, alongside the old one. Nothing reads it yet. Nothing that's running has to change. 2. **Migrate writes** — deploy code that writes to *both* columns. Every row created or updated from this point on is consistent in both places. 3. **Migrate reads** — deploy code that reads from the new column instead of the old one. This is a *separate* deploy from step 2, and the gap between them matters more than it looks like it should — see [chapter 5](05-the-read-switch.md). 4. **Contract** — once every replica in the fleet is confirmed running the Stage 4 code from step 3, drop the old column. Nothing is reading or writing it anymore, so dropping it is safe. 1. EXPAND add email_address schema only, no deploy 2. MIGRATE WRITES write both columns code deploy 3. MIGRATE READS read email_address code deploy 4. CONTRACT drop email schema, then cleanup Same database, the whole time customers.email [always present until step 4] customers.email_address [present from step 1 onward, populated from step 2 onward] Two adjacent stages serve real traffic against this table at once during every rollout above. The diagram's bottom half is the fact the rest of this article keeps coming back to: at every point during a rolling deploy, two adjacent stages are running against the same table at the same time. Deploy 2's rollout has Stage 1 and Stage 2 replicas live together for however long the rollout takes; Deploy 3's rollout has Stage 2 and Stage 3 together; Deploy 4a's has Stage 3 and Stage 4 together. Each of those overlaps is a window where the "old" code and the "new" code both have to produce correct answers against a schema neither one fully owns. [Chapter 8](08-the-rolling-window-proof.md) is the test that checks every one of those six write/read combinations directly, and the article's live load-generator run reproduces the same overlaps under real HTTP traffic and real timing. ## What never changes The four deploys change exactly one thing about how the *database* is used. They change nothing about the *API*: ```java public record Customer(long id, String name, String email) { } ``` [`Customer.java`](https://ankurm.com/git.app/asmhatre/spring-boot-demo/src/branch/main/db-migrations-expand-contract/src/main/java/com/ankurm/expandcontract/customer/Customer.java) never mentions `email_address` — a caller of this API cannot tell which stage answered a given request just by looking at the response shape, and neither can the article's own load generator. That's deliberate: expand-contract is a technique for changing storage without changing the contract clients depend on. ## Going deeper - The companion module for this article is `db-migrations-expand-contract` in [`spring-boot-demo`](https://ankurm.com/git.app/asmhatre/spring-boot-demo) — every chapter from here on links to a real file or a real captured transcript in it. - Martin Fowler's [ParallelChange](https://martinfowler.com/bliki/ParallelChange.html) is the canonical name and description of this pattern outside a specific database or framework. [Next: 2. The expand migration →](02-the-expand-migration.md)