← Blog

Incident-walkthrough

n8n retry storm: anatomie van een €1.840 OpenAI-burn

Het dashboard bevroor om 02:14 op een dinsdag. De OpenAI-rekening bleef oplopen. Zes uur en €1.840 aan tokens later draaide de catalogus-enrichment workflow nog steeds retries.

Jacob Molkenboer· Oprichter · A Brand New Company· 1 aug 2024· 8 min
Messing spies met papieren bonnen op ivoren vloei, één groen doorslagje, omgevallen bel, zakhorloge, inktblauw linnen.

Het dashboard bevroor om 02:14

Een Haags e-commerceteam draait elke nacht catalogus-enrichment op zo'n 12.000 SKU's. De workflow staat in n8n. Hij haalt nieuwe en gewijzigde producten uit Postgres, stuurt elk product naar OpenAI voor een herschreven beschrijving en een categorie-gok, schrijft het resultaat terug, en post elke 500 items een heartbeat naar een Slack-gevoed dashboard.

Om 02:14 op een dinsdag stopte de dashboard-tegel met updaten. De Slack-heartbeat zei batch 4 van 24 voltooid. Niemand zag het. De operations lead opende haar laptop om 08:30, ververste de OpenAI-facturatiepagina uit gewoonte, en zag €1.840 uitgegeven sinds middernacht. Hun normale nachtelijke uitgave is €11.

Zes uur stille, betaalde retries. Geen alert ging af. Geen mail kwam binnen. De workflow werkte in zekere zin perfect: elke node gaf uiteindelijk succes terug.

Dit is de post-mortem.

De workflow die toebeet

De pipeline bestond uit negen nodes. De vorm was bepalend:

  1. Cron trigger, elke nacht om 02:00.
  2. Postgres node, haalt gewijzigde SKU's op sinds de laatste run.
  3. Split In Batches, size 50.
  4. OpenAI Chat node, gpt-4.1, genereert een beschrijving en een categorie-gok.
  5. JSON parse en validatie.
  6. Postgres update.
  7. Heartbeat-webhook na elke vijfde batch.
  8. Einde van de loop.
  9. Error workflow trigger die, mooi detail, OpenAI opnieuw aanroept om de fout voor Slack samen te vatten.

De OpenAI-node was geconfigureerd met Retry On Fail: enabled, Max Tries: 5, Wait Between Tries: 1000ms. De defaults. De error workflow was een n8n community template die de vorige developer had gekopieerd-geplakt zonder iets aan te passen. Aan stap 7 hing een HTTP-node die POSTte naar dezelfde n8n-instantie om de workflow te herstarten op de laatst succesvolle batch-index, in naam van veerkracht.

Je ziet nu al twee van de drie landmijnen.

Anatomie van de retry storm

Begin met één enkele 429. OpenAI geeft rate-limit errors om twee redenen: tokens-per-minute en requests-per-minute. Het team had een Tier 2 account, prima voor batch-calls van rond de 2.400 output tokens per stuk, maar breekbaar. Drie batches van 50 SKU's achter elkaar, en je gaat over het per-minute window heen. De limieten per tier zijn na te lezen in de OpenAI rate-limit guide.

Dit is wat n8n doet bij een 429 met de defaults die het team had:

  • De node faalt.
  • Retry On Fail springt aan. n8n wacht 1 seconde, probeert opnieuw. Vijf keer.
  • De Split In Batches node vangt het niet op; de error gaat door naar workflow-niveau.
  • De Error Trigger workflow vuurt. Die workflow roept OpenAI opnieuw aan om de fout samen te vatten.
  • De HTTP self-trigger vuurt. Een nieuwe workflow-run start op de laatst succesvolle batch-index.
  • De nieuwe run loopt tegen het nog steeds verzadigde quotum. Herhaal.

