Validating Each Slice: Parallel Run, Flags & Rollback

ModernLift · ·8 min read
Part 7 of 8

Each strangler-fig step is made safe by three mechanisms working in sequence. A parallel run (shadow traffic) mirrors real production requests to a new slice and compares its output to the legacy system's, producing evidence the slice behaves identically. Feature flags at the routing facade then ramp the slice's live traffic gradually — canary, step up, full — without a deploy. And per-slice rollback keeps the legacy path live so a bad slice reverts with a flag flip, not an unwind. Routing traffic is a decision; earning the right to route it is the work.

Part 6 traced most strangler-fig failures back to a single root: a step in the loop done without discipline — a slice promoted without proof, a rollback that turned out not to work. This part is about that discipline. Three mechanisms make each strangle-step safe, and they run in sequence: a parallel run earns the right to route traffic to a slice, a feature flag ramps that traffic at the facade, and per-slice rollback contains a slice that misbehaves. Each has a deep treatment in our parity-validation series; here we cover the strangler-specific role of each and point you to the machinery.

Routing traffic is a decision. Earning the right to route it is the work.

Parallel run: earn the evidence before you route

A slice does not take live traffic because it passed its tests. It takes live traffic because it has been proven to behave like the legacy system it replaces — and the way you prove that in a strangler migration is a parallel run, also called shadow traffic.

The mechanism uses the facade from Part 3. For a slice under validation, the facade forwards each real production request to legacy as normal and returns that response — while asynchronously sending a copy to the new slice. The slice’s output is captured and compared against legacy’s, then discarded. Nothing the slice produces is ever shown to a user. The point is to test the new code against reality — the malformed records, the once-a-quarter batch, the client sending a deprecated field since 2014 — instead of against the inputs you thought to imagine. And because the legacy system is still answering for real, every shadowed request comes with the correct answer attached: the legacy system is the oracle.

Two things make or break a parallel run, and both are covered in depth in Shadow Traffic Testing Explained: side-effect isolation (a shadowed write path must not charge the card twice or send the email twice — reads shadow cleanly, writes need sandboxing), and observation window (run long enough to capture the system’s real cycles, because a week of clean results on a system with a quarter-end process has tested the easy 90 days and skipped the hard one). What the parallel run produces is a stream of divergences — concrete, reproducible differences to resolve. When that rate is driven to a defensible floor, the slice has earned its promotion.

Feature flags: ramp the traffic, don’t switch it

Evidence that a slice is safe is not the same as the slice carrying load. Something has to decide, on each live request, whether legacy or the proven slice answers — and that decision has to change in seconds, not in a deploy. That control surface is the feature flag, and in a migration it does a job quite different from the product toggles the term usually evokes.

A migration flag lives at the routing facade, not scattered through application code, for three reasons: one place to change the ramp or roll it back, the flag sitting in front of both implementations so neither needs to know it’s being shifted, and a clean rollback that re-points traffic rather than unwinding logic buried in the code you’re retiring.

And you ramp — you don’t flip to 100% and walk away:

StageTraffic to sliceWhat you’re watching
CanaryA small initial shareFirst live responses, error rate, latency, divergence
RampStepped increaseStability holding as volume grows; downstream behaving
MajorityMost traffic on the sliceTail cases, periodic jobs, off-peak patterns
FullAll traffic; legacy idleA clean settle window before decommissioning

The parallel-run comparison keeps running during the ramp, which turns the ramp itself into a final validation gate rather than a leap of faith. A subtlety that bites migrations specifically is sticky assignment: hash a stable key — user, account, tenant — so a given entity consistently lands on one side for a ramp stage, rather than flickering between legacy and slice mid-workflow. The full treatment of ramps, stickiness, and the flag debt you must clean up afterward is in Feature Flags in Production Migration.

Per-slice rollback: contain the bad slice

