1.Timeouts, retries, backoff and jitter
A **timeout** is the longest you are willing to wait before giving up on a call. A **retry** is trying again after a failure. **Exponential backoff** doubles the wait between attempts, so a struggling service gets quieter traffic instead of more. **Jitter** adds randomness to that wait, so a thousand clients do not all come back at the same instant. Retrying is only safe when the call is idempotent — running it twice has the same effect as running it once.
Key features
- `requests` has no default timeout — an unbounded call ties up a worker forever
- Two timeouts, not one: connect (reach the server) and read (wait for bytes)
- A retry budget is total: attempts × per-attempt timeout must fit the request budget
- Retry timeouts, connection errors, 502/503/504 — never 400/401/403/404
- Backoff drops load on a struggling service; jitter stops the herd re-synchronising
- Retry only what is idempotent, or carry an idempotency key so the duplicate is free
What each failure class deserves
The four waiting strategies, on the same failing call
- Two timelines, both running from 0 to 8 seconds, each showing nine retry attempts from three clients that failed at the same moment.
- The top timeline, labelled "exponential backoff, no jitter", has three tall red bars, at 1 second, 2 seconds and 4 seconds. Each bar is three attempts high, because all three clients retry on exactly the same schedule.
- The bottom timeline, labelled "backoff plus full jitter", has nine short green bars scattered irregularly between about 0.4 and 6 seconds, one attempt high each.
- The two panels carry the same total: nine attempts. Only their arrival pattern differs.
- A note marks the top panel as the shape that knocks a recovering service over again.
Command
requests.post(url, json=payload, timeout=(3.05, 10)) # (connect, read)Every outbound call gets a timeout, because `requests` has none by default and a hung call holds a worker forever. Set connect and read separately, and size attempts × timeout to fit the request budget you actually have. Retry only transient failures — timeouts, connection errors, 502/503/504, and 429 with its `Retry-After` — never a 400 or a 404. Back off exponentially so a struggling service sees less traffic, and add jitter so every client does not come back at the same instant. And retry only what is idempotent: a read timeout does not tell you whether the work happened, so carry an idempotency key generated once and reused on every attempt.