Guide contents

Lesson 55 of 129

Microservices Trade-offs and the Distributed Monolith

Splitting an application into services converts in-process calls into remote calls and one database into several. Both changes add costs that a monolith does not have. The costs fall into four groups: network, consistency, operations, and testing. The worst outcome is a distributed monolith, which has all these costs and none of the benefits of independence.

Costs

Network

Property In-process call Remote call
Latency Nanoseconds Around a millisecond or more within a data center; higher across regions; variable
Failure Call returns or the process dies Timeout, connection reset, partial failure, unknown outcome
Arguments Shared memory Serialized and versioned
Ordering Deterministic Messages may be reordered, duplicated, or lost

Latency accumulates along call chains. A request that traverses five services in sequence pays five network hops, and its total latency is the sum of the per-service latencies. Tail latency compounds too: if each service exceeds its own 99th-percentile latency on 1% of calls, the probability that at least one of five sequential calls is slow is 1 - 0.99^5, about 4.9%, so roughly one request in twenty experiences a slow call somewhere.

Availability also multiplies: if a request needs five services in sequence and each is 99.9% available, the combined availability is about 0.999^5, roughly 99.5%. That is about 44 hours of unavailability per year instead of about 8.8 hours for one service. This assumes independent failures and no fallbacks.

Consistency

Each service owns its data, so a business operation spanning services cannot use one ACID transaction. Options are sagas with compensating actions, or accepting eventual consistency between services. Consequences:

Operations

  • Each service needs a build pipeline, deployment, configuration, secrets, dashboards, alerts, and on-call ownership.
  • Debugging requires correlating logs and traces across services.
  • Infrastructure needs service discovery, routing, and certificate management.
  • Versioning: producers and consumers of an API are released at different times, so contracts must stay compatible.

Testing

  • Unit tests remain local, but integration tests need other services, which are slow, flaky, or unavailable.
  • End-to-end tests across many services are slow and hard to diagnose.
  • Contract tests and test doubles replace some integration tests (see API versioning and contract testing).
  • Failure behavior (timeouts, partial outages) must be tested deliberately (see chaos engineering).

Fallacies of distributed computing

The fallacies of distributed computing are assumptions that engineers new to distributed systems tend to make. The first seven were listed by L. Peter Deutsch at Sun Microsystems, and James Gosling added the eighth (the network is homogeneous). Each is false, and each shapes microservice design:

Fallacy Consequence for design
The network is reliable Timeouts, retries, idempotent operations
Latency is zero Limit call chain depth, batch calls, cache
Bandwidth is infinite Avoid large payloads, paginate, compress
The network is secure Encrypt and authenticate service-to-service traffic
Topology does not change Service discovery instead of fixed addresses
There is one administrator Contracts and versioning between teams
Transport cost is zero Serialization and infrastructure cost money and CPU
The network is homogeneous Interoperable protocols and formats

The distributed monolith

A distributed monolith is a system split into separately deployed services that still cannot be changed or released independently. It keeps the coupling of a monolith and adds the network's costs.

Symptoms

  • A feature change requires coordinated changes and releases in several services.
  • Services must be deployed in a specific order.
  • Many services share one database or the same tables.
  • A failure in one service makes many others fail, because callers depend on synchronous responses without fallbacks.
  • Services share a library containing domain models, so a change to that library forces every service to update (see shared libraries).
  • Teams need cross-team meetings to ship routine changes.
  • A single request fans out through long synchronous call chains.
graph LR
    A["Order service"] -->|"sync"| B["Customer service"]
    B -->|"sync"| C["Pricing service"]
    C -->|"sync"| D["Inventory service"]
    D -->|"sync"| A
    A --> DB[("Shared database")]
    B --> DB
    C --> DB
    D --> DB

In this diagram, a cycle of synchronous calls means no service works when any other is down, and the shared database means schema changes affect all of them.

Causes

Cause Mechanism
Boundaries drawn by technical layer A feature crosses UI, logic, and data services, so each change touches all
Boundaries drawn before the domain is understood Services turn out to change together
Shared database Schema is a shared contract; services cannot evolve independently
Synchronous chains Runtime coupling: availability depends on every service in the chain
Chatty interfaces Fine-grained calls imply that the data or behavior belongs in one service (see service granularity)
Shared domain libraries Compile-time coupling
Team structure not aligned to services Ownership is unclear, so changes need cross-team coordination (see Conway's law)

Remedies

  1. Align boundaries to business capabilities and bounded contexts (see domain-driven design and bounded contexts).
  2. Give each service its own datastore (database per service).
  3. Replace synchronous chains with asynchronous events where the caller does not need an immediate answer (see synchronous vs asynchronous communication).
  4. Merge services that always change together. Merging is a valid correction.
  5. Version APIs and keep changes backward compatible so releases do not need coordination.

Interview questions

  • How does a distributed monolith differ from a monolith? Both have components that must change and release together. The distributed monolith adds network latency, partial failure, and operational overhead, without the local transactions and simple deployment of a monolith.
  • How does chaining synchronous services affect availability? The availability of the chain is roughly the product of the availabilities of its members, so it is lower than any single one. Latency also adds up along the chain.
  • What is a sign that two services should be merged? They always change together, need coordinated releases, or call each other with many fine-grained requests.
  • Which fallacy of distributed computing motivates retries and timeouts? "The network is reliable." Requests and responses can be lost or delayed, so callers must bound waiting and handle repeat delivery.