Design for the Cloud — A Practical Series

Cloud Design Patterns

Article 5 of 5 · The Cures

Jun 10, 2026 · devops · 30 min read · 5600 words advanced

Cloud design patterns — the cures.

devops cloud design-patterns resilience architecture

The payoff. Part 5 of Design for the Cloud is the catalog of good patterns — named, reusable, battle-tested solutions to the exact problems Part 3 named as antipatterns. Cache-Aside, Retry, Circuit Breaker, Bulkhead, Throttling, Queue-Based Load Leveling, Competing Consumers, Gateway Aggregation, CQRS, Materialized View, Sharding, Saga, Strangler Fig. For each: what it is in plain words, the antipattern it cures, and when not to use it. If Part 3 was the diseases, this is the pharmacy.

A design pattern is a named, reusable solution to a problem that comes up over and over. You didn't invent the problem and you don't have to invent the solution — someone hit it before, solved it well, and gave the solution a name so we can all just say "use a Circuit Breaker" and know what each other means. Patterns are the shared vocabulary of architecture.

This is the capstone of the series, and it closes the loop. Part 3 catalogued the antipatterns — the traps. This part catalogues the patterns — the cures. The mapping is almost one-to-one, which is the whole reason I kept pointing forward to "Part 5" throughout: once you know the antipattern by name, the pattern that fixes it is right here. We'll group them the way you'll reach for them: resilience (surviving failure), scaling & decoupling (handling load), and data (managing state).

One warning before the catalog, because it's the most common mistake with patterns: every pattern has a cost. Each one adds complexity, moving parts, or new failure modes. A pattern applied where it isn't needed is just complexity you pay for and don't use — its own kind of antipattern. So for each, I'll say not just what it cures but when not to reach for it.

The map: antipattern → pattern

Here's the whole article in one table. Find your Part 3 antipattern on the left; the cure is on the right. The rest of the post explains each cure.

Part 3 antipatternPart 5 pattern(s) that cure it
No CachingCache-Aside, Materialized View
Retry StormCircuit Breaker, Retry (with backoff)
Noisy NeighborBulkhead, Throttling
Busy Front End / Synchronous I/OQueue-Based Load Leveling, Competing Consumers
Chatty I/OGateway Aggregation
Monolithic PersistenceCQRS, Sharding, polyglot persistence
Busy Database / Extraneous FetchingMaterialized View, CQRS
(microservice data consistency)Saga
(monolith → microservices migration)Strangler Fig

Resilience patterns — surviving failure

Retry (with backoff)

What it is. When a call fails with a transient error, try again — but politely: exponential backoff, jitter, a capped number of attempts, and only for retryable errors. We covered the discipline in Part 2; it's listed here because it's the most basic resilience pattern and the foundation the Circuit Breaker builds on.

Cures: ordinary transient faults. Don't: retry non-idempotent operations without an idempotency key, retry at every layer (the nested-retry multiplier), or retry without backoff — that's how Retry becomes Retry Storm.

Circuit Breaker — stop hitting what's already down

What it is. A state machine that wraps calls to a flaky dependency. When failures pile up, it "trips" and stops calling for a cool-off period — failing fast instead of piling on. It has three states:

CLOSED calls pass through OPEN fail fast, don't call HALF-OPEN let a few trials through too many fails cool-off elapsed trials succeed → reset trial fails → re-open

Fig 1 — The Circuit Breaker's three states. Open = stop calling and let the service recover. Half-Open = cautiously test before fully resuming.

Cures: Retry Storm and cascading failure. It's the pattern that turns "a downstream blip becomes a full outage" back into "a brief blip." The classic example: an e-commerce site whose payment gateway goes slow — the breaker trips, the site fails fast (or falls back to another gateway) instead of hanging every checkout thread and taking the whole site down. Netflix's Hystrix made this pattern famous. Don't: set the thresholds blindly — too sensitive and it trips on normal blips; too lax and it never protects. And always pair it with a sensible fallback (cached data, a default, a clear error) for when it's open.

Bulkhead — watertight compartments

What it is. Named after a ship's hull: watertight compartments so that if one floods, the ship doesn't sink. In software, you partition resources — separate thread pools, connection pools, or queues per dependency or per tenant — so that one overloaded component can't starve the others. If the "recommendations" service hangs and eats all its threads, the "checkout" service has its own pool and keeps working.

Cures: Noisy Neighbor and cascading resource exhaustion. It shows up everywhere in production: separate thread pools per downstream dependency, distinct DB connection pools for transactional vs. analytical work (the Monolithic Persistence cure too), per-tenant quotas, payment operations isolated from non-critical features. Don't: over-partition — too many tiny pools waste resources and can leave each one too small to handle its own legitimate spikes. Partition along real failure boundaries.

