# Distributed rate limiting over meerdere API-instances

[Naar de inhoud](#lm-inhoud)Netwerk/NL[EN](/en/distributed-rate-limiting-over-meerdere-api-instances)[Hubhub.llmnet.nlModellen vergelijken op taak, taal, kosten en licentie.](https://hub.llmnet.nl/)[Communitycommunity.llmnet.nlPrompttechnieken, patronen en systeemprompts.](https://community.llmnet.nl/)[APIapi.llmnet.nlLLM's robuust in software: rate limits, routing, structured output.](https://api.llmnet.nl/)[Consultancyconsultancy.llmnet.nlAI invoeren in een organisatie, van pilot tot productie.](https://consultancy.llmnet.nl/)[Nieuwsnieuws.llmnet.nlOntwikkelingen in AI, geduid voor Nederland.](https://nieuws.llmnet.nl/)[Benchmarkbenchmark.llmnet.nlZelf meten wat AI-kwaliteit is, voor jouw taken.](https://benchmark.llmnet.nl/)[Vacaturesvacatures.llmnet.nlAI-rollen, salarissen en carrièrepaden in Nederland.](https://vacatures.llmnet.nl/)[Lerenleren.llmnet.nlAI-concepten in gewoon Nederlands, van beginner tot bouwer.](https://leren.llmnet.nl/)[Gidsgids.llmnet.nlAI privé draaien op eigen Mac, pc, NAS of thuisserver.](https://gids.llmnet.nl/)[Directorydirectory.llmnet.nlHet AI-ecosysteem in kaart: tools, modellen, bedrijven.](https://directory.llmnet.nl/)[Radarradar.llmnet.nlSignalen uit X, onderzoek en communities voor indie developers.](https://radar.llmnet.nl/)[Appsapps.llmnet.nlReviews van AI-apps en open-source repo's, met tips voor wie zelf bouwt.](https://apps.llmnet.nl/)[llmnet.nl — hoofdsite](https://llmnet.nl/)[](https://x.com/intent/post?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances&text=Distributed%20rate%20limiting%20over%20meerdere%20API-instances)[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances)[](https://www.reddit.com/submit?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances&title=Distributed%20rate%20limiting%20over%20meerdere%20API-instances)[](#)[](https://x.com/intent/post?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances&text=Distributed%20rate%20limiting%20over%20meerdere%20API-instances)[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances)[](https://www.reddit.com/submit?url=https%3A%2F%2Fapi.llmnet.nl%2Fdistributed-rate-limiting-over-meerdere-api-instances&title=Distributed%20rate%20limiting%20over%20meerdere%20API-instances)[](#)

 
# Distributed rate limiting over meerdere API-instances

 Door Ivo Donker — samengesteld met AI-ondersteuning (Claude & Gemini)

 Wanneer een LLM-infrastructuur groeit van een enkele server naar een gedistribueerde cluster met tientallen API-instances, verliest een lokaal rate-limiting-mechanisme zijn effectiviteit. Als individuele workers hun eigen tellers bijhouden in het lokale geheugen, kan een piek in inkomende verzoeken alsnog upstream rate limits overschrijden of tot oneerlijke toewijzing tussen tenants leiden. Binnen architectuurpijler A2 vormt centrale coördinatie het fundament; lees de gids over [zelf een LLM-gateway hosten](https://api.llmnet.nl/llm-gateway-zelf-hosten) om te zien hoe proxy-lagen en failover-clusters fundamenteel worden opgezet.

 In dit artikel analyseren we hoe gedistribueerde rate limiting in een multi-instance gateway werkt. We behandelen de specifieke dimensies van LLM-beperkingen (requests per minuut, tokens per minuut en concurrency), de onderliggende datastructuren en Redis Lua-scripts, technieken voor lokale batchreserveringen om roundtrips te minimaliseren, en de trade-offs tussen absolute synchronisatie en doorvoersnelheid.

 
## De unieke uitdaging van rate limiting bij LLM-gateways

 Klassieke HTTP API rate limiters meten hoofdzakelijk Requests Per Minute (RPM). Bij taalmodellen volstaat deze enkele dimensie niet. Upstream modelproviders (zoals Anthropic, OpenAI of zelf-gehoste vLLM-clusters) hanteren drie verschillende, simultane begrenzingen die onafhankelijk van elkaar kunnen vollopen:

 
 
- Requests Per Minute (RPM): Het ruwe aantal HTTP-verzoeken binnen een tijdsvenster.
 
- Tokens Per Minute (TPM): De som van prompt-tokens en gegenereerde completion-tokens over een glijdend venster van zestig seconden.
 
- Concurrency (inflight requests): Het aantal parallelle model-aanroepen dat op een exact moment actief wordt verwerkt.
 

 De asymmetrie van tokenverbruik maakt gedistribueerde synchronisatie complex. Bij aanvang van een API-call is de omvang van de invoer bekend, maar de uitvoerlengte is onzeker tot de response volledig is gegenereerd of afgebroken. Om het risico op overschrijding te beheersen, moeten we het mechanisme koppelen aan de grondbeginselen van doorvoerbeheer; bekijk de analyse over het [token bucket algoritme in een LLM-gateway](https://api.llmnet.nl/token-bucket-algoritme-llm-gateway) voor de wiskundige eigenschappen van continue navulling en burst-tolerantie.

 
 
 
 
 Dimensie | 
 Meetmoment | 
 Onzekerheid vooraf | 
 Faalmodus bij desynchronisatie | 
 

 
 
 
 RPM | 
 Start van het verzoek | 
 Geen (tellertoename = 1) | 
 HTTP 429 van upstream provider | 
 

 
 TPM | 
 Start (schatting) + Einde (correctie) | 
 Hoog (onbekende outputlengte) | 
 Plotselinge throttling over alle instances | 
 

 
 Concurrency | 
 Start (lock/slot) + Einde (release) | 
 Tijdsduur streaming onbekend | 
 Resource-uitputting op model-backends | 
 

 
 
 

 
## Architectuurpatronen voor gedistribueerde coördinatie

 Er zijn drie hoofdpatronen om de verbruiksstaat tussen onafhankelijke API-instances synchroon te houden. Elk patroon brengt eigen concessies met zich mee op het gebied van netwerklatency, consistentie en complexiteit.

 
### 1. Centrale State Store (Redis / Dragonfly)

 Elke inkomende request forceert een snelle remote operatie naar een centrale in-memory datastore. Omdat meerdere instances gelijktijdig dezelfde teller proberen bij te werken, worden updates verpakt in atomaire Lua-scripts. Dit voorkomt race conditions (zoals check-then-act fouten), maar introduceert een extra netwerk-roundtrip (doorgaans 0.5 tot 2.0 milliseconden in hetzelfde datacenter) vóórdat de gateway het verzoek doorstuurt naar de modelprovider.

 
### 2. Batch Allocation / Token Leasing

 In plaats van per individueel verzoek een centrale store te raadplegen, reserveert elke API-instance periodiek een "lease" of "tokenblok" uit de centrale pool (bijvoorbeeld 50 verzoeken en 50.000 tokens voor 5 seconden). De instance handelt binnenkomend verkeer lokaal en zonder netwerklatency af zolang het lease-budget niet is uitgeput. Ongebruikte tokens worden na afloop van de lease-periode teruggestort.

 
### 3. Consistent Hashing op de Load Balancer

 De load balancer routeert inkomend verkeer op basis van de hash van de tenant-ID of API-sleutel naar altijd dezelfde gateway-instance. Hierdoor kan de rate limiting volledig lokaal in het RAM van de instance plaatsvinden. Het nadeel is dat bij het toevoegen of wegvallen van instances de hashes herverdeeld worden, en dat "hot tenants" één instance onevenredig zwaar kunnen belasten.

 
## Implementatie: Atomair Sliding Window in Redis via Lua

 Een glijdend venster (sliding window log of sliding window counter) voorkomt de bekende grensoverschrijdingen bij vaste vensters, waarbij een dubbele burst rondom de minuutwisseling kan optreden. In Redis implementeren we dit via een geordende set (Sorted Set, ZSET), waarin de score en het lid beide bestaan uit het timestamp in milliseconden.

 -- Lua script voor gedistribueerde sliding window rate limiting
-- KEYS[1]: Redis key (bijv. "ratelimit:tpm:tenant_123")
-- ARGV[1]: Huidige timestamp in milliseconden
-- ARGV[2]: Venstergrootte in milliseconden (bijv. 60000)
-- ARGV[3]: Maximale capaciteit (bijv. 100000 tokens)
-- ARGV[4]: Gevraagde eenheden voor dit verzoek (bijv. 1200 tokens)
-- ARGV[5]: Unieke request identifier

local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local max_limit = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])
local req_id = ARGV[5]

local clear_before = now - window

-- 1. Verwijder records die buiten het glijdende venster vallen
redis.call('ZREMRANGEBYSCORE', key, '-inf', clear_before)

-- 2. Bereken de huidige som van het verbruik
local entries = redis.call('ZRANGE', key, 0, -1, 'WITHSCORES')
local current_usage = 0
for i = 1, #entries, 2 do
 -- Het element bevat "cost:req_id"
 local raw_val = entries[i]
 local sep = string.find(raw_val, ":")
 if sep then
 local item_cost = tonumber(string.sub(raw_val, 1, sep - 1))
 current_usage = current_usage + (item_cost or 0)
 end
end

-- 3. Valideer of er voldoende ruimte is
if current_usage + cost <= max_limit then
 -- Voeg het huidige verbruik toe aan de ZSET
 local member = tostring(cost) .. ":" .. req_id
 redis.call('ZADD', key, now, member)
 -- Stel TTL in op iets meer dan het venster voor automatische opruiming
 redis.call('PEXPIRE', key, window + 1000)
 return {1, max_limit - (current_usage + cost), 0} -- Toegestaan
else
 -- Bereken wachttijd tot er voldoende capaciteit vrijkomt
 local oldest_entries = redis.call('ZRANGE', key, 0, 1, 'WITHSCORES')
 local retry_after = 0
 if #oldest_entries >= 2 then
 retry_after = math.max(0, (tonumber(oldest_entries[2]) + window) - now)
 end
 return {0, max_limit - current_usage, retry_after} -- Geweigerd
end

 Dit script voert de opschoning, berekening en toekenning in één atomair blok uit op de Redis-server. Omdat Redis single-threaded opereert ten aanzien van command-uitvoering, kunnen twee API-instances op hetzelfde milliseconde-interval nooit een inconsistente staat waarnemen of overschrijden.

 
## Twee-fase Token Reservering bij Onbekende Completion Lengte

 Bij het doorsturen van een prompt naar een LLM weten we hoeveel tokens de prompt bevat ($T_{in}$), maar niet hoeveel tokens het antwoord zal genereren ($T_{out}$). Wanneer men pas achteraf meet, kan een gelijktijdige batch van lange antwoorden de upstream TPM-limiet overschrijden. Registreert men vooraf conservatief de maximale modelcontext (`max_tokens`), dan worden legitieme verzoeken ten onrechte afgewezen (valse 429-fouten).

 De oplossing hiervoor is een twee-fase reserveringsprotocol (Two-Phase Token Reservation):

 
 Fase 1: Reservering vooraf
 De API-instance berekent $T_{in}$ via een lokale tokenizer en maakt een inschatting van $T_{est} = T_{in} + \min(\text{max\_tokens}, \text{historisch\_gemiddelde} \times 1.5)$. De instance reserveert $T_{est}$ tokens in de centrale Redis-limiter via het atomair script.

 Fase 2: Afrekening en afstemming achteraf
 Nadat de response via Server-Sent Events (SSE) of standaard JSON is voltooid, leest de instance het daadwerkelijk gerapporteerde aantal tokens ($T_{actueel} = T_{in} + T_{out}$) uit de payload van de provider. Het script voert een reconciliatie uit:

 
 
- Als $T_{actueel} < T_{est}$: Het verschil ($T_{est} - T_{actueel}$) wordt direct als een negatieve boeking of credit teruggegeven aan de sliding window teller.
 
- Als $T_{actueel} > T_{est}$: Het extra verbruik wordt direct bijgeschreven, wat eventueel volgende calls kort vertraagt.
 

 Voor inzicht in hoe upstream quota en financiële limieten zich tot deze technische buffers verhouden, raadpleegt men het artikel over [rate limits, tokens en kosten](https://api.llmnet.nl/rate-limits-en-kosten) om overschrijdingen op organisatieniveau te voorkomen.

 
## Concurrency Beperking met Gedistribueerde Semaphores

 Naast tijdgebonden limieten (RPM/TPM) is concurrency-beperking cruciaal om te voorkomen dat streaming verbindingen alle beschikbare sockets of vLLM batching-slots bezet houden. Een gedistribueerde semaphore via Redis bewaakt het aantal gelijktijdig actieve verbindingen over alle instances.

 import time
import uuid
import redis

class DistributedConcurrencyLimiter:
 def __init__(self, redis_client: redis.Redis, resource_key: str, max_concurrent: int, timeout_sec: int = 120):
 self.r = redis_client
 self.key = f"semaphore:{resource_key}"
 self.max_concurrent = max_concurrent
 self.timeout_sec = timeout_sec

 def acquire(self) -> str | None:
 """Probeert een slot te reserveren. Retourneert slot_id bij succes, anders None."""
 slot_id = str(uuid.uuid4())
 now = time.time()
 
 # Ruim verouderde slots op (bijvoorbeeld door gecrashte instances)
 pipe = self.r.pipeline()
 pipe.zremrangebyscore(self.key, '-inf', now - self.timeout_sec)
 pipe.zcard(self.key)
 _, current_count = pipe.execute()

 if current_count < self.max_concurrent:
 # Voeg slot toe met huidige timestamp als score
 added = self.r.zadd(self.key, {slot_id: now}, nx=True)
 if added:
 return slot_id
 return None

 def release(self, slot_id: str) -> None:
 """Geeft het gereserveerde slot direct vrij."""
 self.r.zrem(self.key, slot_id)

 Een harde les uit productie-omgevingen is het risico op "spook-slots": wanneer een API-instance crasht of een netwerkonderbreking ervaart terwijl een streaming response loopt, mag het slot niet oneindig bezet blijven. Het opruimen van entries ouder dan timeout_sec bij elke `acquire`-operatie voorkomt dat een dode verbinding de totale cluster-concurrency permanent verlaagt.

 
## Pre-fetching en Local Token Buckets bij Hoge Doorvoer

 Bij duizenden requests per seconde creëert een centrale store een latency-bottleneck. Als elke gateway-node 100 verzoeken per seconde afhandelt, veroorzaken tien nodes samen 1.000 Redis-aanroepen per seconde per tenant. Om dit te reduceren passen we een hybride strategie toe: Local Leased Token Buckets.

 Hierbij vraagt een API-instance periodiek een blok tokens op bij de centrale Redis-instantie. Zodra de instance een lokaal budget heeft van bijvoorbeeld 20.000 tokens, valideert en verwerkt hij binnenkomende calls volledig in het lokale werkgeheugen (latency < 0.05ms). Pas wanneer de lokale buffer onder 20% van de capaciteit zakt, stuurt de achtergrondthread een asynchrone vernieuwingsaanvraag naar de centrale Redis-server.

 
 Zwak punt van Local Token Leases: Als het verkeer onevenredig verdeeld is over de instances (bijvoorbeeld 90% van de traffic landt op instance A door sticky routing), kan instance A door zijn lokale pool heen raken en verzoeken onterecht afwijzen, terwijl instance B en C duizenden ongebruikte tokens in hun lokale lease vasthouden. Stel de lease-duur daarom kort in (1 tot 3 seconden) en implementeer een direct teruggavemechanisme bij inactiviteit.
 

 
## Queueing en Backpressure bij Uitputting

 Wanneer een rate limit bereikt wordt, is het direct teruggeven van een HTTP 429 statuscode vaak ongewenst voor de eindgebruiker of de aanroepende pipeline. Een robuuste architectuur combineert rate limiting met een gedistribueerde wachtrij. In plaats van verzoeken weg te gooien, worden ze geparkeerd in een prioriteitsbuffer tot er weer capaciteit beschikbaar komt.

 Om te voorkomen dat interactieve gebruikersinteracties worden geblokkeerd door zware achtergrond-taken, splitsen we de wachtrij op basis van urgentie; lees de documentatie over [prioriteitswachtrijen voor LLM-taken](https://api.llmnet.nl/prioriteitswachtrijen-kritieke-llm-taken) voor implementatiepatronen rondom latency-budgetten en starvation-preventie.

 Voor een breder overzicht van open-source en enterprise tooling die deze logica modulair kan aanbieden, biedt de gids voor [kostenbewaking en token management voor LLM-applicaties](https://directory.llmnet.nl/kostenbewaking-en-token-management-voor-llm-applicaties) vergelijkend inzicht in kant-en-klare gateway-oplossingen versus maatwerkbouw.

 
## Fouttolerantie en Fail-Open vs. Fail-Closed Strategieën

 Wat gebeurt er als de centrale Redis-cluster onbereikbaar wordt of een netwerkpartitie ervaart? In de gateway-architectuur moet expliciet worden gekozen tussen twee gedragslijnen:

 
 
 
 
 Strategie | 
 Gedrag bij Redis-uitval | 
 Voordeel | 
 Risico | 
 

 
 
 
 Fail-Open | 
 Verzoeken worden doorgelaten zonder centrale controle. | 
 Dienstverlening blijft beschikbaar voor eindgebruikers. | 
 Kostenpieken en cascade-fouten door upstream 429's. | 
 

 
 Fail-Closed | 
 Alle verzoeken die centrale verificatie vereisen worden geweigerd (HTTP 503/429). | 
 Gegarandeerde budgetbeveiliging en geen contractbreuk met providers. | 
 Totale downtime van de applicatie bij storing in de cachinglaag. | 
 

 
 Degraded Local Fallback | 
 Schakel over naar een lokaal, conservatief noodbudget per instance (bijv. 20% van normaal). | 
 Beperkte beschikbaarheid zonder risico op massale overload. | 
 Complexere statusovergangen en kans op korte oneerlijke throttling. | 
 

 
 
 

 In de praktijk blijkt Degraded Local Fallback de meest stabiele keuze voor productie-omgevingen: wanneer de centrale store niet binnen een ingestelde timeout van 15 milliseconden reageert, valt de instance tijdelijk terug op een strikte lokale in-memory teller totdat de centrale verbinding hersteld is.

 
## Samenvatting en Besturingsregels

 Gedistribueerde rate limiting over meerdere API-instances vereist een zorgvuldige balans tussen nauwkeurigheid, latency en foutbestendigheid. Om het systeem stabiel te houden in productie hanteren we de volgende vuistregels:

 
 
- Beheer RPM, TPM en Concurrency als gescheiden dimensies in de centrale state store.
 
- Gebruik een sliding window met atomair uitgevoerde Lua-scripts om grensoverschrijdingen bij vensterwisselingen te elimineren.
 
- Implementeer een twee-fase reserveringsprotocol voor tokens om de discrepantie tussen geschat en daadwerkelijk voltooid tokenverbruik op te vangen.
 
- Voeg altijd een automatische TTL/timeout toe aan concurrency-semaphores om dode locks door gecrashte workers te neutraliseren.
 
- Kies voor Degraded Local Fallback bij storingen in de centrale coördinatielaag om volledige uitval te voorkomen zonder upstream limieten te overbelasten.
