AWS Builder Center
Designing for Failure: 6 AWS Reliability Patterns

Designing for Failure: 6 AWS Reliability Patterns

Learn how to build resilient AWS workloads using bounded retries, circuit breakers, bulkheads, idempotency, queues, load shedding, and practical failure testing.

Designing for Failure: 6 Reliability Patterns for AWS Workloads

The architecture that survives failure is usually the architecture that expected it.
At 10:02 AM, a downstream database starts responding slowly.
Nothing is technically “down.”
Requests just take longer.
Service C retries.
Service B retries Service C.
Service A retries Service B.
The API retries Service A.
Within minutes, the database that was merely struggling is drowning in requests created by the application's own recovery logic.
The outage wasn't caused by failure.
It was caused by the response to failure.
This is a composite scenario, but the mathematics are very real.
Amazon's Builders' Library gives a brutal example: in a five-layer service stack, if every layer independently makes three attempts, pressure on the database can multiply to 243 times the original load.
That is why experienced cloud engineers don't just design the happy path.
They design the failure path.
And on AWS, that means going beyond “deploy it across two Availability Zones.”
It means controlling retries.
Breaking circuits.
Isolating workloads.
Making operations idempotent.
Using queues as pressure buffers.
And rejecting load before the system destroys itself trying to accept everything.
Here are six patterns that turn a basic AWS workload into a system designed to fail safely.

Architecture diagram showing a client calling an API, Service A, Service B, Service C, and a database, with three attempts at each layer amplifying one request into as many as 243 database attempts.
Layered retries can amplify a small failure into a major outage. Bounded retries, backoff, jitter, and retry budgets help prevent retry storms.

Pattern 1: Timeouts, Bounded Retries, and Retry Budgets

Retries are useful.
Until everyone retries.
Then they become traffic.
Imagine a simple three-tier path:
API → Orders Service → Inventory Service → Database
Suppose each tier permits three total attempts.
At the first layer:
3 attempts
At two layers:
3 × 3 = 9 attempts
At three layers:
3 × 3 × 3 = 27 attempts
One customer request can potentially create 27 database attempts.
Scale that across 10,000 requests and “retry logic” starts looking suspiciously like a self-inflicted denial-of-service attack.

Start with deadlines

Every remote call needs a timeout.
Not:
Wait until the network decides what happens.
But:
This operation has 800 milliseconds to succeed.
AWS Well-Architected recommends explicitly setting client timeouts rather than assuming defaults are appropriate.
Your timeout should also respect the deadline of the layer above it.
If API Gateway will stop waiting before the backend finishes, letting the backend continue expensive work may accomplish nothing for the user.
For Lambda, function timeout is configurable. Standard Lambda functions can run for up to 900 seconds, although a longer timeout does not mean a longer timeout is architecturally sensible.
API Gateway REST integrations default to 29 seconds. Regional and private REST APIs can be configured beyond that limit, while HTTP APIs currently have a 30-second maximum integration timeout.
The question isn't:
“How high can I set the timeout?”
It is:
“How long is this operation worth waiting for?”

Then bound the retries

Current AWS SDKs provide configurable retry behavior.
For most workloads, AWS recommends standard retry mode.
Adaptive mode adds client-side rate limiting and can be useful for specialized throttling-heavy workloads, but it can also delay initial requests and requires careful client isolation.
A simple shared AWS configuration can make the policy explicit:
1
2
3
[default]
retry_mode = standard
max_attempts = 3
max_attempts = 3 means one original attempt plus up to two retries.
But there is another principle worth adding:

Give retries a budget

Suppose normal traffic is 10,000 requests per minute.
You might decide that retry traffic may consume no more than 10% of that normal volume.
Once the retry budget is exhausted, fail fast.
Why?
Because during an outage, capacity is scarce.
The worst moment to manufacture unlimited additional demand is the exact moment your system has the least capacity to handle it.
Modern AWS SDK retry behavior includes a retry quota mechanism for similar reasons: once retry capacity is exhausted, retries stop rather than continuously adding pressure.