Throttling — enforce a fair share

What it is. Cap how much any one client, tenant, or operation can consume — rate limits, quotas, request ceilings. When a limit is hit, you reject or queue the excess (often returning 429 Too Many Requests) rather than letting one consumer take everything.

Cures: Noisy Neighbor (the other half, alongside Bulkhead) and protects you from traffic spikes and abuse. Don't: set limits so tight they reject legitimate bursts — pair throttling with autoscaling so real growth is absorbed, not blocked.

Scaling & decoupling patterns — handling load

Queue-Based Load Leveling — the shock absorber

What it is. Put a queue between the part of the system that receives work and the part that does it. The front end drops a message and returns immediately; workers drain the queue at their own steady pace. A traffic spike fills the queue instead of crashing the workers — the queue absorbs the burst and smooths it into a steady flow.

Cures: Busy Front End and Synchronous I/O — the most common pair of antipatterns. It's also the heart of the Web-Queue-Worker style from Part 1. Don't: use it for work that genuinely must be synchronous (the user is waiting on the answer right now) — you'd just add latency and a "where's my result?" problem.

Competing Consumers — scale the workers

What it is. The natural partner to the queue: run multiple worker instances all pulling from the same queue. The queue hands each message to whichever worker is free, so adding workers linearly increases throughput, and a dead worker just means its messages go to the others.

Cures: throughput bottlenecks on background work; gives you independent, elastic scaling of the worker tier. Don't: assume message order — with many consumers, messages can be processed out of order, so design for it (or use ordered/partitioned queues where order matters).

Gateway Aggregation — one call, not many

What it is. Put a gateway in front of your services that, for a single client request, fans out to the several backend services it needs, combines the results, and returns one response. The client makes one call over the (slow, high-latency) internet instead of a dozen.

Cures: Chatty I/O — especially the client-to-microservices chattiness that mobile apps suffer from on slow networks. Don't: let the gateway become a dumping ground for business logic (a new monolith), and watch that one slow backend doesn't stall the whole aggregated response (pair with timeouts and Circuit Breaker).

Data patterns — managing state

Cache-Aside — the caching workhorse

What it is. The standard caching pattern. On a read: check the cache first; on a hit, return it; on a miss, load from the source, put it in the cache, and return it. Writes update the source and invalidate (or update) the cache entry. The application manages the cache "on the side" of the data store.

Cures: No Caching, and relieves Busy Database. It's the first optimization to reach for on read-heavy, change-rarely data. Don't: cache data that changes constantly or must be perfectly fresh, and remember Part 3's Cache Stampede — add expiry jitter so hot keys don't all expire at once.

CQRS — split reads from writes

What it is. Command Query Responsibility Segregation: use separate models for writing data (commands) and reading it (queries). Often separate data stores too — a normalized write store and one or more denormalized read stores optimized for exactly the queries your screens need. Reads and writes scale and optimize independently.

Cures: Monolithic Persistence and Busy Database — the read/write contention where heavy reporting queries fight transactional writes on one store. Don't: reach for it by default. CQRS is genuinely complex (two models to keep in sync, usually with eventual consistency between them). Use it where read and write loads are very different or very high — not on a CRUD app that a single table serves fine.

Materialized View — precompute the answer

What it is. Instead of computing an expensive query (joins, aggregations) on every read, precompute the result and store it as a ready-to-serve "view," refreshing it as the underlying data changes. The read becomes a simple lookup.

Cures: Busy Database and Extraneous Fetching — the cases where you're repeatedly doing heavy computation or pulling lots of rows to produce a small answer. Don't: use it for data that must be real-time exact — a materialized view is, by nature, slightly behind. Accept the staleness or refresh more often (at a cost).

Sharding — split the data horizontally

What it is. Partition a large dataset across multiple databases (shards) by a key — customer ID, region, tenant. Each shard holds a slice, so no single database holds (or is hammered by) everything. It's how you scale writes and storage past one machine.

Cures: Monolithic Persistence and the "database is the wall" problem from Part 2. Don't: shard before you must — it complicates queries that span shards, and choosing a bad shard key creates its own Noisy-Neighbor-style hotspots. Plan the shard key carefully; re-sharding live is brutal.

Saga — distributed transactions without distributed locks

What it is. In microservices, each service owns its own database, so you can't wrap a multi-service operation in one ACID transaction. A Saga breaks the operation into a sequence of local transactions, one per service, each with a compensating action that undoes it. If step 4 fails, you run the compensations for steps 3, 2, 1 — so the system ends either fully done or fully undone, never half-finished.

