Il Rate Limiting è una tecnica di controllo del traffico che consente di limitare il numero di richieste che un client può inviare a un servizio entro un intervallo temporale definito. Viene utilizzato per proteggere API REST, applicazioni web, sistemi di autenticazione e servizi backend da utilizzi eccessivi, richieste automatizzate e comportamenti anomali che possono compromettere la disponibilità delle risorse. Senza un meccanismo di limitazione, un singolo client può generare un numero elevato di richieste, aumentando il consumo di CPU, memoria, connessioni al database e banda di rete. In un'architettura distribuita, questi effetti possono propagarsi tra più componenti e provocare rallentamenti, saturazione delle risorse o indisponibilità parziale del servizio. Il Rate Limiting introduce quindi una soglia operativa che permette di controllare il carico, distribuire le risorse disponibili e ridurre l'impatto degli abusi senza impedire il normale utilizzo dell'applicazione.

Come funziona il Rate Limiting a livello applicativo

Il meccanismo si basa sulla definizione di una quota di richieste consentite in un determinato intervallo, associata a un identificativo come indirizzo IP, account autenticato, API key, token o identificativo del client. Quando arriva una richiesta, il sistema verifica il contatore o lo stato associato a quell'identità e decide se consentire l'elaborazione oppure rifiutarla. Se la soglia è stata raggiunta, il servizio può rispondere con un errore HTTP 429 Too Many Requests, indicando eventualmente quanto tempo deve trascorrere prima di poter riprovare attraverso l'header Retry-After. Questo comportamento permette ai client legittimi di gestire correttamente il limite, evitando tentativi ripetuti che aggraverebbero il carico.

La scelta dell'identità su cui applicare il limite è determinante. Una policy basata esclusivamente sull'indirizzo IP può risultare efficace contro alcune forme di automazione, ma può penalizzare utenti legittimi che condividono la stessa connessione attraverso NAT o reti aziendali. Al contrario, un limite associato all'account autenticato permette di distinguere meglio gli utenti, ma richiede che l'identità sia verificata prima dell'applicazione della regola. Per le API esposte a soggetti differenti, può essere opportuno combinare più criteri, applicando soglie globali al servizio, limiti per singolo account e restrizioni specifiche per gli endpoint più costosi.

Algoritmi di Rate Limiting: Token Bucket, Leaky Bucket e Sliding Window

La scelta dell'algoritmo influenza la precisione del controllo e il modo in cui il sistema gestisce i picchi di traffico. Il Token Bucket mantiene un contenitore di token che vengono generati a una velocità prestabilita. Ogni richiesta consuma uno o più token e viene accettata se la quantità disponibile è sufficiente. Questo modello consente di gestire brevi raffiche di richieste, purché rientrino nella capacità accumulata, mantenendo al tempo stesso una velocità media controllata. È particolarmente utile per API che devono tollerare picchi temporanei senza consentire un carico elevato e continuativo.

Il Leaky Bucket modella invece l'elaborazione come un flusso che procede a una velocità controllata. A seconda dell'implementazione, le richieste possono essere accodate oppure rifiutate quando la capacità disponibile viene superata. È un approccio utile quando si vuole rendere più regolare il traffico diretto verso componenti sensibili, anche se l'accodamento introduce una latenza aggiuntiva e deve essere limitato per evitare l'accumulo incontrollato di richieste. Lo Sliding Window, o finestra temporale mobile, calcola il numero di richieste effettuate durante un intervallo che avanza nel tempo. Rispetto a un semplice contatore azzerato a intervalli fissi, può ridurre la possibilità che un client invii due raffiche ravvicinate ai confini di finestre consecutive. La scelta dipende quindi dalla tolleranza ai picchi, dai requisiti di precisione e dal costo computazionale del controllo.

Implementare il Rate Limiting in un'architettura distribuita

In un'applicazione ospitata su un singolo server, i contatori possono essere gestiti in memoria o attraverso un archivio locale. In un ambiente con più istanze applicative, invece, un contatore mantenuto esclusivamente nella memoria di ciascun processo non rappresenta il numero complessivo di richieste ricevute dal servizio. Ogni nodo potrebbe infatti consentire autonomamente il limite configurato, moltiplicando di fatto la soglia effettiva in funzione del numero di istanze attive.

