Chapter 18 of 27 · Part 8: Cross-cutting concerns

Resilience and stability patterns

How services keep working when their neighbours fail.

key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral

What resiliency means

Resiliency is more than adding retries. Newman separates four ideas. From Building Microservices, ch. 12.

Four parts of resiliencyDealing with the expectedRobustness: absorb known shocksRebound: recover after a blowDealing with the unexpectedGraceful extensibility: copeSustained adaptability: keep changingMore robustness adds complexity, which is a new source of failure.

Rebound needs tested backups and an outage playbook: who leads, how fast to tell users, and how. Plan it before the stress hits.

Degrading functionalityItem pageDetailsup: shownItem pagePrice and stockup: shownItem pageCartdown: hide itOne failed service should remove one part of a page, not the whole page.

Stability patterns

PatternWhat it doesWatch out
Time-outGive up on slow calls; also cap the whole operationNo time-out hangs threads; a 30 s one made users refresh and add load
RetryRepeat temporary failures (503, 504), not a 404, after a delayRetries add to the wait: 3 tries plus pauses can reach 3.5 s
BulkheadSeparate resources, e.g. a connection pool per downstreamCosts resources, but stops one failure spreading
Circuit breakerAfter enough failures, fail fast, then probe for recoveryMandate it for synchronous downstream calls
IsolationOwn machines, or middleware that buffers callsMore infrastructure to run
RedundancySeveral instances, across availability zonesNeeds capacity for the extra copies
IdempotencyMake repeats safe so messages can be replayedNeeds a unique reason or id per operation
Load sheddingReject some requests so the service survivesDecide which work matters most
Circuit breaker statesClosedcalls flow throughOpenfail fastHalf-openprobe a few callstoo many failuresafter a waitprobes healthy: resetIf the probes fail, the breaker opens again.
Bulkheads: a connection pool per downstream serviceYour servicecalls 3 servicesPool for AService APool for BService BPool for CService CService C is slow and drains only its own pool. A and B keep working.
Making a points credit idempotentNot idempotentcredit 100 to account 1234Replay: 200 pointsIdempotentcredit 100, reason: order 77Replay: ignored, still 100Add the reason or id, and the receiver can spot a repeat.

Availability and the CAP trade-off

Scaling and availability meet in distributed data. Spread instances over several availability zones, because a cloud provider promises uptime for a region, not for one node. Then accept what the CAP theorem says: when the network between nodes fails, you keep consistency or availability, not both (the sections above cover resiliency in depth).

A partition forces a choiceData centre 1Data centre 2Inventory + DBaccepts writesInventory + DBaccepts writeslink downAP: keep answeringdata may be stale, merge laterCP: refuse some requestsanswers stay consistentChoose per capability: catalog can be AP, a stock update may need CP.

Newman advises against writing your own consistent store; use an off-the-shelf one. Aim for eventually consistent AP designs where the business allows (a rare stock error can be fixed with an apology). The choice is per capability: a loyalty-points service may show stale balances but require consistency when spending them.

From Building Microservices, ch. 12, "Spreading Your Risk" and "CAP Theorem", as it applies to scaling.

CAP theorem, chaos and game days

The CAP theorem says a distributed data store cannot give consistency, availability and partition tolerance all at once. Networks do fail, so the real choice during a partition is between the first two. From Building Microservices, ch. 12.

Pick CP or AP per serviceCP: stay consistentRefuse requests in a partitionFits inventory, balancesAP: stay availableServe possibly stale dataFits product catalogAP systems are eventually consistent. Ask how stale is acceptable for the business.

Do not invent multi-node consistency yourself. Use a data store or lock service built for it, such as Consul. It is not all or nothing: one system can mix AP and CP services.

A chaos experiment or game dayPlanpick a weaknessInjectfail a nodeObservedid users notice?Learnfix it, no blameChaos engineering = experiments that build confidence in production resilience.
PracticeWhat it testsExample
Chaos MonkeyInstance lossTurns off random machines in production
Chaos GorillaZone lossTakes out an availability zone
Latency MonkeySlow networkSimulates slow links between machines
Game dayPeople and processPlanned outage drill; the key expert is kept out of the room

The resilience toolkit

At-least-once delivery means duplicates and replays. The book borrows ACID 2.0 (associative, commutative, idempotent, distributed) from the CALM theorem: facts that only accumulate are safe on an eventually consistent store. From Practical Event-Driven Microservices Architecture, ch. 7.

Designing an idempotent eventStockOut subtracts again on replay. StockChanged carries the current quantity and is safe to repeat.StockOut { Quantity: 1 }replayed twice = stock down by 2order agnostic, not idempotentStockChanged { CurrentQuantity: 5 }replayed twice = same resultneeds versioning for orderorTrade-off: StockChanged is safe to retry but carries less intent. Or keep StockOut and storeprocessed ids.
TopicRule from the book
AcknowledgementAcknowledge after processing, not on receipt. Auto-commit can lose a message in memory (message leak) on a crash or deploy.
Poison eventsIgnore ones that fail validation. For a bug, fix and rewind the offset. A dead letter queue helps when many good events followed the bad one.
RetriesCap attempts, back off exponentially, add jitter, retry only errors that can succeed. Google found retrying after the 95th percentile latency added about 2 percent load.
Circuit breaker around a dependencyFailures over a threshold open the breaker and stop consumption; after a pause a probe decides whether to close it.Closedevents flowOpenstop, do not ackHalf-openprobe dependencythreshold hitafter a pauseprobe succeeds: resume consumingEvents pile up in the broker during the outage and are processed after recovery. A fallback such asan in-memory cache can replace the dependency.
Repairing state by rewinding the offsetAfter a buggy release the service rewinds its offset and reprocesses; only corrected orders produce new events.Bug releasedoffsets 137-141 badRoll backfix the codeRewind offsetfrom 147 to 137Reprocessstate repairedIdempotency makes the already correct events harmless. Wrong events stay in history; corrected onesfollow.
Bulkhead and priority queuesA bulk job gets its own low-parallelism queue so it cannot starve live traffic on the shared database.Live changes queuehigh parallelismBulk operation queuelow parallelismTaxes serviceparallelism per queueShared databaseone clusterLower the parallelism or instance count of the noisy work. It runs slower, but nobody else suffers.
Pair it with scale: use bulkheads for dependencies you cannot isolate (a shared database cluster, a cache, a synchronous call). Separate clusters are cleaner but often not worth the cost.

Recap

Quick check

Order calls Payment with a 30 s time-out and retries three times with no pause. Users refresh. What goes wrong and what helps?
Show answer

Slow calls tie up threads and refreshes and retries add load, so a sick Payment drags Order down. Use a shorter time-out within an overall budget, retry only temporary errors after a delay, add a circuit breaker so calls fail fast, and give Payment its own bulkhead pool.