Het verwerken van een enkele prompt via een Language Model API is eenvoudig. Maar zodra je duizenden documenten moet analyseren, klantrecensies moet classificeren of complete databases wilt migreren, verandert de dynamiek fundamenteel. Realtime API-calls schieten dan al snel tekort. Je botst tegen harde limieten aan, applicaties crashen door geheugenproblemen en een onverwachte netwerkfout kan je hele workflow ruïneren.
Om grote hoeveelheden data stabiel te verwerken, is een doordachte architectuur nodig. Dit artikel bespreekt hoe je batchverwerking opzet, hoe je omgaat met rate limits, waarom idempotentie cruciaal is en hoe je kosten beheersbaar houdt. Voor inzicht in de basiskosten en limieten kun je ook terecht bij rate limits en kosten.
Realtime versus Batchverwerking
Bij traditionele webtoepassingen worden API-verzoeken synchroon afgehandeld: de gebruiker wacht op het antwoord. Voor grootschalige achtergrondtaken is dit patroon ongeschikt. De tabel hieronder illustreert de belangrijkste verschillen:
| Eigenschap | Realtime (Synchroon) | Batchverwerking (Asynchroon) |
|---|---|---|
| Latentie | Direct (milliseconden tot seconden) | Variabel (uren tot dagen afhankelijk van volume) |
| Foutafhandeling | Directe feedback naar client | Automatische herhaling en isolatie van fouten |
| Resource-gebruik | Hoge piekbelasting, gevoelig voor spikes | Gelijkmatig verdeeld over de tijd |
| Kosten | Standaard tarieven | Vaak korting bij native batch-endpoints van providers |
Wanneer je kiest voor asynchrone verwerking, verschuift het ontwerpprincipe van "direct antwoord" naar "betrouwbare statusadministratie". Dit sluit nauw aan bij het opzetten van robuuste systemen, vergelijkbaar met de principes die je leest bij rag voor beginners voor het indexeren van grote documentcollecties.
Het Verwerkingspatroon: Statusadministratie per Item
Een robuuste batch-engine steunt op een database die de status van elk individueel verzoek nauwkeurig bijhoudt. Zonder zo'n administratie ben je bij een crash volledig blind en kun je geen onderscheid maken tussen succesvolle items en mislukte taken.
Elk item in je wachtrij doorloopt een vaste levenscyclus:
- Pending: Het item is aangemaakt, maar nog niet opgepakt.
- Processing: Een worker heeft het item gelocked en verzonden naar de API.
- Completed: Het LLM-antwoord is succesvol ontvangen en opgeslagen.
- Failed: Het verzoek is definitief mislukt na alle pogingen.
Hieronder zie je een schematische weergave van een database-schema in SQL dat deze statussen ondersteunt:
CREATE TABLE batch_jobs (
id SERIAL PRIMARY KEY,
payload JSONB NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
attempts INT DEFAULT 0,
response TEXT,
error_message TEXT,
updated_at TIMESTAMP DEFAULT NOW()
);
Parallellisme Begrenzen en Rate Limits Ontwijken
Het blind afvuren van duizenden parallelle HTTP-requests leidt onmiddellijk tot 429 Too Many Requests errors. LLM-providers hanteren strikte grenzen op basis van *Requests Per Minute* (RPM) en *Tokens Per Minute* (TPM).
Om binnen deze grenzen te blijven, gebruik je een worker pool met een rate limiter (bijvoorbeeld een Token Bucket-algoritme). Dit zorgt ervoor dat het aantal gelijktijdige verzoeken dynamisch wordt afgekapt zodra je de limiet nadert. Mocht je toch tegen een limiet aanlopen, dan is een correcte strategie voor hertentendement essentieel. Lees meer over hoe je hiermee omgaat via retries en backoff.
Concurrency Control in code
In plaats van oneindige async threads te starten, beperk je het aantal actieve workers. Dit voorkomt dat je geheugen volloopt en houdt je netwerkverkeer voorspelbaar.
import asyncio
async def worker(queue, semaphore, api_client):
while True:
item_id, payload = await queue.get()
async with semaphore:
try:
# Verwerk LLM call
result = await api_client.generate(payload)
await save_success(item_id, result)
except Exception as e:
await handle_failure(item_id, str(e))
finally:
queue.task_done()
Idempotentie: Veilig Herstarten Zonder Dubbelwerk
Wat gebeurt er als je batch-script vastloopt op 80% van de dataset en je herstart de applicatie? Zonder idempotentie loop je het risico dat je alle eerdere items opnieuw verstuurt. Dat kost dubbele tokens en dus dubbel geld.
Idempotentie betekent dat een actie meerdere keren kan worden uitgevoerd met exact hetzelfde resultaat als eenmalige uitvoering. Dit bereik je door:
- Een unieke hash (bijvoorbeeld een SHA-256 van de inputtekst) te genereren als primaire sleutel of idempotency key.
- Vóór elke API-call te controleren of de database al een status
completedheeft voor die specifieke hash. - Inkomende resultaten direct te koppelen aan die unieke identifier.
Deelresultaten Tussentijds Wegschrijven
Het opslaan van resultaten pas aan het very einde van een batch van 10.000 items is een recept voor frustratie. Als het proces na 9.000 items crasht door een onverwachte geheugenlek, ben je alles kwijt.
Schrijf daarom resultaten in kleine batches (chunks van bijvoorbeeld 50 of 100 items) direct weg naar je database of een object store. Dit minimaliseert het risico op dataverlies en geeft je direct inzicht in de voortgang via je dashboard.
Omgaan met Gedeeltelijke Mislukking (Partial Failure)
Bij grootschalige verwerking is de kans honderd procent dat een deel van de items faalt. Misschien bevat een specifieke prompt illegale karakters, weigert het model te antwoorden vanwege veiligheidsfilters of treedt er een tijdelijke netwerkstoring op.
Een professionele batch-pipeline stopt nooit bij een fout in een enkel item. In plaats daarvan:
- Vang de specifieke uitzondering op binnen de worker loop.
- Log de foutmelding en het item-ID in de database.
- Verhoog de teller voor het aantal pogingen (
attempts). - Laat de batch doorgaan met de resterende items.
- Exporteer na afloop alle gefaalde items naar een aparte "dead-letter queue" voor handmatige analyse.
Kosten vooraf schatten met een steekproef
Voordat je een gigantische batch loslaat op een duur model, wil je weten wat de kosten gaan worden. Token-aantallen laten zich vooraf moeilijk exact voorspellen omdat de lengte van zowel de input als de output varieert.
De oplossing is een representatieve steekproef:
- Selecteer willekeurig 1% tot 5% van je totale dataset (bijvoorbeeld 100 items).
- Voer deze kleine batch uit en meet exact het aantal gebruikte input- en outputtokens.
- Bereken het gemiddelde aantal tokens per item.
- Vermenigvuldig dit gemiddelde met het totale aantal items en de tarieven van de provider.
Op deze manier voorkom je financiële verrassingen achteraf en kun je op basis van de uitkomst eventueel kiezen voor een goedkoper model of kortere prompts.
Conclusie
Batchverwerking van LLM-calls vraagt om een verschuiving van ad-hoc scripts naar robuuste achtergrondarchitectuur. Door te werken met een strikte statusadministratie, parallellisme te begrenzen, idempotentie toe te passen en tussentijds resultaten weg te schrijven, bouw je systemen die autonoom duizenden verzoeken veilig verwerken. Combineer dit met een goede kosteninschatting vooraf en je houdt zowel de techniek als je budget volledig onder controle.