Er zit hier een cruciaal facturatie-detail verstopt. OpenAI rekent output tokens af die het model heeft gegenereerd, ook als jouw client mid-stream een time-out krijgt of disconnect. De HTTP-timeout van n8n in hun workflow was 60 seconden. Het model begon, n8n verbrak de socket, het model maakte de generatie alsnog af. n8n logde een failure. OpenAI logde de tokens. (n8n's eigen error-handling documentatie waarschuwt voor dubbele error-handling, maar zegt weinig over de kosten daarvan tegen een betaalde endpoint.)

Tokenkosten per cyclus, ruwweg:

50 items per batch
  x ~2,400 output tokens per call
  x 5 node-level retries
  x ~30 self-restart cycles before the morning
= ~18 million billed output tokens

Bij gpt-4.1 output-prijzen van ongeveer €0,008 per 1k tokens kom je uit rond de €1.800. Wat overeenkomt met wat ze zagen.

De blinde vlek van het dashboard

De Slack-heartbeat vuurde vanuit de Split In Batches loop, na een succesvolle batch-write. De eerste drie echte batches gingen prima door, voordat de workflow tegen het TPM-plafond aanliep. Daarna zag elke dashboard-refresh dezelfde batch 4 van 24 heartbeat van een eerdere self-restart, omdat de workflow telkens herstartte op batch 4.

De dashboard-tegel zei batch 4 van 24, last update 02:14. Hij ging nooit updaten, want de workflow zat in een loop op batch 4. Het on-call dashboard had geen tweede signaal: geen throughput per minuut, geen spend tracker, geen draait de workflow nu-widget. Slack hoorde stilte en nam aan dat alles in orde was.

Dit soort dingen voedt de scepsis van engineers tegenover AI-infrastructuur. Een terugkerende klacht in de Ask HN front-page thread over anti-AI-sentiment deze week was dat het kostenoppervlak ondoorzichtig is. Dit is wat ze bedoelen. De workflow faalde niet luid. Hij faalde duur en stil.

De timeline van zes uur

02:00:00  Cron fires. Workflow run #1 starts.
02:01:14  Batches 1, 2, 3 complete. Heartbeat: 3 of 24.
02:01:58  Batch 4 starts. TPM ceiling hit on item 27.
02:02:03  OpenAI node retry 1/5. 429.
02:02:08  Retry 5/5. Node throws.
02:02:09  Error branch fires. HTTP self-trigger.
02:02:09  Workflow run #2 begins at batch 4.
02:02:14  Workflow #2 hits same 429.
...       (this repeats, every ~12 minutes)
05:47:30  Workflow run #~28 hits same 429.
08:30:12  Operations lead opens billing dashboard. €1,840.
08:30:40  Workflow killed manually via n8n UI.

Er was geen enkel punt van catastrofale failure. Er waren dertig kleine failures, elk goed voor ongeveer zestig euro.

De fix, in drie wijzigingen

We deden drie dingen, in volgorde van belang.

1. Sluit de uitgaven af bij de bron

OpenAI laat je harde usage-limieten per project instellen. Het team had dat niet gedaan. We splitsten de API key van hun workflow af in een eigen project, zetten een harde limiet van €50 per dag, en lieten het model 403's teruggeven zodra de limiet wordt geraakt. Een 403 is een echte, luide failure. De controls staan beschreven in de production best practices guide van OpenAI; ze zijn in negentig seconden geconfigureerd en hadden dit incident bij €50 gestopt in plaats van bij €1.840.

2. Verwijder de self-restart loop

De veerkrachtige HTTP self-trigger ging de prullenbak in. We vervingen hem door een queue. Mislukte items worden weggeschreven naar een Postgres-tabel failed_enrichment_queue, met hun laatste error en een retry-teller. Een aparte workflow leegt de queue om 06:00 met een vers quotum-window. Geen live loop, geen recursie, geen verrassing bij zonsopgang.

3. Respecteer de 429

We zetten Retry On Fail uit voor de OpenAI-node en voegden een Code-node toe die de Retry-After header leest en het gevraagde aantal wacht, met jitter:

// Code node, runs after every OpenAI call.
// Honours Retry-After if present, otherwise backs off exponentially.
const err = $input.first().json.error;
if (err?.status === 429) {
  const retryAfter = Number(err.headers?.['retry-after'] ?? 0);
  const base = retryAfter > 0
    ? retryAfter * 1000
    : Math.min(2 ** ($itemIndex % 6) * 1000, 30_000);
  const jitter = (($itemIndex * 9301 + 49297) % 1024);
  await new Promise(r => setTimeout(r, base + jitter));
}
return $input.all();

Let op de deterministische jitter via $itemIndex in plaats van Math.random(). n8n-executions blijven inspecteerbaar als dezelfde input dezelfde output oplevert, en dat is wat je wilt zodra een workflow-run bewijsmateriaal wordt.

Les

De duurste AI-failures zijn niet degene die crashen. Het zijn degene die stil blijven retryen, op een budget dat je nooit hebt afgetopt.

De monitoring-laag die we toevoegden

Een workflow die stil faalt is erger dan een die luid faalt. Drie checks erbij:

  • Een token-spend heartbeat. Elke vijftien minuten queryt een kleine workflow de usage-endpoint van OpenAI en post naar Slack als de uitgave van de dag de rollende veertien-daagse p95 met meer dan drie keer overschrijdt.
  • Een liveness probe op het dashboard. De tegel toont nu laatste heartbeat om HH:MM met een kleur die rood wordt na tien minuten stilte, in plaats van vrolijk de oude waarde te tonen.
  • Een concurrency-alarm. Als dezelfde workflow meer dan drie gelijktijdige executions heeft, krijgt de on-call een PagerDuty-melding.

Niets hiervan is exotisch. Het team had het werkte gisteren als monitoring-strategie vertrouwd.

Een audit van vijf minuten die je vandaag kunt doen

Voor de lunch:

  1. Open het OpenAI-project dashboard. Staan er harde usage-limieten op elk project? Als het antwoord de default is, dan is dat nee.
  2. Open elke n8n-workflow die een betaalde API aanroept. Kijk naar elke node met Retry On Fail aan. Vermenigvuldig max tries × batch size × items per batch. Als die worst-case je angst aanjaagt, pas de configuratie aan.
  3. Zoek je workflows af op self-trigger patronen: HTTP-nodes die POSTen naar dezelfde n8n-instantie, of Error Trigger workflows die de gefaalde run herstarten. Dat zijn loops die op gebeuren wachten.
  4. Controleer dat elke langlopende workflow een heartbeat heeft die fails closed. Het dashboard wordt rood bij afwezigheid, blijft nooit groen op de laatste goede waarde.

Toen we de catalogus-enrichment pipeline voor dit Haagse e-commerceteam herbouwden als één van onze AI-agent trajecten, was de les die we steeds opnieuw gebruikten dat n8n een prima dirigent is en een gevaarlijke uitvoerder. We zetten de model-calls achter een kleine queue-worker die de rate limit beheerde, en lieten n8n doen waar hij goed in is: de rest orkestreren. De vorm van de pipeline veranderde niet. De blast radius van één slechte minuut wel.

Vanavond, voor je je laptop dichtklapt: zet een harde spend limit op elke OpenAI project-key die je hebt. Het kost negentig seconden. Het had dit team €1.829 bespaard.

Kern

Top je OpenAI-uitgaven per project af voor je een workflow vertrouwt. Harde limieten maken van een stille burn van zes uur één duidelijke 403.

FAQ

Waarom rekent OpenAI tokens af als mijn client een time-out krijgt?

Output tokens worden afgerekend op wat het model heeft gegenereerd, niet op wat jouw client heeft ontvangen. Kap je een lange completion af met een 60-seconden time-out, dan betaal je nog steeds voor wat het model produceerde voordat de socket sloot.

Is de default Retry On Fail van n8n veilig voor betaalde API's?

Niet zonder erbij na te denken. De default van 5 tries met 1 seconde wachttijd vermenigvuldigt de kosten bij transient errors en negeert Retry-After headers. Zet hem uit en behandel retries expliciet via een queue, of lees de header en backoff netjes.

Hoe voorkom ik dat een workflow zichzelf in een loop herstart?

Gebruik nooit een HTTP-node om dezelfde workflow bij een error opnieuw te triggeren. Schrijf mislukte items naar een queue-tabel en leeg die vanuit een aparte workflow op een verse schedule. Live recursie tegen een rate-limited API is hoe stille burns ontstaan.

Welke spend cap moet ik instellen op een OpenAI project-key?

Zet hem op ongeveer drie keer je normale dagelijkse uitgave, nooit op de default unlimited. Het punt is dat een ontspoorde workflow binnen uren tegen een 403 aanloopt, niet tegen een factuur van vijf cijfers aan het eind van de maand.

ai agentsautomationintegrationsworkflowoperationscase study

Iets bouwen?

Start een project