Cures: the data-consistency problem that microservices (Part 1) create. Don't: use it inside a single service where a normal database transaction works — Sagas are far more complex, and you only want that complexity when a real transaction is genuinely impossible (i.e., across service/database boundaries).

Migration pattern — getting there safely

Strangler Fig — evolve, don't rewrite

What it is. Named after a vine that grows around a tree and gradually replaces it. To modernize a monolith (or any legacy system), you wrap it and peel off one capability at a time into new services, routing traffic to the new piece as each lands, until the old system is fully "strangled" and can be removed. No big-bang rewrite.

Cures: the risk of monolith→microservices migration (Part 1). The big-bang rewrite is one of the most reliable ways to kill a project; the Strangler Fig makes the migration incremental, reversible, and value-delivering the whole way. Don't: forget to actually finish — half-strangled systems that run both old and new indefinitely are their own kind of mess. Have a plan to retire the old core.

How to combine them (without cargo-culting)

Patterns compose — real systems use many together. A robust service call to a downstream dependency might stack: a Bulkhead (its own thread pool) wrapping a Circuit Breaker wrapping a Retry with backoff, falling back to Cache-Aside data when the breaker is open, behind a Gateway that aggregates it with other calls, with Throttling at the edge. That's not over-engineering — each layer handles a distinct failure mode.

But the discipline from the intro holds: add a pattern when you have the problem it solves, not before. The failure mode of this whole topic is the engineer who read the catalog and now wants CQRS, Sagas, and an event-sourced gateway for a CRUD app with forty users — recreating the "premature microservices" mistake from Part 1, one pattern at a time. Patterns are tools. The skill is knowing which problem you actually have.

The decision rule for any pattern: name the specific problem (ideally one of Part 3's antipatterns) you're seeing in production or can clearly predict. If you can't name it, you don't need the pattern yet. When you can — the cure is in the table above, and now you know how it works and what it costs.

FAQ

What's the difference between Retry and Circuit Breaker?

Retry handles a single transient failure by trying again. Circuit Breaker handles a persistent failure by stopping calls entirely so you don't pile on a service that's down. You use them together: retry the occasional blip, but if failures persist, the breaker trips and stops the retries from becoming a storm.

Bulkhead vs Throttling — aren't they the same?

Related but different. Bulkhead isolates resources (separate pools) so one consumer's overload can't starve others. Throttling caps how much any consumer can use (rate limits). Bulkhead contains the damage; throttling prevents the over-consumption in the first place. Together they cure Noisy Neighbor.

Is CQRS the same as having a read replica?

No. A read replica is a copy of the same model for read scaling. CQRS uses a different model for reads — often denormalized and shaped for specific queries — kept in sync from the write side. Read replicas are simpler; reach for full CQRS only when read and write needs genuinely diverge.

How many of these should a normal app use?

Fewer than you'd think. A typical well-built app uses Cache-Aside, Retry, and Queue-Based Load Leveling, and adds Circuit Breaker and Bulkhead when it depends on flaky downstreams. CQRS, Sagas, and Sharding are for specific high-scale or microservices situations. Don't collect patterns — solve problems.

Takeaways

  • Patterns are named cures. Every antipattern from Part 3 has a documented pattern that fixes it — that's the map at the top of this article.
  • Resilience: Retry, Circuit Breaker, Bulkhead, Throttling. Retry the blip; trip the breaker on persistent failure; isolate with bulkheads; cap with throttling.
  • Load: Queue-Based Load Leveling + Competing Consumers, Gateway Aggregation. Decouple with queues, scale workers, collapse chatty calls at a gateway.
  • Data: Cache-Aside, CQRS, Materialized View, Sharding, Saga. Cache the hot reads, split reads from writes when they diverge, precompute, partition, and use sagas for cross-service consistency.
  • Every pattern has a cost. Apply one when you can name the problem it solves — usually a Part 3 antipattern — and not a moment before.

That's the series

Five parts, one arc. You picked a shape (architecture styles), learned the habits that survive load (best practices), learned to spot the traps (antipatterns), made reliability, security, cost, and sustainability first-class (responsible engineering), and now have the cures (design patterns) for every trap. That's a complete mental model for designing well in the cloud — from the first whiteboard box to the 3am page and back.

None of this is about memorizing a catalog. It's about a way of thinking: know the shape you're building, expect failure as the steady state, recognize the traps by name, make your trade-offs on purpose, and reach for the documented cure when you have the documented problem. Do that, and the cloud stops being a place where things mysteriously fall over at scale — and becomes a place you can build on with confidence. Thanks for reading the whole way.

References

Extra reads

← prev: responsible engineering restart: the series →
© cvam — written in plaintext, served warm