Vai al contenuto principale

Livellare le chiamate con il rate limiting

L'API pubblica CaptainDNS applica due livelli di rate limiting: uno per IP sorgente prima dell'autenticazione, e un token bucket per chiave la cui capacità dipende dal piano. Questa guida spiega entrambi i livelli e come scrivere un client rispettoso.

Due livelli di protezione

Rate limit per IP pre-auth

Prima di leggere l'header Authorization, un limitatore in memoria limita il traffico per IP sorgente. Il default sono 120 richieste al minuto per IP, regolabile lato CaptainDNS. Questo livello protegge da attacchi volumetrici e credential stuffing.

Un superamento restituisce 429 RATE_LIMITED con Retry-After: 60. Nessuna chiave viene verificata, nessun credito consumato.

Token bucket per chiave

Una volta autenticata la chiave, un token bucket persistente in Postgres gestisce il limite per chiave. La capacità è allineata al piano:

PianoCapacità (token)Ricarica
Free1010 token/min
Solo1010 token/min
Starter6060 token/min
Pro500500 token/min
Business1.0001.000 token/min
Enterprise1.2001.200 token/min

Ogni richiesta autenticata consuma un token. Il bucket si ricarica continuamente (ricarica frazionaria), quindi non devi aspettare un tick completo.

Un bucket vuoto restituisce 429 RATE_LIMITED. L'header Retry-After contiene i secondi fino al prossimo token disponibile.

Header di risposta

Ogni risposta include questi header:

RateLimit-Policy: "default";q=60;w=60
X-RateLimit-Limit: 60
  • RateLimit-Policy: formato IETF draft, descrive la policy. q è la capacità del bucket, w la finestra in secondi.
  • X-RateLimit-Limit: capacità del bucket, identica a q.

X-RateLimit-Remaining non viene emesso a ogni richiesta: è posizionato a 0 solo in caso di rifiuto 429 (la funzione PL/pgSQL che consuma un token non restituisce il numero di token residui e una lettura aggiuntiva avrebbe un costo di latenza ingiustificato). Dimensiona il tuo client basandoti sulla capacità, sull'header Retry-After e sulle tue misurazioni lato client, non su un contatore server in tempo reale.

Su un 429, avrai inoltre:

Retry-After: 12
X-RateLimit-Remaining: 0

Il client deve rispettare Retry-After e attendere almeno i secondi indicati prima di riprovare.

Strategia di retry raccomandata

Il pattern idiomatico è un backoff esponenziale limitato:

  1. Conta i token lato client a partire da X-RateLimit-Limit. Tieni il tuo bucket locale per prevedere la saturazione prima di inviare la richiesta.
  2. Su 429 RATE_LIMITED, leggi Retry-After e attendi almeno quel tempo.
  3. Se accumuli più 429 consecutivi, applica un moltiplicatore: 2x, 4x, 8x con un tetto a 60 secondi.
  4. Aggiungi un jitter casuale di +/- 20%, così più client non riprovano nello stesso istante.
  5. Limita il numero totale di retry (es. 5). Oltre quella soglia, registra l'errore e portalo nella tua osservabilità.

Esempio di pseudo-codice:

delay = retryAfter + random(-0.2, 0.2) * retryAfter
for attempt in 1..5:
    response = send(request)
    if response.status != 429:
        return response
    retryAfter = response.header["Retry-After"] or delay * attempt
    sleep(min(retryAfter, 60))
raise RateLimitExceeded

Strategie per pipeline batch

Le integrazioni che elaborano grandi volumi in batch (per esempio la scansione di 10.000 domini ogni notte) traggono beneficio da strategie diverse:

  • Token bucket lato client: replica localmente il bucket del server e invia solo se un token è disponibile. Eviti così i 429 e il jitter che ne consegue.
  • Parallelizzazione in base alla capacità: un piano Pro ha 500 token/min, circa 8 req/s. Otto worker in parallelo sono più efficienti di un worker solo che fa 500 iterazioni al minuto con delle pause.
  • Separazione di endpoint costosi: se lanci 500 page-crawl-check (10 crediti) e 500 dmarc/lookup (1 credito), mettili in due code distinte. Livelli il consumo di crediti e rispetti il rate limit con meno sforzo.
  • Finestre di manutenzione: alcuni batch possono girare fuori dalle ore di punta, per non contendere l'inviluppo al traffico in tempo reale.

Cosa conta come richiesta

Ogni richiesta HTTP autenticata consuma un token, anche se fallisce con 400 o 500. Il rate limit protegge il backend; la validazione dell'input viene dopo l'ottenimento del token, non prima.

Eccezioni:

  • 429 RATE_LIMITED: non consuma token, ovviamente, dato che il bucket è già vuoto.
  • 401 INVALID_API_KEY: non tocca il bucket per chiave, perché non si sa ancora di quale chiave si tratti. Il rate limit per IP, invece, conta.
  • 403 IP_NOT_ALLOWED: non consuma né token né credito, perché la verifica precede il consumo.

Aumentare la capacità

La capacità del token bucket è una proprietà del piano, non un parametro della chiave. Per aumentarla:

  1. Passa a un piano superiore da /account/billing.
  2. L'effetto è immediato: la chiave eredita il nuovo tetto già dalla richiesta successiva.

I clienti Enterprise possono negoziare un tetto personalizzato, fino a 5.000 req/min su preventivo, con il proprio account manager CaptainDNS.

Diagnosticare un 429

Un 429 inatteso può avere più cause:

  • Bucket pre-auth: superi 120 req/min su un singolo IP. Verifica che più servizi non condividano lo stesso egress (NAT, CGNAT).
  • Token bucket per chiave: il piano non offre la capacità necessaria. Passa a un piano superiore o riduci la concorrenza.
  • Picco episodico: un batch non livellato ha emesso 200 richieste in un secondo. Aggiungi jitter e livellamento lato client.
  • Deriva di clock: nei rari casi di un server desincronizzato da NTP, l'header Retry-After può sembrare eccessivo. Sincronizza l'orologio.

Se il problema persiste nonostante un rispetto scrupoloso di Retry-After, apri un ticket di supporto con l'X-Request-Id di una richiesta 429 rappresentativa.

Strumenti CaptainDNS correlati

  • Il DNS lookup permette di verificare manualmente un record senza consumare crediti.
  • Il DMARC monitoring aggrega i report DMARC senza passare dall'API pubblica.

Prossimo passo: l'idempotenza per risparmiare crediti e token sui retry, o i codici d'errore.