Retry Logic Basics
Retry logic decides what a workflow does after an error, such as a timeout, a transient network failure, or a temporary upstream outage. A retry usually includes a delay and sometimes a backoff strategy, so repeated attempts do not hammer the same dependency.
In practice, retries show up in job runners, message consumers, and API clients. For example, a workflow that books an appointment might retry when the scheduling service returns a 503, but it should not retry when the request fails validation due to a missing patient identifier. A common pattern is: retry only for errors that are likely to resolve on their own, then escalate to a human review or a compensating action.
One small detail that changes outcomes: many systems cap retries with a maximum total time window, not just a retry count. If you set “5 retries” but each retry waits 2 minutes, the workflow can still sit for 10 minutes before it gives up, which affects user experience and downstream queues.
Common Retry Pain Points
Teams often pick a retry count by habit, then discover the workflow either gives up too early or keeps retrying during an outage. The failure mode depends on how errors are classified and how the workflow handles idempotency.
A frequent mistake is treating every error as retryable. HTTP 4xx responses usually indicate a client-side problem, and retrying them wastes time and can create repeated side effects if the request partially succeeded. Another dependency issue appears when the workflow calls multiple services; a retry may re-run earlier steps that already completed, which can duplicate records unless the workflow is idempotent.
Idempotency keys, deduplication tables, and “check-before-write” logic matter more than the retry count. Without them, “retry 3 times” can still create three bookings, three lab orders, or three claim submissions. I have seen teams set retries in a config file (for example, a YAML setting in version 2.1.0 of an internal runner) and later forget that the workflow also retries at the message-queue layer, so the effective retry count multiplies.
Another pain point is retry storms. If many workflow instances retry at the same fixed delay, they can synchronize and overload the dependency again. Jitter—randomizing the delay—reduces synchronization, but it also makes testing harder because timing becomes nondeterministic.
How To Choose Retry Limits
Choose retry limits by separating transient failures from permanent failures, then bounding the total retry time and side effects. A practical approach starts with error taxonomy: network timeouts, connection resets, and 5xx responses are often transient; validation errors, authorization failures, and malformed payloads are usually permanent.
Next, decide what “success” means for each step. If the workflow writes to a database, retries should not create duplicates. If the workflow triggers an external action, retries should either be idempotent or be guarded by a deduplication key that the external system understands.
Finally, set a maximum retry budget. Many teams use a small retry count for user-facing operations and a larger budget for background jobs, but the number should be derived from the expected recovery time of the dependency and the workflow’s tolerance for delay. When the dependency’s recovery time is unknown, a conservative time cap prevents long queue buildup.
Retry Only Transient Errors
Define retryable conditions at the boundary where the error is observed. For API calls, retry on timeouts and selected 5xx codes, and avoid retrying on 4xx codes that indicate the request will not succeed without changes. For message processing, retry on transient downstream failures, but route permanent failures to a dead-letter queue or a “needs review” state.
Use structured error signals when available. For example, if a service returns a “retry-after” header, honor it instead of guessing a delay. If the service does not provide guidance, a conservative default delay with backoff and jitter is safer than immediate retries.
Outcome target: the workflow should recover from brief outages without duplicating side effects, and it should stop retrying quickly enough that queues and operators can see the failure pattern.
Pick A Retry Budget, Not Just Count
Set both a maximum retry count and a maximum total elapsed time. A time cap prevents a workflow from waiting too long when the dependency remains down. For example, a background workflow might allow up to 10 minutes total retry time, while a user-facing action might allow 1–2 minutes.
Backoff typically increases delay after each failure. A common pattern is exponential backoff with jitter, where each retry waits longer than the previous one but includes randomness to avoid synchronized retries. If you use a fixed delay, jitter becomes even more important because many instances will otherwise retry in lockstep.
Small aside: in some orchestrators, the retry delay is configured in milliseconds but the logs display seconds, which can hide misconfigurations during incident review. I have seen a “5000ms” delay treated as “5s” in dashboards while the actual value was “500ms,” causing more load than intended.
Make Retries Idempotent
Design each retryable step so repeated attempts do not create repeated outcomes. For database writes, use unique constraints and “upsert” patterns keyed by a workflow run identifier. For external calls, use idempotency keys if the API supports them, or store a local record of “attempted” actions and check it before re-sending.
When idempotency is not possible, reduce the blast radius by limiting retries and adding compensating actions. For example, if a workflow triggers a third-party action that cannot be deduplicated, you may need fewer retries and a manual reconciliation step after failure.
Outcome target: after a retry, the system state should match the state after a single successful attempt, not “one plus N duplicates.”
Test With Failure Injection
Validate retry behavior under controlled failures. Use failure injection to simulate timeouts, 503 responses, and partial downstream success. Confirm that the workflow transitions to the expected state after the retry budget expires and that it does not re-run non-idempotent steps.
Track metrics such as retry count distribution, time-to-success, and dead-letter rate. If you see a high dead-letter rate during a dependency incident, the retry budget may be too small or the error classification may be too strict.
Outcome target: you should be able to explain, from logs and metrics, why a workflow stopped retrying and what it did next.
Case Examples For Retry Counts
Appointment Scheduling Workflow
An anonymized scheduling workflow calls a scheduling API to reserve a time slot. The API returns 503 during a maintenance window, and the workflow retries with exponential backoff starting at 2 seconds, capped at 5 retries or 2 minutes total. The workflow includes an idempotency key derived from the appointment request ID, so retries do not create multiple reservations.
When the scheduling service recovers, the workflow succeeds on the 3rd attempt. When the service remains down beyond the time cap, the workflow marks the request as “needs review” and notifies an operator, rather than continuing to retry indefinitely.
Background Claims Submission
An anonymized claims submission job reads pending claims from a queue and sends them to an external claims processor. The job retries on network timeouts and selected 5xx errors, using 8 retries with a total retry window of 30 minutes. The job writes a local “submission status” record keyed by claim ID so that a retry does not resubmit a claim that already reached “accepted.”
During a processor outage, the job exhausts its retry window and moves items to a dead-letter queue for reconciliation. During normal operations, most claims succeed within the first 1–2 attempts, and the retry metrics remain stable.
Retry Checklist And Table
| Scenario | Retryable Errors | Typical Retry Budget | Stop Condition |
|---|---|---|---|
| User-Facing Action | Timeouts, 5xx, rate-limit with guidance | 1–5 retries; 1–2 minutes total | Total time cap or non-retryable error |
| Background Job | Timeouts, transient 5xx, connection resets | 5–10 retries; 10–30 minutes total | Total time cap; then dead-letter or review |
| Non-Idempotent External Call | Only clearly transient failures | 0–3 retries; short time cap | First ambiguous outcome triggers review |
Use this checklist to decide retry count for a specific workflow step.
- Classify errors into retryable and non-retryable using status codes, exception types, and error payload fields.
- Confirm idempotency for every side-effecting step that can run more than once.
- Set a maximum total retry time that matches user tolerance or operational capacity.
- Add backoff with jitter to reduce retry synchronization during outages.
- Define the next action after retries end: dead-letter, manual review, or compensating workflow.
- Instrument metrics so you can see retry counts, time-to-success, and failure reasons.
Common Mistakes That Skew Retry Counts
One mistake is stacking retries across layers. A message consumer might retry on failure, while the API client also retries, and the workflow engine retries the whole step. The result is a higher effective retry count than the configuration suggests, which can amplify load and duplicates.
Another mistake is using a single retry policy for all steps. A workflow often mixes read-only calls, idempotent writes, and non-idempotent external actions. Applying the same retry budget to all of them increases the chance of repeated side effects.
Teams also mis-handle “partial success.” If a workflow writes to a database and then fails before it records completion, a retry may re-run the write. The fix is to record completion state before returning success, or to use idempotent writes keyed by a stable identifier.
Finally, some teams rely on logs alone. Without metrics, you may not notice that retries spike during specific dependency incidents, or that a small misconfiguration increases retry frequency by an order of magnitude.
FAQ
How Many Retries Should A Workflow Use?
Use a small retry count paired with a total time cap, then tune based on dependency recovery time and idempotency. User-facing steps often fit within 1–5 retries and about 1–2 minutes total; background jobs often use 5–10 retries and 10–30 minutes total.
Should I Retry On HTTP 4xx Errors?
Most 4xx responses indicate a client-side issue that will not resolve without changing the request, so retries usually waste time and can repeat side effects if the request partially succeeded.
What Delay Strategy Works Best?
Use exponential backoff with jitter for transient failures so retries spread out during outages. If a service provides a retry-after guidance, honor it instead of using only a fixed delay.
How Do I Prevent Duplicate Side Effects?
Make side-effecting steps idempotent using idempotency keys, unique constraints, or deduplication records keyed by a stable workflow identifier. Then verify that retries do not re-run non-idempotent actions.
When Should Retries Stop And Escalate?
Stop when the total retry time cap expires, when a non-retryable error occurs, or when the outcome becomes ambiguous. After stopping, route to dead-letter, manual review, or a compensating workflow.
Author's Insight
Retry count is a policy decision, not a universal number. The safest approach ties retry limits to error classification, idempotency, and a bounded retry time window so workflows recover without multiplying load or duplicating outcomes.
Evidence from distributed-systems practice consistently shows that retry storms and duplicate side effects come from retrying too broadly and from missing idempotency, not from using “too many” retries in isolation.
When you tune retries, measure time-to-success and dead-letter rate during controlled failure tests, then adjust the budget based on observed dependency behavior rather than guesswork.
Key Takeaways
- Retry only transient failures and stop on permanent errors.
- Set both retry count and total time budget to match the workflow’s tolerance.
- Make side effects idempotent so retries do not duplicate outcomes.
- Use backoff with jitter to reduce synchronized retry storms.
- Test with failure injection and monitor retry metrics so tuning stays grounded in evidence.