Sviluppo web
Rate limit di un'API: progetto e costi
Un controllo di rate limit costa 0,222 µs e 121 byte per chiave: il progetto si gioca sulla chiave e sul 429, non sulla microottimizzazione.
In sintesi
- Un milione di controlli fixed window costa 221,9 ms, cioè 0,222 µs per controllo; il token bucket 271,7 ms.
- Ogni chiave occupa 121 byte di heap: un milione di chiavi pesa circa 121 MB.
- La pulizia di un milione di chiavi scadute blocca il loop per 244,3 ms.
- RFC 6585, aprile 2012, definisce il codice 429; RFC 9110, giugno 2022, definisce Retry-After.
Un rate limit è un contratto con tre parti: una chiave, una finestra e un costo per richiesta. Il controllo in sé costa poco, 0,222 µs per chiamata in memoria; il progetto si gioca su cosa conti, dove conservi il contatore e cosa restituisci quando il limite scatta.
Scegli la chiave prima del numero
La chiave decide chi paga il limite. Per un client autenticato è l'identificativo dell'utente o della chiave API; per un client anonimo è l'indirizzo IP, eventualmente combinato con la rotta. Un limite per IP punisce gli utenti dietro un proxy condiviso, un limite per utente non ferma un attacco distribuito. Scegli la chiave che corrisponde alla risorsa che stai proteggendo: il database, il provider di pagamenti, la coda di invio email.
Tre algoritmi, tre compromessi
La finestra fissa conta le richieste in intervalli uguali: è la più semplice e consente un picco doppio a cavallo del confine, perché il contatore riparte da zero. Il token bucket riempie un secchio a velocità costante e permette un burst fino alla capacità: costa uno stato per chiave e due operazioni aritmetiche. La finestra scorrevole pesa le richieste precedenti ed è più precisa, ma richiede di conservare i timestamp. Le tre implementazioni misurate stanno nello stesso ordine di grandezza.
Il costo per controllo
0,222 µs per controllo significa che un singolo core può fare dell'ordine di milioni di controlli al secondo: il collo di bottiglia non è il calcolo, è la memoria e il coordinamento fra istanze. Con 100.000 chiavi attive il consumo è di circa 12 MB, con un milione sale a 121 MB. Se il contatore vive in un processo, ogni istanza conta per sé e il limite effettivo è il numero di istanze moltiplicato per il limite dichiarato.
Cosa restituire quando il limite scatta
RFC 6585, aprile 2012, definisce il codice 429 Too Many Requests; RFC 9110, HTTP Semantics, giugno 2022, descrive Retry-After, che dice al client quando riprovare. Restituisci anche quanti token restano e qual è il limite, in header documentati, così il client può rallentare prima di sbattere contro il muro. Non usare 403: il client è autenticato e il problema è temporaneo.
for i in $(seq 1 8); do curl -s -o /dev/null -D - "https://api.example.com/v1/orders" | grep -iE "^(HTTP|retry-after|ratelimit)"; done
La pulizia è parte del progetto
Le chiavi accumulate vanno eliminate, e la scansione non è gratis: 244,3 ms per un milione di chiavi scadute, in un colpo solo, sul loop degli eventi. Spezza la pulizia in blocchi piccoli, falla girare a intervalli e usa una struttura ordinata per scadenza se le chiavi sono molte. In alternativa lascia scadere le chiavi nel datastore con un TTL e paga il costo dove non ferma le richieste.
Il limite: non è una difesa DDoS
Un rate limit applicativo protegge un servizio da un client mal configurato o da un abuso moderato; non ferma un attacco volumetrico, che si ferma prima, in rete. E con più istanze un contatore in memoria non è un limite globale: o sposti lo stato in un datastore condiviso, o accetti che il limite reale sia più alto. Entrambe le scelte vanno scritte, perché il numero promesso nella documentazione deve corrispondere a quello applicato.
Da dove partire
Domande frequenti
429 o 403 quando il limite scatta?
429. Il client è autenticato e ha il permesso, ma ha superato una quota temporanea; 403 direbbe che il permesso manca, e il client smetterebbe di riprovare. Aggiungi Retry-After per indicare quando tornare.
Dove conservo i contatori?
In memoria se hai una sola istanza, in un datastore condiviso appena ne hai due. Con lo stato locale ogni istanza applica il proprio limite, quindi il limite effettivo è la somma e non il numero documentato.
Che limite devo scegliere?
Quello che il servizio sostiene, misurato: carico il sistema alla capacità nota, guarda dove la latenza cresce e metti il limite sotto quel punto. Un numero copiato da un'altra API non dice nulla sul tuo sistema.
Il rate limit ferma un DDoS?
No. Ferma un client che esagera e un abuso moderato; un attacco volumetrico si assorbe in rete o sulla CDN. Il rate limit applicativo è l'ultimo strato, non il primo.
Come gestisco un burst legittimo?
Con un token bucket: il secchio permette un picco fino alla capacità e poi impone la velocità media. La finestra fissa lascia passare il doppio del limite a cavallo del confine, che di solito non è ciò che vuoi.