GraphQL Router Retry Mechanism | Cosmo by WunderGraph - WunderGraph

Recover from transient failures before your users see them

When a subgraph call hits a transient error, the router retries it with sensible spacing — and leaves mutations alone, so nothing gets duplicated. You decide when retries kick in and how many to allow.

The problem

Retries belong in one place with guardrails

Per-service retry logic and blind retries both break under load. The router fixes both.

Retries per service produce inconsistent behavior

Team A uses three attempts, Team B uses one, Team C forgot. The client experience depends on which subgraph happened to blip.

Naïve retries make outages worse

If every client retries the same failing service at the same interval, you get a retry storm: synchronized traffic waves that keep the service from recovering.

Retrying a mutation is dangerous

Mutations are not always idempotent. Retry the same charge operation twice and you have double-charged a customer. A retry layer that does not know queries from mutations is a liability.

Our solution

Transient failures retry automatically without duplicating mutations

Mutations are never retried.

  1. Cosmo Router automatically retries failed GraphQL queries using exponential backoff with jitter, configured once at the router, with expression-based conditions for when retries fire.

  2. When a subgraph request fails with a retryable condition, the router waits using the AWS-recommended Backoff and Jitter pattern, then tries again. Limits cap attempts, intervals, and total retry duration.

  3. Mutations are never retried. An expression evaluates each failure; defaults cover connection errors and HTTP 502/503/504. Extend with statusCode == 429 or boolean logic as needed.

Tradeoffs

Before & After

Before Cosmo With Cosmo
Retry logic scattered across subgraph services Centralized retry policy at the router
Fixed retry intervals causing retry storms Exponential backoff with jitter distributes retry load
All failures retried, including non-idempotent mutations Only queries retry; mutations never do
Hard to tune which errors should trigger a retry Expression-based conditions with built-in helpers

Expression helpers

Use these in retry expression strings; combine with ||, &&, and comparisons.

Function Returns true for
IsRetryableStatusCode() HTTP 500, 502, 503, 504
IsConnectionError() Connection refused, reset, DNS, TLS failures
IsTimeout() Any timeout (HTTP, network, deadline exceeded)
IsHttpReadTimeout() HTTP read timeouts specifically
IsConnectionRefused() ECONNREFUSED
IsConnectionReset() ECONNRESET

How the retry mechanism works

01

Evaluate the failure

When a subgraph request fails, the router evaluates the retry condition expression.

02

Wait with backoff and jitter

On a retry, the router waits for the configured interval. Each subsequent retry follows the pattern; jitter adds randomness so parallel retries do not align.

03

Retry the subgraph call

The retried request goes to the same subgraph. If it succeeds, the client sees the success.

04

Stop at the limit

If max_attempts or max_duration is reached without success, the router returns the final error to the client.

Use case patterns

When teams tune retries first

Same mechanism, different expressions for network blips, deploys, rate limits, and fine-grained exclusions.

Recovering from a network blip

Default retry catches connection errors via IsConnectionError(), waits with jitter, retries. Client never sees the error.

Riding out a deployment

Subgraph returns 503 during rolling deploy. IsRetryableStatusCode() includes 503. Up to five attempts over ten seconds usually lands on a healthy pod.

Honoring rate limits

Include statusCode == 429 in the expression and enable Retry-After handling. Router waits the requested amount and tries again.

Excluding slow business logic

Expression like !IsHttpReadTimeout() && IsTimeout() retries connection-level timeouts but skips HTTP read timeouts.

Cosmo vs generic HTTP retry vs service mesh

Centralized router retries stay GraphQL-aware and expression-driven without bolting policy onto every subgraph.

Aspect Cosmo Generic HTTP retry Service mesh
GraphQL-aware Yes (queries vs mutations) No No
Condition expressions Yes Usually no Limited
Backoff algorithm Jitter built in Varies Varies
429 Retry-After support Yes Varies Varies

Ship retries once at the router

Jitter beats storms. Expressions beat one-size-fits-all. Queries only, always.

FAQ

GraphQL router retries

Does the router retry mutations?
What errors does the default configuration retry?
Does the router retry on unexpected EOF?
Can I retry on 429 Too Many Requests?
What prevents retry storms during an outage?
How do I see which retries are happening?
Does retry interact with circuit breakers?

Full detail in the retry documentation.

SOC 2 Type II
HIPAA
ISO 27001