aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Webontwikkeling

API Rate Limiting: strategieën voor verzoeklimieten

API rate limiting is de techniek om te beperken hoeveel verzoeken een client binnen een bepaald tijdvenster naar je API mag sturen. Het doel is nooit enkelvoudig: het blokkeert misbruik en brute-force-aanvallen, houdt infrastructuurkosten voorspelbaar, voorkomt dat één client alle resources opslokt ten koste van anderen en zorgt voor een eerlijke verdeling van het gebruik. Een goed ontworpen limietlaag maakt je API zowel veiliger als stabieler.

Waarom heb je rate limiting nodig?

Een onbeschermd endpoint kan in diverse scenario's snel onderuit gaan. Een oneindige lus in een client, een kwaadaardige bot of een gebruiker die je gratis laag misbruikt, kan honderden verzoeken per seconde afvuren. Deze situaties leiden tot:

  • Uitputting van resources: databaseverbindingen, CPU en geheugen worden door één client opgeslokt.
  • Kostenexplosie: cloudrekeningen en aanroepen van externe API's groeien ongecontroleerd.
  • Beveiligingsrisico: wachtwoorden raden, OTP-brute-force en scraping worden eenvoudig.
  • Oneerlijkheid: één agressieve gebruiker verpest de ervaring voor alle anderen.

Het token bucket-algoritme

Token bucket is dankzij zijn flexibiliteit de meest gebruikte methode. Het idee is eenvoudig: elke client heeft een emmer met een vaste capaciteit, en er worden in een gelijkmatig tempo tokens aan toegevoegd. Elk verzoek verbruikt een token; is de emmer leeg, dan wordt het verzoek geweigerd. Omdat de emmer capaciteit heeft, tolereert hij korte pieken (bursts) maar houdt hij het gemiddelde tempo op de lange termijn constant.

class TokenBucket {
  constructor(capacity, refillPerSecond) {
    this.capacity = capacity;
    this.tokens = capacity;
    this.refillRate = refillPerSecond;
    this.last = Date.now();
  }

  tryRemove(count = 1) {
    const now = Date.now();
    const elapsed = (now - this.last) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
    this.last = now;

    if (this.tokens >= count) {
      this.tokens -= count;
      return true;
    }
    return false;
  }
}

Hier bepaalt capacity de toegestane burst-grootte en stelt refillRate het houdbare gemiddelde tempo in. Bij een capaciteit van 20 en een vultempo van 5 per seconde kan een client bijvoorbeeld snel tot 20 verzoeken pieken, maar zakt op de lange termijn terug naar 5 verzoeken per seconde.

Het sliding window-algoritme

Een naïeve "fixed window"-teller (zeg 100 verzoeken per minuut) veroorzaakt een randprobleem: als 100 verzoeken in de laatste seconde van het ene venster vallen en 100 in de eerste seconde van het volgende, gaan er 200 verzoeken in twee seconden doorheen. Sliding window lost dit op met een meeschuivend tijdsbereik. De nauwkeurigste vorm is de sliding window log, die de tijdstempel van elk verzoek opslaat en de verzoeken buiten het venster verwijdert. Een veelgebruikte manier om dit in Redis te implementeren is met een sorted set:

-- voeg de tijdstempel toe met ZADD, ruim oude op, tel daarna
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
  redis.call('ZADD', key, now, now)
  redis.call('PEXPIRE', key, window)
  return 1
end
return 0

De log-methode is het nauwkeurigst, maar slaat elke tijdstempel per client op. Is geheugen krap, kies dan voor de zogeheten sliding window counter: die weegt de tellers van het huidige en het vorige vaste venster om een benaderde waarde te schatten, met veel minder geheugen tegen een kleine foutmarge.

Tellers delen in een gedistribueerde opzet

Heb je meerdere applicatieservers, dan vermenigvuldigt elke server met zijn eigen in-memory teller de limiet feitelijk met het aantal servers. De oplossing is de tellers in een centrale, snelle opslag te houden — meestal Redis. De atomaire commando's van Redis (INCR, sorted-set-bewerkingen) en Lua-scripts die in één round-trip draaien, zorgen voor consistent tellen zonder race conditions. Veel frameworks bieden dit kant-en-klaar: Laravel biedt de RateLimiter-facade en een throttle-middleware, aan de Express-kant is er express-rate-limit met een Redis-store, en NGINX heeft de limit_req-directive.

Correcte HTTP-responses en headers

De juiste signalen sturen wanneer je een verzoek weigert, hoort bij goed API-ontwerp. Geef de standaard statuscode 429 Too Many Requests terug en vertel de client wanneer hij het opnieuw mag proberen:

  • Retry-After: na hoeveel seconden een nieuwe poging is toegestaan.
  • RateLimit-Limit: totaal toegestane verzoeken per venster.
  • RateLimit-Remaining: resterend verzoekquotum.
  • RateLimit-Reset: resterende tijd tot de teller wordt gereset.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Content-Type: application/json

{"error":"rate_limit_exceeded","message":"Te veel verzoeken. Probeer het over 30 seconden opnieuw."}

Moedig clients aan deze headers te lezen en opnieuw te proberen met exponentiële backoff. Zo komt een geweigerde client niet allemaal tegelijk terug zodra de limiet wegvalt en ontstaat er geen nieuwe golf van belasting.

Praktische tips

  • Kies de juiste sleutel: limiteer per gebruiker/API-sleutel voor geauthenticeerd verkeer en per IP voor anoniem verkeer. IP alleen kan misleidend zijn achter een proxy of NAT.
  • Gelaagde limieten: hanteer strakkere limieten voor dure endpoints (zoeken, export, inloggen) en lossere voor lichte lees-endpoints.
  • Fail-open of fail-closed: beslis bewust of je verzoeken weigert of doorlaat als Redis uitvalt.
  • Observeer: meet je 429-ratio en welke sleutels de limiet raken; onderscheid echt misbruik van legitieme pieken.

Veelgestelde vragen

Moet ik token bucket of sliding window gebruiken?

Wil je korte pieken toestaan terwijl je het gemiddelde tempo behoudt, dan is token bucket ideaal en eenvoudig te implementeren. Moet je het randverdubbelingsprobleem absoluut elimineren en wil je zeer precies tellen, kies dan de sliding window log. Voor de meeste API's is token bucket ruim voldoende.

Waar moet ik rate limiting toepassen?

Idealiter gelaagd: aan de voorkant met NGINX/CDN/API-gateway voor grove bescherming, en in de applicatielaag voor fijne, bedrijfsspecifieke limieten. De applicatielaag kan slimmere beslissingen nemen omdat die context kent, zoals de identiteit van de gebruiker.

Wat is het verschil tussen 429 en 503?

429 Too Many Requests zegt dat de client zijn eigen limiet heeft overschreden; de verantwoordelijkheid ligt bij de client. 503 Service Unavailable betekent dat de server in het algemeen overbelast is of in onderhoud. Voor rate limiting is 429 altijd de juiste code.

Wil je een solide rate limiting-laag voor je API bouwen? Van token bucket tot Redis-gebaseerde gedistribueerde tellers, ik ontwerp een oplossing die past bij je bestaande infrastructuur. Neem contact op en laten we je project samen beveiligen.

Bu kategorideki tüm yazılar →

Devamı için