Skip to main content

Retry and recover

A batch of calls where one endpoint is broken. The run retries it, gives up, records what happened, and processes the rest. The output is a file with a row for every input — which is the point: a run that ends on the first bad record tells you nothing about the other ninety-nine.

The workflow​

Global variables:

endpoints:
type: array
value:
- type: string
value: 'https://jsonplaceholder.typicode.com/users/1'
- type: string
value: 'https://this-host-does-not-exist.invalid/users/2'
- type: string
value: 'https://jsonplaceholder.typicode.com/users/3'
CreateTable("outcomes", schema={"columns": [{"name": "endpoint", "isKey": True}, {"name": "status"}, {"name": "detail"}]})

for endpoint in endpoints: # _name="Call each endpoint" _description="One row per endpoint whatever happens; a failure must not end the batch"
try:
with RetryOnError(retries=3, delay=2):
record = HttpRequest("{{ endpoint }}", "GET")
TableUpsertRow("outcomes", {"endpoint": "{{ endpoint }}", "status": "ok", "detail": "{{ record.name }}"})
except Exception as err:
TableUpsertRow("outcomes", {"endpoint": "{{ endpoint }}", "status": "failed", "detail": "{{ err }}"})

export = TableExport("outcomes", "C:/Temp/rinkt-examples/outcomes.csv", format="csv")
WriteLine("{{ export.rows }} endpoints recorded in {{ export.filePath }}")

The run​

Twelve seconds — most of it the three retries, two seconds apart — and three rows:

endpoint,status,detail
https://jsonplaceholder.typicode.com/users/1,ok,Leanne Graham
https://this-host-does-not-exist.invalid/users/2,failed,"Maximum number of retries reached — Get ""https://this-host-does-not-exist.invalid/users/2"": dial tcp: lookup this-host-does-not-exist.invalid: no such host"
https://jsonplaceholder.typicode.com/users/3,ok,Clementine Bauch

The run completed. Nothing about it is green-washed: the failure is in the file, with the error that caused it, and the two good records are there as well.

Worth taking from it​

RetryOnError goes inside try, not instead of it. They answer different questions. with RetryOnError(retries=3, delay=2): handles the failure that goes away on its own — a timeout, a rate limit, a page that had not finished loading. try / except handles the one that does not. Retrying alone still ends the run when the retries are exhausted; catching alone gives up on the first hiccup.

Wrap the smallest thing that can fail. Only the request is inside RetryOnError here. Putting the row-write in there too would rewrite a row three times to recover a request.

except Exception as err binds the error. {{ err }} is the message, and writing it into the row is the difference between a file that says something failed and a file that says why. The message above names DNS resolution — you can act on that without opening the run.

Retries cost wall-clock time. Three retries two seconds apart turned a sub-second batch into twelve seconds. On a batch of a thousand records with a systematically broken endpoint, that arithmetic is the whole run. Retry counts want to be small.