¿Cuándo Rust se encuentra con la sobrecarga? ¿Por qué su servicio «se cae»?
\n\nImagine que ha escrito en Rust un servicio ideal de alta carga. Vuela, utiliza todas las ventajas de las abstracciones de costo cero y la propiedad de la memoria. Pero he aquí el problema: llega un cliente «voraz» y comienza a golpear su servidor con miles de solicitudes por segundo. ¿Qué sucede? ¿Se cae la base de datos? ¿Se ahoga la cola? O, peor aún, ¿recibe una prohibición de la API externa a la que usted mismo accede?
\n\nEn Rust, como en cualquier otro lenguaje, no se puede prescindir de rate limiting (limitación de velocidad) y throttling (aceleración). Esto no es solo una «buena práctica»: es una cuestión de supervivencia de su aplicación en producción. Pero Rust tiene una particularidad: nos encanta hacer todo de manera eficiente y desde cero. Sin embargo, no será necesario reinventar la rueda: tenemos potentes crates (bibliotecas) y patrones elegantes.
\n\nEn este artículo, analizaremos cómo implementar correctamente la limitación de solicitudes en Rust, utilizando tanto algoritmos clásicos (Token Bucket) como enfoques asíncronos modernos. Aprenderá cómo proteger su API de ataques DDoS, cómo no exceder los límites de servicios externos y cómo escribir su propio acelerador que funcione más rápido que en Go o Python.
\n\nRate Limiting vs Throttling: ¿cuál es la diferencia?
\n\nAntes de sumergirnos en el código, aclaremos los términos de una vez por todas. A menudo se confunden, pero la diferencia es crítica.
\n\n- \n
- Rate Limiting — es una política de seguridad. Usted dice: «El cliente X puede hacer un máximo de 100 solicitudes por minuto». Si se excede el límite, devuelve 429 Too Many Requests. Esto protege su backend. \n
- Throttling — es un mecanismo de control de velocidad. Usted mismo decide a qué velocidad procesar las solicitudes para no sobrecargar la CPU, E/S o la base de datos. El cliente puede enviar 1000 solicitudes, pero usted las procesará a 10 por segundo, y el resto las pondrá en cola. \n
En Rust, ambos enfoques se pueden implementar con las mismas herramientas, pero con lógica diferente. Veremos ambos.
\n\nAlgoritmos: Token Bucket y Leaky Bucket
\n\nLos algoritmos más populares para rate limiting son Token Bucket y Leaky Bucket. Son simples como una bota, pero increíblemente efectivos.
\n\nToken Bucket (Cubo de fichas)
\nImagine un cubo al que cada segundo le caen N fichas. Cuando llega una solicitud, toma una ficha. Si el cubo está vacío, la solicitud es rechazada. Rust es ideal para implementar este algoritmo gracias a las operaciones atómicas.
\n\nLeaky Bucket (Cubo con fuga)
\nAquí el cubo tiene un agujero en el fondo por el que sale una cantidad fija de solicitudes por segundo. Las solicitudes caen en el cubo desde arriba. Si el cubo se desborda, las solicitudes sobrantes se descartan. Este algoritmo es mejor para suavizar picos de carga.
\n\nEn Rust, no escribiremos estos algoritmos a mano (aunque sea educativo). En su lugar, usaremos el crate governor — el estándar de oro para rate limiting en el ecosistema Rust.
Práctica: Instalando Governor
\n\nEl crate governor es una implementación rápida, asíncrona y segura para hilos de Token Bucket. Soporta modos síncrono y asíncrono, y funciona con tokio y async-std.
Añadimos a Cargo.toml:
[dependencies]\ngovernor = "0.6"\ntokio = { version = "1", features = ["full"] }\n\n\nAhora escribamos un rate limiter simple para un servidor HTTP basado en hyper o axum. Para simplificar, usaremos axum.
use axum::{\n extract::Request,\n http::StatusCode,\n middleware::{self, Next},\n response::Response,\n routing::get,\n Router,\n};\nuse governor::{DefaultDirectRateLimiter, Quota, RateLimiter};\n