A replacement architecture is easy to describe at rest. Requests enter the new path. Data has the new shape. Dependencies use the new interface. The old system is gone.

Production never occupies that clean diagram. For a period, old and new paths coexist. Data moves at different speeds. A request can cross the boundary twice. Operators must diagnose failures without knowing which version owns the state. The migration architecture matters as much as the destination.

I begin with the promises that must remain true while the system changes.

Write the invariants first

An invariant is a condition the migration may not violate. It should be observable at the boundary where a user or dependent system experiences it.

Examples include:

  • an accepted request produces at most one external effect;
  • a completed action remains visible after traffic moves;
  • a caller can recover without repeating irreversible work;
  • an operator can determine which path handled a request;
  • rollback does not discard state created by the new path.

The list should be short enough to govern decisions. “Preserve all behavior” is impossible to test. “Every accepted payment has one durable ledger entry” can drive a design, a monitor, and a release gate.

Each invariant needs an owner, an instrument, and a response when it fails. Without them, a failure leaves the team with no agreed action.

Choose the unit of movement

A migration becomes safer when the unit of change is smaller than the whole system. The unit might be a customer cohort, a workflow type, a read path, a region, or a bounded set of records.

The best unit has three properties. It can be routed independently. Its result can be compared with the existing path. Its failure can be contained.

Moving reads before writes can reduce risk when both systems can interpret the same durable state. Moving one workflow end to end may be safer when splitting the workflow creates ambiguous ownership. The architecture determines which boundary is real. The project plan should follow that boundary.

Make coexistence explicit

Dual-running creates temporary complexity. Hidden dual-running creates permanent confusion.

I write down which system owns each responsibility during every phase:

ResponsibilityQuestion
RoutingWhich path receives a new request, and how can that choice be traced?
WritesWhich system is authoritative for each state transition?
ReadsHow are disagreements detected and presented?
RepairWho reconciles divergence, using which source of truth?
RollbackCan the old path understand state produced by the new path?
OperationsWhich team responds, and which dashboards distinguish the paths?

Temporary adapters and duplicated state need removal conditions when they are introduced. Otherwise they become a second architecture with no deliberate owner.

Compare before committing

Shadow execution is useful when the new path can run without causing an external effect. The same input goes through both paths; only the established path controls production. Differences become evidence.

The comparison must reflect the outcome. Matching HTTP status codes says little if the important result is a durable external change. For an AI workflow, textual similarity can hide different actions, authority choices, or recovery behavior.

I define the comparison before collecting the data:

  • which cases belong in the cohort;
  • which differences are acceptable;
  • which difference requires investigation;
  • how missing or delayed observations are handled;
  • what evidence permits the next traffic increase.

The release decision should depend on this record. If a dashboard cannot change the traffic decision, it should not count as release evidence.

Design rollback around state

Traffic can move back in seconds. State may not.

A credible rollback plan answers what happens to work accepted by the new path. Can the old system read it? Must it be transformed? Does replay duplicate an external effect? Who resolves records left between states?

For some changes, backward compatibility is worth its cost. For others, a forward repair is safer than rollback. The team should decide this before release and state the point after which rollback is no longer valid.

Rolling back traffic without repairing state can create a second incident.

Remove the old path

Traffic on the new path is an intermediate milestone. Completion requires removing the old code, data flow, infrastructure, alerts, access, and operating instructions.

I use explicit removal gates:

  1. The new path has carried the representative workload for a stated period.
  2. The invariants have held under normal and adverse conditions.
  3. Open divergence has been reconciled.
  4. Operators can diagnose the new path without the migration team.
  5. Rollback obligations have expired or been replaced by a repair procedure.

Removing the old path forces hidden dependencies into view. A consumer still reading an old table or an operator relying on an old dashboard is evidence that the migration has more work.

The migration is a production system

A migration has users, state, failure modes, operators, and a retirement plan. It deserves the same discipline as the destination architecture.

The finish is precise: the new path owns the intended work, the preserved invariants are measured, operators can handle failure, and the old path can be removed without guessing. Until then, the system is still migrating.