Backoff without jitter isn't enough

This:
1 sec → 2 sec → 4 sec → 8 sec
is better than:
1 sec → 1 sec → 1 sec → 1 sec
But thousands of clients that all failed at 10:02:00 can still wake up together.
Jitter randomizes retry timing.
Instead of every client retrying at exactly four seconds, they spread themselves across a window.
Recovery gets breathing room.
Amazon explicitly recommends exponential backoff with jitter for this reason.
The lesson:
Retry at one carefully chosen layer whenever possible.
Do not let five layers independently decide that optimism is a reliability strategy.
Comparison diagram showing one user request multiplying into 3 API attempts, 9 service attempts, and 27 database attempts versus a safer retry flow using a single retry layer, backoff with jitter, a retry budget, and fail-fast behavior.
Retries should absorb failure, not amplify it. Centralizing retry logic and using backoff, jitter, and retry budgets helps prevent cascading load during outages.

Pattern 2: Circuit Breakers — Stop Calling What Is Already Broken
Consider a payment dependency returning 500 errors.
Your application knows three things:
The service failed.
It failed again.
And it failed again.
Then it asks:
Want another request?
That is not resilience.
That is denial.
A circuit breaker gives the system memory.
It usually moves through three states:
Closed: traffic flows normally.
Open: failures have crossed a threshold; calls are rejected immediately.
Half-open: a limited number of requests are allowed through to test whether recovery has occurred.
Libraries such as Resilience4j for JVM applications and Polly for .NET implement this stateful circuit-breaker model.

What does this look like on AWS?

AWS does not give every workload one universal “enable circuit breaker” checkbox.
You implement the pattern at the appropriate layer.
For application code, use libraries such as Resilience4j or Polly.
For ECS workloads, ECS Service Connect provides passive health checks through outlier detection, automatically avoids unhealthy hosts, performs limited connection retries, and supports request timeout settings.
For EKS, the pattern can live in your application or service-mesh/proxy layer.
For Lambda, you can maintain failure state in a fast external store such as DynamoDB or ElastiCache when centralized state is genuinely necessary.
One important 2026 update: don't build a new recommendation around AWS App Mesh. AWS discontinued App Mesh support on September 30, 2026. AWS has pointed ECS users toward ECS Service Connect and broader connectivity use cases toward options such as VPC Lattice.

The fallback matters as much as the breaker

Imagine recommendations are unavailable.
You can:
Wait 30 seconds.
Retry seven times.
Crash the product page.
Or display the product without recommendations.
Which system is more reliable?
The fourth.
Reliability isn't keeping every feature alive.
Reliability is protecting the feature the customer actually came to use.
A circuit breaker should therefore answer two questions:
When do we stop calling?
And what happens after we stop?
If the second question has no answer, the design is incomplete.

Diagram showing three circuit breaker states—Closed, Open, and Half-Open—with transitions based on failure threshold, recovery timer, success, and failure, plus AWS implementation options for Lambda, ECS, and EKS.
A circuit breaker protects downstream services by failing fast when errors spike, probing recovery carefully, and restoring traffic only when the dependency is healthy again.

Pattern 3: Bulkheads — Reduce the Blast Radius

Ships survive because they are divided into compartments.
One section floods.
The whole ship does not have to.
Cloud systems need compartments too.
Imagine your AWS account runs:
Checkout.
Search.
Recommendations.
Reporting.
All four share the same pool of workers.
Then someone runs a massive reporting workload.
It consumes every available worker.
Checkout stalls.
Search slows down.
Recommendations fail.
One non-critical workload has taken four systems with it.
That is not a capacity problem.
It is a failure-domain problem.

AWS gives you several ways to build bulkheads

