Per-Slice Rollback Architecture
Per-slice rollback architecture designs each modernized slice so that returning to the legacy path is a single, fast, low-risk action — a flag flip, not an unwind. It keeps the legacy path live, makes schema changes additive and reversible, and bounds the blast radius to one slice so a bad promotion never threatens the whole system.
Part 5 made a bold promise: rolling back a slice is setting a flag’s value, not unwinding a quarter of work. Part 6 added the thing you roll back to — an immutable, validated prior artifact. But both rest on an assumption that isn’t free: that the slice was built to be reversible. A flag can reroute traffic in seconds; it cannot un-write the rows the slice persisted, un-send the message it emitted, or teach the legacy system to read a schema it’s never seen. Per-slice rollback architecture is the engineering that makes the flag’s promise true — designing each slice so that going back is genuinely cheap.
Rollback is a property you build, not a button you press
The flag flip is the trigger. Whether the trigger is safe to pull depends entirely on the state of the world behind it. A slice that took live traffic for a week has done things: written data, called downstream systems, accumulated state. If any of that is shaped so the legacy path can’t pick back up cleanly, then “roll back” stops meaning “return to the prior known-good state” and starts meaning “return the traffic, then spend two days reconciling the mess the slice left behind.” That’s not rollback — that’s recovery, and it happens at exactly the worst moment.
So rollback safety is designed in at slice-planning time, alongside the parity work. The question “how do we undo this?” is asked before the slice is built, not improvised during an incident. Three properties carry most of the weight: the legacy path stays live, schema changes stay reversible, and the blast radius stays bounded to one slice.
Property one: the legacy path stays live
This is the foundation, and it’s why the strangler facade exists. Throughout a slice’s ramp, the legacy implementation is not deleted, not disabled, not “available if we redeploy it” — it is running, warm, and capable of serving the full request load this instant. Rollback re-points the flag and legacy resumes serving with no cold start, no redeploy, no scramble.
The temptation to retire the legacy path early — to “save the cost of running two systems” — is exactly the temptation that turns a flag flip back into an outage. The legacy path is the parachute. You don’t cut it away while you’re still in the air. It is decommissioned only after the slice has held full traffic through a real settle window, and not one minute sooner. Until then, paying to run both systems is not waste; it’s the price of reversibility, and it’s cheap next to a failed cutover.
Property two: data has to be reversible too
Traffic reroutes instantly; data persists. This is the genuinely hard part of rollback, and the part teams underestimate. While a slice serves live traffic, it writes state. Roll the traffic back to legacy, and that state is still there — and now the legacy system has to cope with it.
The failure mode is concrete. The slice “improved” a schema — split a column, renamed a field, introduced a new table legacy doesn’t know about. It wrote records in the new shape. You roll back. Now legacy is reading data written in a shape it can’t parse, or missing data that lives only in the new table. The traffic rollback was instant; the data is corrupt or orphaned. You’ve made things worse than no rollback at all.
The discipline that prevents this is expand/contract — additive, backward-compatible schema evolution:
| Phase | What happens | Rollback safety |
|---|---|---|
| Expand | Add new structures additively; old ones stay intact | Both implementations can read/write; fully reversible |
| Migrate | Both paths run; data dual-written or dual-readable | Either system can serve; rollback is clean |
| Contract | Remove the old structures — only after the slice is permanent | The point of no return; done last, deliberately |
The principle is that for as long as rollback must stay cheap, every schema change is additive and backward-compatible. You never rename a column out from under legacy — you add the new one and keep the old in sync. You never remove a field both might need — you stop using it before you drop it. Destructive, narrowing changes (the contract phase) are deferred until the slice is so settled that rollback is no longer a realistic need. The order is the safeguard: expand and migrate are reversible; contract is the bridge you burn last, on purpose.
Property three: bound the blast radius to one slice
A slice-by-slice migration never has a single grand cutover to reverse — it has a stream of independent promotions. That’s the structural reason rollback is designed per slice: each slice is promoted on its own flag, behind its own facade boundary, and so each rolls back on its own without touching the others.
This containment is the quiet superpower of the slice model. When a slice misbehaves, you roll back that slice — that one flag, that one boundary. Every other already-promoted slice keeps serving. The migration as a whole doesn’t lurch backward; one piece does, and you fix it and re-promote while the rest of the program proceeds. Contrast a big-bang cutover, where a problem anywhere means reversing everything everywhere — the all-or-nothing risk the strangler pattern exists to eliminate. Bounded blast radius is what lets a program absorb a bad slice as a contained incident instead of a project-level crisis. A bad slice turns into a bug ticket for one team, not a war-room call for everyone.
For the boundary to actually contain a failure, slices must be carved along seams that don’t entangle their state and contracts with their neighbors — which is why slice boundaries are a discovery-phase decision, not an afterthought. A slice whose data is hopelessly interwoven with three others can’t be rolled back alone, and that’s a design smell worth catching before the code is written.
Side effects: the limit of cheap rollback
Some actions can’t be taken back, and pretending otherwise is dishonest. If a slice charged a card, sent an email, shipped an order, or fired a webhook a partner already acted on, rerouting traffic to legacy does not recall any of it. The flag controls the future; it has no authority over the past.
Per-slice rollback architecture handles this not by pretending the actions are reversible, but by being precise about the point of no return — the moment a slice does something the system can’t cleanly undo. The design goals:
- Push the irreversible action as late as possible in the request flow, so the maximum amount of processing is still rollback-safe if something goes wrong before it.
- Make it idempotent and well-bounded where you can, so a retry or a rollback-and-replay doesn’t double the effect.
- Name it explicitly. Every slice’s rollback plan states where its cheap-rollback window closes. Past that point, recovery is compensation (a refund, a correction event), not reversal — and that’s a different, slower, human-involved process that everyone should understand before the slice goes live.
This is the honest boundary of the whole technique. “Roll back with a flag flip” is true and powerful right up until a slice takes an action the world has already seen. The architecture’s job isn’t to make that impossible — it’s to make the window where rollback stays cheap as wide as it can be, and to mark its edge clearly so no one is surprised by where it ends.
What a rollback-ready slice looks like
Pulling the properties together, a slice is rollback-ready when:
- The legacy path is live and load-capable, not archived — the parachute is still attached.
- A known-good prior artifact exists to return to (Part 6’s immutable builds), and the current one is byte-identical to what parity validated.
- All schema changes to date are additive and backward-compatible; nothing destructive has run.
- The slice’s boundary is clean enough that rolling it back leaves every other slice untouched.
- The rollback plan names the point of no return, and everyone knows where it is.
Meet those and the flag flip from Part 5 does exactly what it promised. Miss any one and “instant rollback” is a slogan, not a capability.
Where this leads
You now have every piece of safe delivery: shadow traffic to gather evidence, equivalence triage to judge it, comparison and reconciliation to produce it, feature flags to control promotion, a pipeline to deliver validated slices, and a rollback architecture that makes every step reversible. Part 8, Zero-Downtime Cutover Playbook, assembles them into the move it all builds toward: running a full cutover with no downtime and no big-bang risk — where everything in this series comes together as one practiced sequence.
Frequently asked questions
- Why design rollback per slice instead of for the whole migration?
- Because a slice-by-slice migration never has a single cutover to reverse. Each slice is promoted independently, so each needs its own reversible boundary. Per-slice rollback bounds the blast radius — a problem in one slice rolls back that slice alone, leaving every other promoted slice untouched.
- What makes data the hardest part of rollback?
- A flag flip reroutes traffic instantly, but data written while the slice was live still exists. If the slice wrote in a shape legacy can't read, rolling back the traffic leaves orphaned or unreadable state. Reversible schema changes — additive, backward-compatible, dual-readable — are what keep the data side rollback-safe.
- Is instant rollback always achievable?
- No. Once a slice performs an irreversible action — a destructive migration, an external side effect that can't be recalled — rollback is no longer free. The architecture's job is to push that point of no return as late as possible and to name it explicitly, so everyone knows when the cheap-rollback window has closed.