almessadi.
Back to Index

Expand and Contract Is How You Change Schemas Without Breaking Running Code_

Zero-downtime migrations usually need staging: expand the schema, backfill, switch the code, and only then remove the old shape.

PublishedDecember 5, 2024
Reading Time9 min read

The dangerous migration is not the one that changes the schema. It is the one that assumes every running app instance, job worker, and background consumer will switch expectations at exactly the same instant.

That assumption is almost never safe in production.

What Expand and Contract Means

The pattern works because it accepts overlap between the old world and the new world:

  1. expand the schema
  2. keep old code working
  3. backfill or dual-write
  4. switch reads and writes
  5. remove the legacy shape later

For example, moving from full_name to first_name and last_name usually starts with expansion:

ALTER TABLE users ADD COLUMN first_name TEXT;
ALTER TABLE users ADD COLUMN last_name TEXT;

Then you backfill:

UPDATE users
SET first_name = split_part(full_name, ' ', 1),
    last_name = substring(full_name from position(' ' in full_name) + 1);

Only after the application reads the new columns safely do you remove full_name.

Why This Pattern Is Worth the Extra Steps

Expand and contract is slower than a one-shot breaking migration, but it handles real production conditions better:

  • rolling deploys
  • background jobs on older versions
  • retries against mixed application versions
  • partial backfills that need monitoring

The extra steps are not ceremony. They are what buy you compatibility during change.

Trade-Offs

The cost is temporary complexity:

  • more code paths for a while
  • migration bookkeeping
  • cleanup work later

Teams get into trouble when they do the expand phase and forget the contract phase. If the legacy path never gets removed, the migration becomes permanent architecture debt.

Further Reading