For Lambda, use reserved concurrency to protect critical functions from noisy neighbors.
For queues, use separate SQS queues and separate consumer groups.
For ECS, separate services and scaling policies.
For databases, avoid letting unrelated workloads exhaust the same application connection pool.
For tenants, advanced systems may use cell-based architecture or shuffle-sharding techniques so one tenant's failure cannot consume the entire fleet.
The design principle is simple:
Shared capacity creates shared fate.
Checkout and analytics may technically be able to run on the same workers.
That doesn't mean they should.
Efficiency asks:
How much can we share?
Reliability asks:
What happens if the thing we share is exhausted?
Those are different questions.

Split-screen diagram comparing a fragile system where checkout, search, analytics, and reports share one overloaded worker pool versus a resilient system where each service has dedicated workers and only reports becomes overloaded.
Bulkheads reduce blast radius by isolating workloads. When each service has its own worker pool, one overloaded component does not take down the entire system.

Pattern 4: Idempotency — Make Duplicate Work Harmless

Distributed systems create uncertainty.
The client sends:
Create order 48291.
The server creates it.
Then the network connection dies before the success response reaches the client.
The client sees failure.
But the server succeeded.
So the client retries.
What happens now?
Without idempotency:
Order 48291.
Order 48291 again.
Maybe two charges.
Maybe two shipments.
Maybe one very unhappy customer.
With idempotency, the request includes a stable key.
The second attempt says:
Process operation ORDER-48291.
The system sees that key already completed and returns the original result.
No duplicate side effect.

AWS implementation

For Lambda workloads, Powertools for AWS Lambda includes an idempotency utility that can persist request state and previously successful results. Its documented persistence options include DynamoDB and supported cache backends depending on runtime.
A common DynamoDB design uses:
idempotency_key
status
result
expiration
with TTL cleaning up records after the idempotency window.
Conditional writes help guarantee that two concurrent workers cannot both claim the same new operation.
SQS FIFO queues also support MessageDeduplicationId, preventing another message with the same ID from being delivered within the queue's five-minute deduplication window.
But FIFO deduplication does not remove the need for idempotent business logic.
Infrastructure can reduce duplicates. Your application must survive them.

Lambda + SQS has another trap

By default, if one record in an SQS batch fails, successfully processed records can become visible again with the failed record.
That creates unnecessary duplicate processing.
Enable partial batch responses using:
ReportBatchItemFailures
Then Lambda can report only the records that actually failed. AWS Powertools also includes batch-processing support for this pattern.
This is one of those small configuration choices that separates:
“We use SQS.”
from:
“We understand how SQS fails.”

Diagram showing an order request checked against an idempotency key. New requests are conditionally written to DynamoDB, processed, and stored, while duplicate requests return the stored result. A side panel compares full SQS batch retries with ReportBatchItemFailures, where only failed messages are retried.
Idempotency makes duplicate requests harmless, while partial batch responses prevent one failed SQS message from forcing successful Lambda records to be processed again.

Pattern 5: Queues, Backpressure, and Dead-Letter Queues

Synchronous architectures are attractive because they look simple.
Customer places order.
Order service calls inventory.
Inventory calls warehouse.
Warehouse calls email.
Email calls analytics.
Everything happens immediately.
Until email is slow.
Then checkout is slow.
A customer cannot buy a product because the marketing email provider had a bad afternoon.
That is coupling.
Ask a harder question:
What absolutely must happen before I tell the customer the order succeeded?
Usually fewer things than you think.
Perhaps the critical path is:
Customer → API → Validate → Persist Order → Confirmation
Then everything else becomes asynchronous:
Order Event → SQS/EventBridge
From there:
Email consumer.
Warehouse consumer.
Analytics consumer.
Loyalty consumer.
One dependency can fall behind without freezing the transaction.

But queues don't eliminate load

They move it through time.
That means queue depth becomes a reliability signal.
When consumers slow down, ApproximateNumberOfMessagesVisible grows.
AWS recommends monitoring queue backlog and message age because sustained growth can indicate consumers cannot keep pace.
That metric can feed autoscaling.
But scaling consumers infinitely is not always the right response.
If consumers write into an already overloaded database, scaling from 50 consumers to 500 simply moves the outage downstream faster.
That is why backpressure matters.
Sometimes producers need to slow down.
Sometimes consumers need capped concurrency.
Sometimes low-priority work must wait.
A queue is not a magic pipe.
It is a pressure gauge.

