resilience-patterns
Skill8 starsChanged 9 days agoMITView on GitHub
Makes services survive slow and failing dependencies - timeouts and deadline propagation, safe retries with backoff and jitter, retry budgets, circuit breakers, bulkheads, load shedding, backpressure, idempotent consumers, transactional outbox, sagas, health checks and graceful shutdown. Use when adding or reviewing calls to other services, databases, queues or third-party APIs, designing message consumers, fixing cascading failures or retry storms, or writing health probes and shutdown logic.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- abhisheksharma2411/distributed-systems-skillstheir own version, first seen Aug 27, 2026
- dbtinoy-/lexigram-framework-skillstheir own version, first seen Aug 24, 2026
- tygwan/skillstheir own version, first seen Nov 12, 2025
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- abhisheksharma2411/distributed-systems-skills★ 1same name
- dbtinoy-/lexigram-framework-skills★ 1same name
- tygwan/skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository60 other files it gives its agents.
- api-designSkill
- auth-implementationSkill
- background-jobsSkill
- building-agentsSkill
- caching-strategiesSkill
- ci-cd-pipelinesSkill
- cloud-cost-optimizationSkill
- codebase-onboardingSkill
- code-review-changesSkill
- concurrency-patternsSkill
- database-migrationsSkill
- database-schema-designSkill
- database-testingSkill
- data-pipelinesSkill
- data-privacy-retentionSkill
- dependency-upgradesSkill
- design-docsSkill
- design-systemsSkill
- dockerfilesSkill
- domain-driven-designSkill
- event-driven-architectureSkill
- file-storage-uploadsSkill
- frontend-securitySkill
- git-workflowSkill
- graphql-apisSkill
- grpc-servicesSkill
- incident-responseSkill
- infrastructure-as-codeSkill
- internationalizationSkill
- kubernetes-deploymentsSkill
- large-scale-migrationsSkill
- legacy-refactoringSkill
- llm-evalsSkill
- llm-integrationSkill
- mcp-serversSkill
- micro-frontendsSkill
- ml-experimentsSkill
- monorepo-managementSkill
- nosql-designSkill
- observabilitySkill
- orm-patternsSkill
- performance-profilingSkill
- postgres-operationsSkill
- rag-systemsSkill
- react-patternsSkill
- realtime-apisSkill
- root-cause-debuggingSkill
- search-integrationSkill
- security-auditSkill
- seo-and-ssrSkill
- sql-optimizationSkill
- system-designSkill
- test-strategySkill
- threat-modelingSkill
- typescript-patternsSkill
- unit-testingSkill
- verify-before-doneSkill
- web-accessibilitySkill
- web-performanceSkill
- writing-skillsSkill
The file
We split this skill into the rules it gives an agent
8 rules. 0 are common, 8 are distinctive, and 0 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 9
Every remote call can be slow, fail, or succeed without you knowing.
- Distinctive: line 18
Not for: in-process error handling with no I/O, or choosing a message broker.
- Distinctive: line 36
Every network call has a timeout.
- Distinctive: line 66
Open: after the failure rate passes a threshold (with a minimum number of calls), fail immediately for a cool-down period.
- Distinctive: line 105
Keep probes cheap, and give them their own timeout.
- Distinctive: line 110
Stop accepting new connections and messages.
- Distinctive: line 125
If a mesh already retries, do not add application-level retries on the same calls.
- Distinctive: line 140
Every outbound call has explicit connect and request timeouts that fit inside the caller's deadline.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Resilience Patterns
Every remote call can be slow, fail, or succeed without you knowing. Bound every wait, retry only what is safe to repeat, and make sure one failing dependency cannot take the whole system down with it.
When to use
- Adding or reviewing code that calls another service, a database, a cache, a queue or a third-party API.
- Writing a message or event consumer.
- Investigating cascading failures, thread or connection pool exhaustion, retry storms, or duplicate side effects.
- Writing liveness/readiness probes or shutdown handling.
Not for: in-process error handling with no I/O, or choosing a message broker.
Workflow
- Inventory the dependencies. For the code in scope, list every outbound call: target, protocol, client library, whether the operation is idempotent, and what the caller does if it fails.
- Check timeouts first. Find each client's connect timeout and request/read timeout. Many clients default to no timeout (for example Python
requestsand Go's zero-valuehttp.Client). Set explicit values derived from the caller's own deadline. - Classify failures. For each call, mark which errors are transient (connection reset, 503, 429, gRPC
UNAVAILABLE) and which are permanent (4xx validation, auth, 404). Only transient errors are retry candidates. - Decide retries per call with the table below. Retry at one layer only.
- Add isolation: a circuit breaker for dependencies that can fail hard, bulkheads (separate pools or concurrency limits) so one slow dependency cannot use up shared threads or connections.
- Protect yourself: bounded queues, concurrency limits and load shedding at the entry point.
- Make side effects idempotent where delivery is at-least-once (queues, webhooks, retries). Use the outbox when a database write and a message publish must both happen.
- Define degraded behavior for each dependency: cached value, default, partial response, or fail fast with a clear error.
- Verify with tests that inject latency and errors (a fake server, a fault-injection proxy such as Toxiproxy, or your service mesh's fault injection). Watch for behavior, not just "no exception".
Pseudo-code for each pattern is in references/patterns.md. Read it when implementing one.
Timeouts and deadlines
- Every network call has a timeout. No exceptions, including database queries and cache reads.
- Set the connect timeout short (typically well under a second inside a data center) and the request timeout from the latency SLO of the dependency (for example a little above its p99.9), not a round number someone guessed.
- Propagate deadlines. If the incoming request has 800 ms left, a downstream call must not get a 2 s timeout. gRPC propagates deadlines through the context. Over HTTP, pass the remaining budget explicitly (a header your services agree on) and use your language's cancellation mechanism (Go
context, .NETCancellationToken, JSAbortSignal). - An inner timeout must be shorter than the outer one. Otherwise the caller gives up while work continues for nothing.
- Cancel abandoned work when the caller disconnects or the deadline passes.
Retries
| Situation | Retry? |
| Idempotent read (GET, SELECT) failing transiently | Yes, with backoff and jitter |
| Idempotent write (PUT, DELETE, upsert by key) failing transiently | Yes |
| Non-idempotent write (POST create, charge, send) | Only with an idempotency key the server honors |
| 4xx validation, 401, 403, 404, 409 | No; it will fail the same way |
429 or 503 with Retry-After | Yes, but wait at least Retry-After |
| Timeout on a non-idempotent write | Never blindly; the first attempt may have succeeded |
| Circuit breaker open | No; fail fast or degrade |
Rules:
- Exponential backoff with jitter, capped:
sleep = random(0, min(cap, base * 2^attempt))("full jitter"). Without jitter, clients retry in synchronized waves. - Small attempt counts: 2-3 total attempts is usual for synchronous calls. The user is waiting.
- Retry at one layer. Three layers each making 3 attempts turns one failure into 3 x 3 x 3 = 27 calls to the bottom service. Pick the layer closest to the failure (often the client library or the mesh) and disable retries elsewhere.
- Retry budgets: cap retries as a fraction of total traffic (for example around 10%), so that during an outage retries cannot multiply load. gRPC retry throttling and many meshes support this.
- Respect the overall deadline. Never retry past it.
- Log and count retries. A rising retry rate is an early warning.
Circuit breakers
- Closed: calls flow; failures are counted over a sliding window.
- Open: after the failure rate passes a threshold (with a minimum number of calls), fail immediately for a cool-down period.
- Half-open: let a few trial calls through; close on success, re-open on failure.
- One breaker per dependency (and per endpoint if they fail independently), never one global breaker.
- Count timeouts and 5xx as failures; do not count 4xx caused by the caller.
- Pair it with a fallback and expose the breaker state as a metric.
Bulkheads, load shedding, backpressure
- Bulkheads: a separate connection pool, thread pool or semaphore per dependency, sized to that dependency's capacity. A slow recommendations service must not use up the threads that serve checkout.
- Concurrency limits at the entry point: reject excess work early with 503 (or 429 for per-client limits) instead of queueing it until it times out. Adaptive concurrency limiters adjust the limit from observed latency.
- Prioritize: under overload, shed low-priority traffic (batch jobs, prefetch) before user-facing requests.
- Bounded queues everywhere. An unbounded queue hides overload until memory runs out, and every queued item is already late.
- Backpressure: consumers pull at their own rate (bounded prefetch on brokers, reactive streams), and producers slow down or drop when queues are full.
Idempotency and messaging
- Assume at-least-once delivery from every queue, webhook and retrying client. Consumers must be idempotent.
- Idempotent consumer: record the message ID in a processed-messages table with a unique constraint, in the same transaction as the side effect. Skip the message if the ID already exists.
- Natural idempotency is better when available: upserts by business key,
SET status = 'shipped' WHERE status = 'packed', absolute values instead of increments. - Transactional outbox: never write to the database and then publish to a broker as two independent steps, because a crash between them loses or duplicates events. Write the event to an
outboxtable in the same transaction as the state change, then a relay (polling, or change data capture such as Debezium) publishes it and marks it sent. Consumers still dedupe. - Sagas for multi-service workflows: a sequence of local transactions, each with a compensating action (refund, release, cancel). Orchestration (one coordinator) is easier to reason about than choreography (services react to each other's events) once there are more than about three steps. Compensations must themselves be idempotent and retryable.
- Poison messages: after N failed attempts, move the message to a dead-letter queue with the error, alert, and keep consuming.
Graceful degradation
For each dependency, write down the fallback before an incident forces the choice:
| Dependency down | Typical fallback |
| Recommendations, personalization | Static or popular defaults |
| Pricing or inventory read | Short-lived cached value, clearly marked stale |
| Search | Basic database query, or a clear "temporarily unavailable" |
| Payments, auth | Fail closed with a clear error; never fake success |
Health checks and shutdown
- Liveness: "is this process broken beyond recovery?" Check only the process itself (event loop responsive, no deadlock). Never check dependencies in liveness. If you do, a database outage makes the orchestrator restart every instance, which is a self-inflicted outage.
- Readiness: "should this instance receive traffic now?" Fail it while starting, while draining, and when a dependency it cannot work without is unreachable.
- Startup probe (Kubernetes) for slow-starting apps, so liveness does not kill them during boot.
- Keep probes cheap, and give them their own timeout.
Graceful shutdown on SIGTERM:
- Fail readiness so the load balancer stops sending new requests. In Kubernetes, endpoint removal is asynchronous, so keep serving for a few seconds (a
preStopsleep) before closing listeners. - Stop accepting new connections and messages; stop consumer polling.
- Finish in-flight requests and messages within a bounded time.
- Flush telemetry, close pools, and exit before the orchestrator's grace period ends (Kubernetes default
terminationGracePeriodSecondsis 30).
Libraries (check current docs before adding)
| Stack | Common choices |
| Java/Kotlin | Resilience4j (Hystrix is in maintenance mode; do not adopt it) |
| .NET | Polly; Microsoft.Extensions.Http.Resilience for HttpClient |
| Python | tenacity (retries); explicit timeout= on every client |
| Go | context deadlines; sony/gobreaker or failsafe-go |
| Node.js | AbortSignal.timeout(); cockatiel or opossum |
| Platform | Service mesh or proxy (Envoy, Istio, Linkerd) timeouts, retries, outlier detection |
If a mesh already retries, do not add application-level retries on the same calls.
Anti-patterns
- No timeout because "the service is fast" — it is fast until it isn't, and then every thread waits on it.
- Retrying everything — retrying 400s, retrying non-idempotent POSTs, retrying at every layer.
- Retries without jitter — synchronized retries knock the dependency over again as soon as it recovers.
- Liveness probe that checks the database — turns a database blip into a full restart storm.
- Dual writes (database commit, then publish) — lost or phantom events; use the outbox.
- Catch-and-ignore fallbacks that return wrong data silently (zero balance, empty cart) — degrade visibly.
- Unbounded in-memory queues and pools — they turn overload into out-of-memory crashes.
- Circuit breaker shared across all dependencies — one bad dependency blocks healthy ones.
Done when
- Every outbound call has explicit connect and request timeouts that fit inside the caller's deadline.
- Retries exist only for transient errors on idempotent (or idempotency-keyed) operations, use capped exponential backoff with jitter, and happen at exactly one layer.
- Hard-failing dependencies have a circuit breaker and a defined fallback.
- Queues and pools are bounded; overload is rejected early with 503/429.
- Consumers are idempotent; database writes paired with events use an outbox.
- Liveness checks only the process; readiness and shutdown drain cleanly.
- Fault-injection tests (latency, errors, dependency down) show the expected degraded behavior.
References
- references/patterns.md — pseudo-code for backoff, circuit breaker, idempotent consumer, outbox relay, saga and graceful shutdown. Read it when implementing any of them.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

