CodeOath
← All posts
Architecture & Patterns70 min total · 17 parts

Microservices vs. Monolith: Architecture Patterns and Trade-offs

Contents — Part 10 of 17: Resilience Patterns
Part 10 of 17 · ~2 min

Resilience Patterns

The "a network call can fail in ways a function call can't" cost (mentioned above) needs actual, deliberate handling — otherwise one struggling service tends to take down every other service that calls it, cascading a single, localized problem into a system-wide outage. A handful of patterns are the standard answers:

  • Timeouts. A synchronous call to another service must have a timeout — without one, a slow (not down, just slow) dependency can hold a caller's connection/thread open indefinitely, and enough of those piling up exhausts the caller's own resources, spreading the outage upstream even though the original service is only slow, not fully down.
  • Retries (with backoff). A transient failure (a momentary network blip, a brief spike in the callee's load) can often succeed on a second attempt — but retrying immediately, and especially retrying aggressively from many callers at once, can turn a struggling service's brief trouble into a self-inflicted overload (a "retry storm"). Exponential backoff (waiting progressively longer between retries) and capping the total number of attempts are both necessary, not optional refinements.
  • Circuit breakers. After a dependency fails repeatedly, a circuit breaker "opens" — it stops even attempting calls to that dependency for a cooldown period, failing fast instead, then periodically allows a trial request through to check whether the dependency has recovered before fully "closing" again. This protects a healthy caller from wasting its own resources on calls that are very likely to fail anyway, and gives a struggling dependency room to recover instead of being immediately hit with the same retry traffic that may have contributed to its trouble in the first place.
  • Bulkheads. Named after a ship's compartmentalized hull sections — isolating resources (thread pools, connection pools) used for calls to one dependency from the resources used for calls to another, so one dependency being slow or unavailable doesn't exhaust a shared pool of resources that a completely unrelated dependency also needed to keep functioning.

These patterns compound: a well-behaved service-to-service call typically has a timeout, a bounded number of retries with backoff, sits behind a circuit breaker, and draws from a bulkheaded resource pool — not because any single one of these is exotic, but because each protects against a distinct failure mode the others don't cover.