Skip to content

Migration Design

Backward-compatible. Staged. Tested in staging. Reversible by default. The four rules every migration honours — designed at shaping time, not improvised at deploy time.

Owners: Tech Lead, Developer Phase it lives in: What We Build → How We Build The corpus principle this enacts: Rollback possible is not. A confirmed time is.

Where it lives in the chain

How to do this

The migration ADR names, before code begins:

  1. The destination schema — what the model looks like after.
  2. The bridge statewhat both old and new code reads during the migration. Both versions must work, simultaneously, against the bridge.
  3. The deploy orderschema first or code first? Adding a column? Schema first. Removing a column? Code first.
  4. The backfill planhow do existing rows get the new shape? Online (during read), offline (batch job), lazy (on next write).
  5. The cutover signalwhat tells the team it's safe to remove the bridge? Usually a metric: zero reads of the deprecated field for N days.
  6. The reversal planif the cutover fails, what's the path back? Tested in staging.

What good practice looks like

A rename submitted_atgraded_at. Not a single migration. Five PRs:

  1. Add graded_at, dual-write from now on. Backward-compatible. Reversible.
  2. Backfill graded_at from submitted_at for old rows. Idempotent.
  3. Switch reads to graded_at. Old code still works (writes both). Reversible.
  4. Soak for two weeks. Watch the metric: zero reads of submitted_at.
  5. Drop submitted_at. The point of no return — labelled explicitly.

A single PR that renames the column is an outage waiting to happen. The JWT outage shipped because the migration discipline wasn't applied to a config change. Configuration changes are migrations too.

200apps · How We Work · NWIRE