Per mantenere un comportamento coerente è possibile utilizzare un archivio condiviso come Redis, che consente di conservare lo stato dei contatori e applicare aggiornamenti atomici. L'atomicità è essenziale per evitare che richieste concorrenti leggano contemporaneamente lo stesso valore e superino il limite prima che il contatore venga aggiornato. Le operazioni di incremento, verifica e scadenza devono essere progettate in modo coerente, ad esempio attraverso script Lua eseguiti direttamente in Redis o primitive atomiche appropriate. Occorre inoltre valutare la gestione delle chiavi, la memoria occupata dai contatori, le politiche di scadenza e il comportamento del sistema quando l'archivio condiviso non è raggiungibile.

Il controllo può essere applicato anche a livello di API Gateway, reverse proxy o ingress controller, prima che il traffico raggiunga il backend. Questo permette di rifiutare le richieste in eccesso senza impegnare inutilmente le risorse applicative e di centralizzare le policy per più servizi. Tuttavia, la configurazione deve tenere conto degli indirizzi IP inoltrati dai proxy: header come X-Forwarded-For o Forwarded devono essere considerati attendibili soltanto quando provengono da proxy conosciuti e configurati correttamente. In caso contrario, un client potrebbe manipolare l'indirizzo dichiarato e aggirare le limitazioni basate sull'IP.

Rate Limiting per autenticazione e API sensibili

Gli endpoint di login, recupero password, verifica dei codici temporanei e generazione dei token richiedono policy specifiche perché possono essere presi di mira da tentativi automatizzati. In questi casi il limite deve essere progettato per ridurre la possibilità di brute force e abuso senza trasformarsi in uno strumento che consente a un attaccante di bloccare facilmente gli account legittimi. È preferibile combinare limiti per indirizzo IP, identità, dispositivo e finestra temporale, valutando anche l'introduzione di ritardi progressivi, autenticazione multifattore e controlli basati sul rischio.

Per le API aziendali, non tutti gli endpoint hanno lo stesso costo operativo. Una richiesta che legge un dato già disponibile in cache può richiedere poche risorse, mentre un'operazione che esegue query complesse, genera report o avvia elaborazioni asincrone può avere un impatto molto maggiore. È quindi possibile assegnare pesi differenti alle operazioni, prevedendo quote più restrittive per le funzionalità costose e limiti separati per i client con esigenze diverse. Le API key devono essere associate a identità e autorizzazioni precise, senza essere considerate da sole una garanzia di sicurezza: se una chiave viene esposta, il limite per client deve contenere il consumo improprio e consentire una rapida revoca.

Monitoraggio, logging e gestione dei limiti

Un Rate Limiting efficace richiede metriche che permettano di comprendere quante richieste vengono accettate, quante vengono rifiutate e quali endpoint generano più frequentemente il superamento delle soglie. La raccolta di questi dati aiuta a distinguere un abuso da un aumento legittimo del traffico, individuare client configurati in modo errato e correggere limiti troppo restrittivi. Le metriche possono essere correlate con tempi di risposta, utilizzo della CPU, connessioni al database, errori applicativi e saturazione dei pool di connessione. Nei sistemi distribuiti, questa correlazione è particolarmente importante perché il limite può intervenire correttamente a livello di ingresso mentre il carico continua a crescere su componenti interni non protetti.

Le policy devono essere verificate attraverso test di carico e scenari di concorrenza, controllando che il numero massimo di richieste venga rispettato anche quando arrivano molte chiamate simultanee. È inoltre necessario definire cosa accade in caso di errore del componente che gestisce i contatori. Un comportamento fail-open consente di proseguire le richieste anche quando il controllo non è disponibile, privilegiando la continuità ma aumentando il rischio di abuso; un comportamento fail-closed blocca o rifiuta le richieste, proteggendo maggiormente le risorse ma potendo compromettere la disponibilità del servizio. La scelta deve dipendere dalla criticità dell'applicazione, dal tipo di endpoint e dalle conseguenze di un'interruzione.

Rate Limiting e protezione complessiva dei servizi aziendali

Il Rate Limiting è una componente importante della protezione delle applicazioni, ma non sostituisce un Web Application Firewall, i controlli di autenticazione, la validazione degli input o le misure di difesa contro gli attacchi distribuiti. La sua efficacia dipende dalla corretta scelta delle soglie, dalla gestione delle identità, dall'implementazione coerente in ambienti distribuiti e dal monitoraggio continuo del comportamento dei client. Un'azienda che espone API e applicazioni su Internet dovrebbe integrare il controllo delle richieste nella progettazione dell'architettura, applicando policy differenziate in base al rischio e al costo delle operazioni. In questo modo è possibile ridurre gli abusi, contenere il consumo delle risorse e mantenere un livello di disponibilità adeguato anche in presenza di picchi di traffico o richieste automatizzate anomale.