# API rate limiting: design and cost

A rate-limit check costs 0.222 µs and 121 bytes per key: the design work sits in the key and the 429, not in micro-optimisation.

> Page: https://ui.eneawork.it/blog/api-rate-limit-design
> Markdown: https://ui.eneawork.it/blog/api-rate-limit-design.md

*2026-11-01*

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.

Node 24.15.0, 1 novembre 2026: un milione di controlli su 100.000 chiavi con limite di 5 per minuto ha impiegato 221,9 ms con finestra fissa e 271,7 ms con token bucket, rifiutando metà delle chiamate. Il costo in memoria misurato è di 121 byte per chiave, quindi un milione di chiavi pesa circa 121 MB, e la scansione che elimina un milione di chiavi scadute blocca il loop per 244,3 ms. Il catalogo EneUI conta 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, letti da /api/stats lo stesso giorno.

## 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

Scegli una chiave per risorsa protetta e documentala, invece di copiare un limite generico.,Rispondi 429 con Retry-After e header con limite e residuo; registra i rifiuti come metrica.,Sposta il contatore in un datastore condiviso appena hai più di un'istanza, e verifica il limite reale con un test di carico.,Applica la stessa logica alle chiamate MCP, descritte in rate limit per un server MCP.,Per l'impaginazione che accompagna le liste, leggi paginazione a cursore; per il contratto generale, progettare un'API.,Per mostrare quote e consumi in interfaccia, la vista tabella, i blocchi dashboard e il catalogo form coprono i casi comuni.

## FAQ

### 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.
