A six-year-old monolith serves about 1200 requests per second. Pricing is roughly 4% of the code and 60% of the CPU and has to be rewritten. Give the sequence that extracts it into its own service under live traffic, say where results can diverge, and name the point of no return.
Show the full answer Hide the answer
The sequence
- Draw the boundary in-process first. One module, one interface, no network. Every caller goes through the interface. This step finds the leaks - the three places that reach into pricing's tables directly - and it is reversible by a revert.
- Capture real inputs. Record a sample of production calls with their inputs and outputs for a period that covers one full business cycle, including a month-end and a promotion. These become the characterisation tests; a rewrite judged against a test suite written by its own author tests nothing.
- Stand the service up and shadow it. The monolith calls both, serves the old answer, and compares asynchronously. Watch the mismatch rate by reason, not in aggregate: a single number tells you nothing when 80% of it is rounding on one promotion type.
- Flip reads by percentage, with the flag evaluated per request: 1%, 10%, 50%, 100%, with a day at each step and a named person watching the mismatch and latency panels. The added network hop is real - budget 1-3 milliseconds plus serialisation on every priced request, and check it against the checkout latency budget before you start.
- Move the write side last - cached price snapshots, audit records, anything pricing persists.
- Delete the in-process implementation.
Where results can diverge
- Rounding and currency, especially where the old code accumulated in floats and the new one uses decimals. Expect this one; classify it rather than chasing it.
- Time. Two services read the clock at different instants, so a price valid at 23:59:59 can differ across a midnight or a promotion boundary. Pass the pricing timestamp in the request instead of reading the clock twice.
- Configuration drift. Tax tables, discount rules and feature flags now exist in two deployables. Until they share one source of truth, every divergence investigation starts by diffing config.
- Caches. The monolith's in-process cache and the service's cache expire at different moments, so the same input legitimately yields two answers.
The point of no return
Not the traffic flip - that is a flag. The line is crossed when the new service writes state the monolith can no longer produce: its own price-snapshot rows, its own audit trail, a schema the old module cannot read. From that moment, rolling back means reconciling data rather than redeploying code. Deleting the old implementation is the second, cheaper line - cheap only because the first one has already made the decision.
Keep that ordering deliberate: every step before the first write is a one-command rollback, and teams get into trouble by letting the write-side change ride along with the read switch to save a week.
How long it really takes
For one team, two to four months of calendar time, of which the shadow period is the part that cannot be compressed - it needs a month-end and a promotion to have happened. The rewrite itself is rarely the long pole.
When this is the wrong answer
If the 60% CPU is a hotspot rather than a workload, extract nothing. Profile first: a missing index, an N+1 over price rules, or a cache on a stable computation often removes most of that CPU in a sprint, and then there is no case left for a service - you would be paying a permanent operational cost and a network hop for a bill you already deleted. Extraction earns its keep when the constraint survives the profiling: pricing genuinely needs to scale on different hardware, or ship on a different cadence, or be owned by a different team.