Watch the SQS visibility timeout

With Lambda consuming SQS messages, AWS recommends configuring visibility timeout to at least six times the Lambda function timeout, plus the batch window when one is configured.
Otherwise, a still-running message can become visible and be processed again.

And design the dead-letter path

Some messages will never succeed.
Malformed input.
Deleted customer.
Bad dependency.
Unexpected code path.
Do not retry poison messages forever.
SQS redrive policies use maxReceiveCount to determine how many receives occur before a message moves to a DLQ. AWS also supports redriving messages from the DLQ when you're ready to process them again.
But remember:
An unmonitored DLQ is not reliability.
It is a quiet graveyard.
Create a CloudWatch alarm on the DLQ's:
ApproximateNumberOfMessagesVisible
A value above your normal baseline should create an actionable alert. AWS explicitly recommends this metric for monitoring SQS DLQs.

Diagram comparing a synchronous order flow where an email failure breaks checkout downstream versus a decoupled architecture where an order is saved, the user is confirmed immediately, and SQS distributes work to email, warehouse, analytics, and loyalty services while CloudWatch monitors queue pressure.
Queues decouple critical transactions from downstream processing, making pressure visible through backlog and age metrics instead of letting one failing dependency block the entire user flow.
Pattern 6: Load Shedding — Sometimes Reliability Means Saying “No”
This is the pattern many systems learn only after an outage.
When the system can safely serve 5,000 requests per second and receives 20,000, there are two options.
Reject some requests early.
Or accept everything and fail slowly.
The second option feels friendlier.
It usually produces a worse outage.
Load shedding means deliberately refusing excess work before it consumes scarce downstream capacity.
API Gateway supports throttling using rate and burst targets and returns 429 Too Many Requests responses when throttling is applied.
Usage plans can define throttling and quotas for API consumers, although AWS warns that these should not be treated as absolute hard limits.
AWS WAF rate-based rules can also rate-limit requests according to aggregation criteria such as IP address and other request characteristics.
This is where reliability and security meet.
The same controls that protect your backend from accidental overload can also help absorb abusive traffic.
WAF.
Rate limits.
Concurrency limits.
Queue boundaries.
Circuit breakers.
Retry budgets.
Different controls.
Same principle:
Protect finite capacity.
A system that rejects 5% of non-critical traffic while preserving checkout is often healthier than a system that accepts 100% and successfully completes 0%.
Graceful degradation is a business decision expressed through architecture.

Split-screen diagram comparing a system with no load shedding, where 20,000 requests per second overload the application and database, with a controlled-load design where API Gateway, WAF, and rate limits accept 5,000 requests and reject excess traffic early to protect the core service.
Load shedding protects critical AWS workloads by rejecting excess traffic at the edge before downstream services become overloaded and fail for everyone.

Reliability Goes Beyond These Six Patterns

These patterns protect runtime behavior.
But a serious AWS reliability design goes further.

Design across failure domains

Run critical workloads across multiple Availability Zones where the service supports it.
More importantly, design for static stability.
AWS defines static stability as maintaining a normal operating mode during failure rather than depending on successfully creating new resources after the failure begins.
If one Availability Zone disappears, your plan should not begin with:
Hopefully we can provision all the missing capacity somewhere else.
The surviving capacity should already be able to support the required workload.

Know your RTO and RPO

Reliability is not “high availability” in the abstract.
The business should define:
RTO — Recovery Time Objective
How long can the workload remain unavailable?
RPO — Recovery Point Objective
How much data loss is acceptable?
AWS Well-Architected recommends defining both according to business impact rather than choosing arbitrary targets.
Those numbers change architecture.
A service with a 24-hour RTO does not need the same design as one with a 30-second RTO.
Architecture should follow economics.
Not ego.

