Migration Cutover Workflow
Run old and new in parallel with a dual-write gate, shift read traffic by percentage, verify parity on a canary, then cut over and keep the old on standby for rollback.
Objective
Move traffic from the old system to the new one with a parity check at every step and a rollback that is one switch away.
Environment
- The old and new systems running in parallel
- A traffic router that can split by percentage
- A parity check that compares old and new responses
Steps
1. Dual-write
Write to both systems so the new one accumulates state while the old remains the source of truth.
Expected state: Both systems have the same writes; reads still go to the old.
2. Shadow reads
Send reads to both and compare the responses, logging mismatches without serving the new to users.
Expected state: A mismatch log with a rate; if the rate is zero, the new is ready to serve.
3. Canary reads
Shift a small percentage of reads to the new, watch the error rate, and fall back if it rises.
Expected state: A canary slice served by the new with a flat error rate.
4. Cut over
Shift all reads to the new, keep the old on standby for a rollback window, then decommission.
Expected state: All traffic on the new, the old on standby, rollback is one switch.
Decision points
How long is the rollback window?
- One day
- One week
One week, so a weekly batch path that only runs on the new has a full cycle before the old is gone.
What mismatch rate is acceptable?
- Zero
- Below 0.1 percent
Zero for write paths; a tiny rate for read paths may be acceptable if the diff is cosmetic.
Expected outcome
All traffic on the new system, parity verified before the cutover, and the old kept on standby for a rollback window.
Verification
Confirm the mismatch rate was zero before the canary, the error rate is flat after the cutover, and the old is on standby.
Cleanup
Decommission the old after the rollback window, archive the mismatch log, and record the cutover in the changelog.
Parallel with parity
Run old and new in parallel. Dual-write so the new accumulates state. Shadow-read and compare; a zero mismatch rate means the new is ready to serve. A non-zero rate is a finding, not a blocker.
if (!deepEqual(oldResp, newResp)) log.mismatch(route, oldResp, newResp);
Canary, then cutover
Shift a small percentage of reads to the new and watch the error rate. If it is flat, shift all. Keep the old on standby for a rollback window, then decommission.
Verification
The mismatch rate was zero before the canary, the error rate is flat after the cutover, and the old is on standby. Keep the old for a full week so a weekly batch path has a cycle on the new before the old is gone.
Verification detail
A zero mismatch rate for write paths is non-negotiable; a tiny rate for read paths may be acceptable if the diff is cosmetic and reviewed.