Quand votre API crie "SOS" : Introduction au Rate Limiting et au Throttling
\n\nImaginez : vous avez écrit une application Kotlin parfaite. Elle est rapide, élégante, utilise les coroutines et Flow. Vous l'avez mise en production, et là — bam ! — un "gentil" utilisateur (ou, pire, un attaquant) commence à bombarder votre endpoint avec des milliers de requêtes par seconde. Le serveur tombe, la base de données pleure, et les autres utilisateurs reçoivent un 503 Service Unavailable.
Une douleur familière ? C'est exactement pour cela que les mécanismes de rate limiting (limitation de débit) et de throttling (étranglement) existent. Dans cet article, nous ne nous contenterons pas de parler de théorie, mais nous verrons, avec des exemples pratiques, comment implémenter ces modèles en Kotlin.
\n\nNous allons écrire du code qui protégera réellement votre backend, que ce soit une application avec Ktor, Spring Boot ou une simple coroutine. C'est parti !
\n\nRate Limiting vs Throttling : Quelle est la différence (et pourquoi est-ce important) ?
\n\nCommençons par les bases pour ne pas confondre les termes. Bien que ces concepts soient souvent utilisés comme synonymes, il y a une nuance entre eux.
\n\n- \n
- Rate Limiting — c'est une limite stricte. "Tu ne peux faire que 100 requêtes par minute. Après la 101e — tu obtiendras un
429 Too Many Requestset un bannissement jusqu'à la minute suivante". C'est une question de limite. \n - Throttling — c'est une régulation plus douce. "Tu ne peux pas envoyer de requêtes plus vite qu'une toutes les 200 ms. Si tu envoies plus vite — nous les mettrons en file d'attente ou les ignorerons simplement". C'est une question de vitesse. \n
Dans le monde du Kotlin, nous implémentons le plus souvent le rate limiting au niveau de l'API (middleware) et le throttling au niveau d'opérations spécifiques (par exemple, pour ne pas spammer un serveur externe avec des requêtes).
\n\nLes algorithmes que tout développeur Kotlin devrait connaître
\n\nAvant d'écrire du code, choisissons notre outil. Il existe trois algorithmes classiques qui s'intègrent parfaitement à l'architecture Kotlin.
\n\n1. Token Bucket (Seau de jetons) — Le plus populaire
\nImaginez un seau dans lequel N nouveaux jetons tombent chaque seconde. Le seau a un volume maximum (burst). Pour traiter une requête, il faut prendre un jeton. S'il n'y a pas de jetons, la requête est rejetée.
\nAvantages : Permet de petites pointes de charge (bursts), simple à implémenter.
\n\n2. Leaky Bucket (Seau percé) — Lissage des pics
\nUn seau avec un trou. Les requêtes entrent dans le seau, et le "trou" les libère à une vitesse constante. Si le seau déborde, les requêtes sont rejetées.
\nAvantages : Équilibre parfaitement la charge, ne crée pas de pics.
\n\n3. Sliding Window (Fenêtre glissante) — Précision sans à-coups
\nAu lieu de limites rigides (comme dans "1 minute"), nous examinons une période de temps glissante. Plus complexe à implémenter, mais reflète plus précisément la charge réelle.
\n\nPour les projets Kotlin, le Token Bucket est le juste milieu entre simplicité et efficacité. C'est celui que nous allons implémenter.
\n\nÉcrivons le code : Rate Limiter en Kotlin from scratch (sans bibliothèques)
\n\nCréons un Rate Limiter simple mais fonctionnel, en utilisant les coroutines et Mutex. Ce sera une version thread-safe du Token Bucket.
import kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlin.time.Duration\nimport kotlin.time.TimeSource\n\nclass TokenBucketRateLimiter(\n private val maxTokens: Int,\n private val refillRate: Long, // tokens par seconde\n private val timeSource: TimeSource = TimeSource.Monotonic\n) {\n private var tokens: Long = maxTokens.toLong()\n private var lastRefillTimestamp: Long = timeSource.markNow().elapsedNow().inWholeMilliseconds\n private val mutex = Mutex()\n\n suspend fun tryAcquire(): Boolean {\n mutex.withLock {\n refillTokens()\n return if (tokens > 0) {\n tokens--\n true\n } else {\n