Cuando tu API comienza a ahogarse: Introducción al rate limiting
Imagina la situación: escribiste un servidor perfecto en C++. Es rápido, eficiente, utiliza todos los núcleos del procesador. Lo pones en producción y... a los cinco minutos se cae. ¿Qué pasó? Resulta que algún script travieso decidió «probar» tu API, enviando miles de solicitudes por segundo.
¿Te suena familiar? Si no, tienes suerte. Pero tarde o temprano, todo desarrollador se enfrenta a la necesidad de controlar la carga. Aquí es donde entran en escena rate limiting y throttling: dos mecanismos poderosos que salvan a los servidores de sobrecargas y garantizan una distribución uniforme de los recursos.
En este artículo, analizaremos cómo implementar estos mecanismos en C++. No solo aprenderás la teoría, sino que también obtendrás código funcional que puedes usar de inmediato en tus proyectos. ¡Vamos allá!
Rate Limiting vs Throttling: ¿Cuál es la diferencia?
Antes de sumergirnos en el código, aclaremos estos dos conceptos. Muchos los confunden, pero la diferencia existe y es crítica.
Rate Limiting (Limitación de velocidad)
Es una «barrera». Dices: «Por segundo, no más de 10 solicitudes. Todo lo que exceda, queda bloqueado (o en cola)». El rate limiting protege tu API de solicitudes en avalancha, ataques DDoS y picos de tráfico accidentales.
Throttling (Estrangulamiento)
Es un «control de volumen». No bloqueas las solicitudes por completo, sino que ralentizas su procesamiento. Por ejemplo, si un cliente envía 100 solicitudes por segundo, las procesas, pero con un retraso de 10 ms entre cada una. El throttling es una forma más «educada» de gestionar la carga; no descarta datos, simplemente suaviza los picos.
En proyectos reales, estas técnicas suelen combinarse: primero throttling para suavizar, y al superar un umbral crítico, rate limiting devolviendo un error 429 (Too Many Requests).
Algoritmos de rate limiting en C++
Existen varios algoritmos clásicos. Analizaremos los dos más populares y eficaces: Token Bucket y Sliding Window. Ambos se implementan fácilmente en C++ y dan excelentes resultados.
1. Token Bucket (Cubo de fichas)
Imagina un cubo al que cada segundo le caen N fichas. Cada solicitud toma una ficha. Si el cubo está vacío, la solicitud se rechaza. Simple y genial.
Aquí tienes una implementación en C++ moderno usando std::chrono:
#include <chrono>#include <mutex>#include <atomic>
class TokenBucket {public: TokenBucket(size_t max_tokens, double refill_rate) : max_tokens_(max_tokens) , refill_rate_(refill_rate) , current_tokens_(max_tokens) , last_refill_(std::chrono::steady_clock::now()) {}
bool try_consume() { std::lock_guard<std::mutex> lock(mutex_); // Calculamos cuántas fichas se han acumulado desde la última vez auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_refill_); // Reponemos el cubo double tokens_to_add = elapsed.count() * refill_rate_ / 1000.0; current_tokens_ = std::min(max_tokens_, current_tokens_ + tokens_to_add); last_refill_ = now; // Intentamos tomar una ficha if (current_tokens_ > 0) { --current_tokens_; return true; } return false; }
private: std::mutex mutex_; size_t max_tokens_; // Capacidad máxima del cubo double refill_rate_; // Fichas por segundo double current_tokens_; // Cantidad actual de fichas std::chrono::steady_clock::time_point last_refill_;};Uso:
TokenBucket limiter(10, 1.0); // 10 fichas, recarga 1 ficha/segundo
// En el manejador de solicitudes:if (limiter.tr