Stackbook Logo
data-consistencyestablished · medium operational burden

Expand-Contract Migration

Also known as: parallel-change, expand-and-contract, backward-compatible-migration

Intent

Change a schema or interface in compatible phases so old and new application versions work against it simultaneously, enabling zero-downtime releases.

Problem

Renaming a column or changing an API in one step breaks whichever application version is not yet updated. During rolling deploys, rollbacks or multiple consumers, a breaking change forces downtime or a risky big-bang coordination.

Forces

  • •Old and new code run side by side during deploys
  • •Rollback must stay possible after each step
  • •Large tables cannot be rewritten instantly
  • •Consumers update on their own schedules

Solution

Overview

Expand-contract, also called parallel change, makes incompatible changes compatible by never doing the breaking part and the dependent part together. Think of it as building a second bridge before demolishing the first.

How it works

Take renaming the column fullname to display_name.

1 EXPAND    add column display_name (nullable)
2 WRITE     app writes both columns
3 BACKFILL  copy fullname into display_name in batches
4 READ      app reads display_name
5 CONTRACT  stop writing fullname, then drop column

Each numbered step is its own deploy. After step 2, a rollback to the earlier app version is safe because the old column is still maintained. After step 4, rollback to the previous release still works because both columns hold data.

ALTER TABLE users ADD COLUMN display_name text;

The same shape applies to APIs: add the new field, support both, migrate clients, then remove the old one after usage drops to zero.

Pitfalls

  • Skipping the verification between read-switch and contract. Measure that the old column is unused first.
  • Backfilling in one giant transaction that locks the table. Batch with throttling.
  • Adding NOT NULL or defaults in a way that rewrites a large table.
  • Forgetting to remove dual-write code, leaving permanent clutter.
  • Contracting while an old mobile app version is still in the field.

Key takeaways

  • Never ship a breaking change and its dependents together.
  • Expand, migrate, then contract, each as a separate release.
  • Keep rollback possible at every step.
  • Backfill in throttled batches.
  • Track the contract step so it actually happens.

✓ When to Use

  • •Zero-downtime schema changes in relational databases
  • •Public API or event schema evolution with external consumers
  • •Any rolling or canary deploy touching shared state

✗ When Not to Use

  • •Throwaway or single-user systems where downtime is fine
  • •Brand-new tables with no existing readers
  • •Trivial additive changes that need no contract phase

Pros

  • +Zero downtime and safe rollback at every step
  • +Decouples schema deploys from code deploys
  • +Works for APIs, events and databases alike

Cons

  • −Takes several releases for one logical change
  • −Temporary duplication and dual-write complexity
  • −Contract step is easy to forget

Cost Profile

Infrastructure

Low - temporary extra storage

Operational

Medium - backfills and phased releases

Cognitive

Medium - sequencing discipline

Failure Modes

  • Dual writes diverge and the new column holds stale data

  • Contract step runs before a lagging consumer migrates

  • Backfill locks a hot table and causes an outage

Real-World Examples

Alternatives

  • database-migration

Related Patterns

  • database-migration
  • rolling-update
  • api-versioning
  • progressive-delivery

Competency Domains

data statereliability ops