The flag is also your rollback — but only if the slice was built to be reversible. This is the property that makes the whole pattern safe, and it’s worth stating plainly: because the legacy path stays live and warm throughout the ramp, rolling back is setting the flag’s value, not unwinding work. If a slice misbehaves at 20% traffic, you set it to 0% and every request is back on legacy as fast as config propagates. No redeploy, no database restore, no war room — just a flag flip while legacy quietly takes the load back.

That promise holds only if its preconditions hold, and engineering them is the subject of Per-Slice Rollback Architecture:

  • The legacy path stays live. It’s the parachute; you don’t cut it away while you’re still in the air. It’s decommissioned only after the slice settles.
  • Schema changes stay reversible. Additive, backward-compatible, expand-then-contract — so rolling back the traffic doesn’t strand data in a shape legacy can’t read.
  • The blast radius is bounded to one slice. Each slice rolls back on its own flag and its own seam, so a bad slice is a contained incident, not a project-level crisis — exactly the all-or-nothing risk the strangler pattern exists to eliminate.

This is also where the strangler pattern’s slice boundaries pay off: containment only works if slices are carved along seams that don’t entangle their state with their neighbors. A clean boundary is what lets one slice roll back while every other promoted slice keeps serving.

Validation has edges

These three mechanisms are powerful, and forcing them where they don’t fit produces expensive false confidence rather than safety. Where the discipline stops:

  • A parallel run needs a stable oracle. A slice whose correct output legitimately varies — A/B randomization, ML scoring, anything with intentional entropy — has no byte-for-byte answer to compare against. You can shadow the deterministic parts and bound the rest statistically, but you can’t expect exact parity.
  • Behavior you’re deliberately changing isn’t held to parity. If a slice fixes a known legacy bug or improves a workflow, the legacy output is wrong by intent. Those paths are excluded from the parity bar and tested against the new spec instead.
  • Some side effects can’t be taken back. The flag controls the future, not the past. Once a slice has charged a card or shipped an order, rollback becomes compensation — a refund, a correction — not reversal. The architecture’s job is to push that point of no return as late as possible and name it, not to pretend it doesn’t exist.

Naming these is what keeps the discipline credible. Validation is the safety bar for behavior you’re preserving; it is not a promise that everything is reversible forever.

Where this leads

You now have the full strangler fig pattern: the facade that routes, the anti-corruption layer that translates, the loop that runs it, the failure modes to avoid, and the parallel-run-flag-rollback discipline that makes each step safe. The last part puts it in motion. Part 8, Strangler Fig Pattern Examples & Case Studies, walks through how the pattern actually unfolds on real system shapes — a monolith, a mainframe batch, a forms-based application — so the machinery from this series reads as a migration you could run, not just a pattern you could name.

Frequently asked questions

What is the difference between a parallel run and a feature flag in a migration?
A parallel run (shadow traffic) gathers evidence before a slice carries any live load — it mirrors real requests to the new slice while legacy still serves every response, then compares the two. A feature flag acts after the evidence exists — it decides, per live request, whether legacy or the proven slice answers, and lets you ramp that share up or roll it back without a deploy. One proves the slice is safe; the other promotes it incrementally.
How do feature flags differ in a migration versus normal product use?
Ordinary feature flags toggle a product feature on or off for a user segment. Migration flags route the same request between two implementations that must behave identically — legacy and a modern slice — usually with percentage ramps and sticky assignment, evaluated at the routing facade rather than scattered through application code. They control infrastructure-level traffic shifting, and every one is temporary, meant to be removed once the slice is fully cut over.
Is rollback in a strangler fig migration always instant?
Rerouting traffic is instant, but rollback is only truly cheap when the slice was built to be reversible — the legacy path still live, schema changes additive and backward-compatible, and no irreversible side effect already taken. Once a slice has charged a card or sent an email, a flag flip can't recall it. Per-slice rollback architecture pushes that point of no return as late as possible and names it explicitly, so everyone knows where cheap rollback ends.
All 8 parts of The Strangler Fig Pattern & Incremental Migration →