Why splitting a service can lower availability

Microservices · lesson 26 of 33 · 6 min read

Latency adds and availability multiplies - the maths that decides your architecture.

Open this lesson in the learning hub

Key points

  • In a synchronous call chain, availability multiplies. Five services at 99.9% each give 0.999 to the fifth power - about 99.5%, or roughly four hours of downtime a month instead of forty minutes.
  • Latency adds in the same way, and worse: a request that fans out to ten services waits for the slowest, not the average. If each has a p99 of 200ms, the combined p99 is far above 200ms.
  • This is why a monolith split naively into services is often less reliable than what it replaced. Nothing got worse individually - the composition did.
  • The fix is not making each service more reliable, which has sharply diminishing returns. It is removing hard dependencies from the request path.
  • Three ways to do that: cache the dependency so a failure serves stale data, default it so a failure serves something reasonable, or make it asynchronous so the request does not wait at all.
  • Classify every dependency as critical or optional before you build. If checkout genuinely cannot proceed without the recommendations service, that is a design decision - and usually the wrong one.

Example

/*
 * The arithmetic, made concrete.
 *
 * SEQUENTIAL CHAIN - availability multiplies:
 *
 *   1 service   99.9%                  -> 43 min/month down
 *   3 services  0.999^3 = 99.70%       -> 2.2 hours/month
 *   5 services  0.999^5 = 99.50%       -> 3.6 hours/month
 *  10 services  0.999^10 = 99.00%      -> 7.3 hours/month
 *
 * Every one of those services met its SLO. The composition did not.
 *
 * PARALLEL FAN-OUT - latency tracks the slowest:
 *
 *   10 calls, each p99 = 200ms
 *   P(all under 200ms) = 0.99^10 = 90.4%
 *   -> ~10% of requests are slower than ANY single call ever is
 *
 * This is why tail latency dominates a fan-out architecture.
 */

// Removing a hard dependency. The call still happens - the REQUEST no
// longer depends on it succeeding.
@Service
public class ProductPageService {

    public ProductPage build(String sku) {
        Product product = catalog.find(sku);          // critical - must succeed

        // Optional: a failure degrades the page, it does not fail the request.
        List<Product> suggestions = recommendations.forSku(sku)
                .onErrorReturn(List.of())             // empty section, page renders
                .timeout(Duration.ofMillis(120))      // and it never waits long
                .block();

        return new ProductPage(product, suggestions);
    }
}

/*
 * With recommendations optional, the page availability is the catalog
 * availability alone - 99.9% instead of 99.8%, and no tail latency added.
 */

Availability multiplies down a call chain, so the lever is removing hard dependencies - not making each service marginally better.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Microservices course, and every lesson in it is listed on the Microservices contents page.