A language runtime used by 180 of 240 services leaves vendor support in 14 months. Services are owned by 40 teams with their own roadmaps, and the upgrade is mostly mechanical but breaks in about 15% of cases. Which approach gets the estate upgraded?
Show the full answer Hide the answer
The deciding property
The work is 85% mechanical and 15% genuinely hard, and those two halves need different owners. The mechanical part is cheap per service and expensive in aggregate — 180 services × a day or two is 200-400 engineer-days — and it is exactly what tooling amortises: an automated dependency bump, a codemod for the known breaking changes, a test run, an opened pull request.
The hard 15% is roughly 27 services with a native extension, a pinned transitive dependency, or behaviour that changed subtly. Those need someone who has now seen the same problem twenty times, which is an argument for concentration, not distribution.
The second deciding fact: the deadline is external and the teams' incentives are internal. Nothing in a product roadmap rewards an upgrade, so an unfunded mandate converts into a queue of deferrals and a rush in month 13, which is when the breakages get discovered under time pressure.
The design that follows
- A central team of three to five for the 14 months, whose output is tooling and the hard cases, not a service-by-service migration.
- A generated pull request per service, with the mechanical change applied and the test suite run, so a team's cost is a review rather than a project.
- A visible burndown by team and by service, with the support date on it. Dashboards do not create urgency by themselves, but an absent one guarantees that nobody knows where the estate is until the end.
- A blocking rule for new services from day one — cheap, uncontroversial, and it stops the denominator growing while you work.
- Escalation by exception: teams that cannot take the generated change say so early and get help, rather than reporting progress they are not making.
The reason this shape works is mechanical rather than cultural: the cost of the mechanical path falls as the tooling improves, because each fix to the codemod applies to every remaining service, while the cost of an unfunded mandate is constant per team and paid 40 times. Organisations that have run this well — the public accounts of large Python 2 to 3 and JDK migrations through the 2010s say the same thing — report that the tooling, not the deadline, is what moved the number.
Why the other options fail
- Mandate with a deadline, unfunded. The classic, and it fails predictably: each team rationally defers work with no product value, the hard 15% is discovered in the final weeks by people encountering it for the first time, and the organisation either ships the deadline or ships nothing else that quarter.
- Block new, migrate opportunistically. Correct as a component, insufficient alone. It stops growth and does nothing about 180 existing services, and "opportunistically" with no funding means the services that never change never migrate — which are disproportionately the ones nobody understands.
- Extended vendor support. Sometimes a legitimate tactical purchase, and a trap as a strategy. It costs money, typically arrives with a security-fix-only scope, and returns the organisation to the same decision a year later with less time and the same 180 services. Buy it to de-risk a plan, never instead of one.
When this is the wrong answer
If the upgrade is not mechanical — a major framework rewrite where each service needs genuine redesign — tooling has nothing to amortise, and the honest answer is a prioritised list where most services are deliberately left behind and isolated, with only the ones that matter moved. The central-team model earns its funding on repetition, and where every case is bespoke, there is nothing to repeat.