A parallel run is a migration technique where you run the old system and the new system side by side against the same real inputs, compare their outputs, and only cut traffic over to the new system once the two agree closely enough to trust it — instead of testing a rewrite in staging and hoping it behaves the same under real production conditions. It is the single most reliable way to verify a rewrite, because it replaces "we believe the new system is equivalent" with a measured discrepancy rate against real traffic, not synthetic test cases.
How it works
The old system stays the system of record and keeps serving the response users actually receive. Every request (or a sampled percentage of them) is also sent to the new system in shadow — its response is computed and logged, but never returned to the user and never allowed to cause a side effect like a write, a charge, or a notification. A comparison step then diffs the two outputs and records where they disagree. Only once the discrepancy rate has been driven down to an acceptable, well-understood level does traffic actually start shifting authority to the new system — first for reads, then, carefully, for writes.
Step by step
- Instrument the old system as the source of truth. Every request path you intend to migrate needs its current output captured cleanly enough to compare against.
- Shadow traffic to the new system without side effects. The new system computes its answer but does not send emails, charge cards, or write to shared state during this phase — it is being observed, not trusted yet.
- Diff outputs automatically, not by manual review. A human spot-checking samples will miss the low-frequency edge cases that matter most; an automated comparison catches every discrepancy at real traffic volume.
- Triage and fix discrepancies, then re-run. Each mismatch is either a bug in the new system or a discovery that the old system's actual behavior differs from its documented behavior — both are useful findings.
- Shift authority gradually, starting with reads. Once discrepancies are rare and understood, let the new system's output actually reach users for low-risk paths first, watching error rates and business metrics as you go.
- Decommission the old system only after full authority has shifted and a safety window has passed. Keep it available as a fallback until you are confident the new system holds up under full production load with no regressions.
Tools and techniques
| Technique |
How it is used |
Example |
| Shadow traffic mirroring |
Duplicate live requests to the new system at the network layer |
Envoy/Istio traffic mirroring |
| Output diffing library |
Run old and new code paths in-process, compare results, log mismatches |
GitHub's Scientist library (Ruby, with ports to other languages) |
| Dual writes with reconciliation |
Write to both old and new data stores, periodically reconcile differences |
Database migration cutovers |
| Feature-flagged dark launch |
New code path runs for real but its output is hidden from the user until validated |
Gradual UI or logic rewrites |
Common mistakes
- Comparing only the happy path. Rewrites usually diverge on edge cases — unusual input, rare error conditions, timezone or rounding differences — not the common case. A parallel run that only exercises typical traffic misses exactly the bugs it exists to find.
- Letting the new system have side effects during the shadow phase. If the "shadow" path sends a real email or writes to a shared table, you no longer have a safe way to test it against production traffic without affecting real users.
- Relying on manual review instead of automated diffing. At any meaningful traffic volume, manual comparison cannot keep pace, and the rare mismatches that matter most are exactly the ones a tired reviewer will miss.
- Cutting over before the discrepancy rate is actually understood. A shrinking-but-still-present mismatch rate needs an explained cause for every category of discrepancy, not just a lower percentage, before you trust the new system with real traffic.
FAQ
How long should a parallel run last?
Long enough to see your full range of real traffic patterns, including monthly or seasonal edge cases if they exist — often weeks, not days, for systems with meaningful variability in their inputs.
Does a parallel run work for database migrations?
Yes, typically as dual writes to both the old and new data stores with a reconciliation job comparing them, which is how many major schema and datastore migrations are validated before cutover.
What is the difference between a parallel run and a canary release?
A canary release sends a small percentage of real traffic to the new system and lets its output actually reach those users. A parallel run computes the new system's output too but does not let it reach users until it has been validated against the old system's output.
Is a parallel run only for full rewrites?
No — it applies just as well to a significant refactor, a new CQRS read model being validated against an old CRUD one, or a change to business-critical calculation logic, anywhere confidence in equivalence matters more than development speed.
Where to go next
Use chaos engineering to test how the new system fails, not just how it performs on the happy path, before it takes real traffic. Track discrepancies found during a parallel run against your error budget so the migration's risk is measured the same way the rest of your reliability work is. And for a language-level rewrite specifically, Go vs Java is a relevant comparison if the migration involves a runtime change alongside the architecture change.