Layered diagram protecting a critical AWS workload with six reliability controls: edge protection using WAF and throttling, call protection with timeouts and retry budgets, dependency circuit breakers, compute bulkheads and concurrency isolation, event idempotency with SQS and DLQs, and Multi-AZ infrastructure with static stability.
Reliable AWS workloads use multiple layers of protection so failures are contained at the edge, dependency, compute, event, and infrastructure levels instead of reaching the critical core.

If You Never Test Failure, You Don't Have Reliability

A diagram can lie.
Terraform can lie.
A runbook can lie.
Production eventually tells the truth.
So inject the failure yourself.
AWS Fault Injection Service can run controlled fault-injection experiments, and AWS Well-Architected recommends chaos engineering and regular game days to validate resilience and incident response.
Test questions like:
What happens if one Availability Zone becomes unavailable?
What happens if the database adds 500 milliseconds of latency?
What happens if a third-party API returns 500s?
What happens when SQS backlog grows 50x?
What happens when an auth dependency fails?
Does the circuit actually open?
Does the fallback actually work?
Does the CloudWatch alarm actually wake somebody up?
Does the DLQ actually get investigated?
A failure mechanism you've never exercised is just a theory.

Observe the system while you break it

At minimum, watch:
Latency.
Request rate.
Error rate.
Saturation.
Throttling.
Retry volume.
Queue depth.
Age of oldest message.
DLQ depth.
Lambda concurrency.
Dependency errors.
Use CloudWatch alarms and dashboards.
Use distributed tracing where appropriate, including AWS X-Ray-compatible tracing and OpenTelemetry-based tooling.
Then connect those measurements to SLOs and error budgets.
Because “99.9% availability” sounds impressive until nobody agrees on what counts as available.
Circular game day diagram showing six reliability-testing steps: define failure, inject faults with AWS FIS, observe CloudWatch and tracing, measure SLO impact, fix weaknesses, and run the experiment again until failure becomes routine and predictable.
Game days turn resilience from theory into practice by repeatedly injecting failures, measuring impact, fixing weak points, and validating the system again.

A Production AWS Reliability Checklist
Before calling a workload production-ready, answer these questions:
  • Does every remote call have a deadline?
  • Are retries capped and concentrated at the right layer?
  • Are retries using backoff and jitter?
  • Is retry traffic itself limited?
  • Can unhealthy dependencies be temporarily bypassed?
  • Do critical workloads have isolated capacity?
  • Are mutating operations idempotent?
  • Can duplicate events be processed safely?
  • Are Lambda/SQS visibility timeout and batch behavior configured deliberately?
  • Are poison messages moved to a DLQ?
  • Does DLQ depth trigger an alarm?
  • Can queue backlog drive scaling without overwhelming downstream systems?
  • Can the edge throttle or shed excess load?
  • Can non-critical features degrade without taking down critical ones?
  • Does the workload tolerate an Availability Zone failure without depending on emergency provisioning?
  • Are RTO and RPO explicit?
  • Have you actually tested these assumptions with failure injection?
If several answers are “no,” the workload may be highly available on a diagram.
That doesn't mean it is resilient.

The Principle Behind Every Pattern

Retries.
Circuit breakers.
Bulkheads.
Idempotency.
Queues.
Backpressure.
Load shedding.
Multi-AZ design.
Chaos testing.
They sound like different techniques.
They are really expressions of one idea:
Failure should lose energy as it moves through the system.
Bad architectures amplify failure.
One timeout becomes three retries.
Three retries become 27 calls.
A slow dependency becomes exhausted workers.
Exhausted workers become an unavailable API.
A broken recommendation engine becomes a broken checkout.
Good architectures do the opposite.
They contain.
They isolate.
They delay.
They reject.
They degrade.
They recover.
Reliability is not the absence of failure.
It is the refusal to let one failure become everybody's failure.
That is the difference between software that runs on AWS and software that is designed for AWS-scale failure.
Which of these patterns have you seen save—or take down—a production system?
Open to Cloud Ops Remote and technical writing projects.

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article