Keep retries under 10% of requests and throttle on the client when the backend accepts less than half of what you send, using the formula from Google's SRE book.
When a backend is overloaded, its clients make it worse: every rejected request is retried, and every retry is more load on the thing that is already failing (Week 2 preview, Concept 3). Backoff and jitter spread retries out (the retry-backoff-jitter lab); this lab caps how many there are and stops sending what the backend will only reject. Both mechanisms come from the "Handling Overload" chapter of Google's Site Reliability Engineering book:
requests (attempts made by the application, including ones it rejected itself) and accepts (attempts the backend accepted), and rejects new attempts locally with probabilityp = max(0, (requests − K × accepts) / (requests + 1))With K = 2 the client sends freely while the backend accepts at least half of what it sends; beyond that it drops the excess itself, so the backend spends nothing on requests it would have refused.
Implement OverloadClient(clock, rng, k=2.0, window=120, retry_ratio=0.1, max_attempts=3) with call(send), where send() makes one attempt and returns "ok" or "overloaded". Each attempt (attempt 0 is the first, 1 and 2 are retries) does this, using the counts in the window:
retries < retry_ratio × requests. Otherwise return "budget".p = max(0, (requests − k × accepts) / (requests + 1)).u = rng.random() per attempt, even when p is 0. If u < p, return "throttled": don't call send, don't retry.send(). "ok": count an accept and return "ok". "overloaded": go to the next attempt.After max_attempts attempts that were all overloaded, return "failed".
The window. Count in one bucket per whole second, floor(clock.now()). The counts are the sums over the buckets s with s > floor(now) − window: with window = 120, the current second and the 119 before it. Drop older buckets, so memory stays bounded however long the client runs. Also expose requests(), accepts(), retries() and reject_probability() (the p of step 2, right now).
In JavaScript and TypeScript: new OverloadClient(clock, rng, { k = 2, window = 120, retryRatio = 0.1, maxAttempts = 3 } = {}) with call(send), requests(), accepts(), retries() and rejectProbability().
The SRE book describes these mechanisms and the formula; it doesn't specify bucket sizes or the order of the checks, so the rules above are this lab's choices, fixed so the tests are exact. The chart's test uses a 10-second window so the reaction fits on screen; the book's clients keep two minutes of history.
Write your code, then run 3 example tests and 9 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.