The retry loop that retries a 400
Find the three defects one weekend's log exposes in a contractor's retry wrapper, cost the worst, and replace it with a policy sorted by status.
On this page
Ten lines of wrapper decide how every call in the invoice run fails. One weekend's log shows them failing three different ways, and only one of the three would show up in a test.
The situation
The agency's invoice run calls the accounting system through a wrapper a contractor wrote:
def call(method, url, body=None):
for attempt in range(6):
try:
r = http(method, url, json=body, timeout=10)
if r.status < 300:
return r
except Timeout:
pass
sleep(2)
raise Failed(url)
The run restarts every 15 minutes until every invoice has gone out. The API allows 30 requests a minute per account, shared with the overdue reminders. It accepts no idempotency key, but each invoice carries its timesheet id in a reference field. The weekend's log shows three things:
- One client's invoice returned
422 tax_number missingsix times per run, from 17:00 Friday to 09:00 Monday. - A
POST /invoicesfor another client timed out at 10 seconds; the retry returned 201. INV-0533 and INV-0534 now exist for the same week. - In the 17:00 burst, nine calls got
429withRetry-After: 20. Each retried five times, 2 seconds apart, and failed.
Assume staff time at $30 an hour. The lesson is rate limits, retries and backoff.
The question
Find the three defects the log exposes, put a number on the first, and say what replaces the wrapper.
A strong answer · Rubric · Where answers go wrong
Write your answer first, then open