Diseño de sistema de un rate limiter: algoritmos y diagrama de arquitectura

Diseña un rate limiter distribuido: token bucket o ventanas deslizantes, dónde va el limitador, contadores en Redis, respuestas 429 y qué pasa si Redis cae.

· Fundador y desarrollador

Publicado el

Diseño de sistema de un rate limiter: algoritmos y diagrama de arquitectura

Toda API pública necesita un rate limiter, y la mayoría de los equipos lo descubren igual: un solo cliente con un bucle de reintentos, o un solo scraper, tumba el servicio para todos los demás. Un limitador pone un tope a lo que puede pedir una clave, un usuario o una IP, de modo que un actor malicioso te cuesta un 429 en lugar de una caída.

Las preguntas de diseño son siempre las mismas: qué algoritmo, dónde va el limitador, dónde viven los contadores y qué pasa cuando cae su almacén. Este artículo responde a cada una y termina con un diagrama de arquitectura de un limitador distribuido delante de una API.

Qué debe hacer el limitador

  • Decidir en pocos milisegundos si una petición está permitida, en cada petición.
  • Aplicar reglas distintas a llamantes distintos: por clave de API, por usuario, por IP, por ruta.
  • Comportarse igual sin importar qué instancia del servidor atienda la petición.
  • Decir con claridad a los clientes cuándo están limitados y cuándo volver a intentarlo.
  • Seguir funcionando, o fallar de forma predecible, cuando su propio almacenamiento tiene un problema.

El tercer punto es lo que lo convierte en un problema de sistemas distribuidos. Un límite de 100 peticiones por minuto no significa nada si cada una de diez instancias del gateway cuenta sus propias 100.

Cinco algoritmos

Algoritmo Cómo cuenta Fortaleza Debilidad
Token bucket Un cubo se rellena a ritmo fijo; cada petición toma un token Permite ráfagas cortas hasta el tamaño del cubo Dos parámetros que ajustar
Leaky bucket Las peticiones salen de una cola a ritmo fijo Ritmo de salida uniforme Las ráfagas esperan en la cola y añaden latencia
Contador de ventana fija Un contador por clave y por minuto El más barato: un incremento Puede pasar el doble del límite alrededor de un borde de ventana
Log de ventana deslizante Guarda una marca de tiempo por petición Exacto La memoria crece con el tráfico
Contador de ventana deslizante Pondera la cuenta de la ventana anterior según el solapamiento Casi exacto, dos contadores por clave Una aproximación

El problema de la ventana fija se ve fácil: un cliente envía 100 peticiones a las 12:00:59 y otras 100 a las 12:01:00, y ambas ventanas las aceptan. Son 200 peticiones en dos segundos con un límite de 100 por minuto.

Token bucket es la opción habitual para APIs, porque las ráfagas son un comportamiento normal de los clientes. Un límite como «100 peticiones por minuto, ráfaga de 20» se traduce directamente en un ritmo de recarga y un tamaño de cubo. Stripe contó cómo usa limitadores token bucket sobre Redis en su artículo sobre escalar una API con rate limiters, junto con un limitador aparte para peticiones concurrentes.

Los contadores de ventana deslizante son la forma barata de eliminar el problema del borde cuando prefieres un límite estricto por ventana.

Dónde va el limitador

Puedes aplicar límites en tres sitios, y muchos sistemas usan más de uno:

  • En el borde (CDN o WAF). Límites gruesos por IP para absorber avalanchas antes de que te lleguen.
  • En el API gateway. Límites por clave y por ruta que necesitan saber quién llama. Aquí viven la mayoría de los límites de producto.
  • Dentro de un servicio. Límites que dependen de datos del negocio, como «dos exportaciones simultáneas por cuenta».

Un gateway puede contar en local (cada instancia tiene sus contadores) o en global (todas las instancias comparten un almacén). Los límites locales no cuestan nada y bastan para una protección gruesa; divide el límite entre el número de instancias y acepta el error. Los límites de producto que pagan los clientes necesitan un almacén global. Envoy, por ejemplo, admite ambos: un filtro de límite local y un modo global que llama a un servicio externo de límites respaldado por Redis.

Contadores en Redis

Redis es la elección habitual para contadores globales: es rápido, guarda los contadores en memoria y hace caducar las claves por sí mismo. La trampa es la carrera entre leer un contador y volver a escribirlo. Dos gateways leen 99, ambos permiten la petición, ambos escriben 100.

Haz que cada decisión sea atómica. Para una ventana fija basta con INCR seguido de EXPIRE sobre una clave nueva; la documentación de INCR en Redis muestra patrones de rate limiting construidos sobre él. Para un token bucket, ejecuta un pequeño script Lua que lea el cubo, lo rellene según el tiempo transcurrido, tome un token y lo escriba de vuelta en un solo paso. Redis ejecuta un script sin intercalar otros comandos, así que las peticiones concurrentes ven un cubo coherente.

-- KEYS[1] = bucket key, ARGV = rate per second, capacity, now (ms), cost
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local rate, cap, now, cost = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4])
local tokens = tonumber(b[1]) or cap
local ts = tonumber(b[2]) or now
tokens = math.min(cap, tokens + (now - ts) / 1000 * rate)
local allowed = tokens >= cost
if allowed then tokens = tokens - cost end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate * 1000))
return allowed and 1 or 0

Pasa la hora desde quien llama o usa el comando TIME de Redis; mezclar los relojes de muchos gateways es una fuente habitual de resultados raros. Si el mismo Redis sirve también de caché, vigila su política de expulsión: un cubo expulsado es un límite reiniciado. El artículo sobre estrategias de caché trata los ajustes de expulsión.

Avisar al cliente

Responde a una petición limitada con 429 Too Many Requests, definido en el RFC 6585, e incluye una cabecera Retry-After con los segundos que debe esperar. Los buenos clientes se retiran; los demás al menos reciben una señal clara. Un borrador del IETF, RateLimit header fields for HTTP, propone cabeceras estándar que dicen a los clientes su cuota y su presupuesto restante antes de que lleguen al límite; en el momento de escribir esto (octubre de 2026) sigue siendo un borrador, así que trata como provisionales los nombres de cabecera que propone.

Documenta los límites. Un límite que los clientes descubren al ser bloqueados se convierte en un ticket de soporte.

Cuando Redis cae

Decídelo antes de que ocurra:

  • Fail open. Deja pasar las peticiones cuando el limitador no pueda decidir. La API sigue en pie y pierdes la protección mientras dure. Es la opción habitual para los límites de producto.
  • Fail closed. Rechaza las peticiones. Es lo correcto para operaciones caras o propensas al abuso, como los intentos de inicio de sesión o el envío de SMS.
  • Volver a límites locales. Cada gateway aplica un límite local grueso hasta que vuelva el almacén.

Elijas lo que elijas, pon una alerta. Un limitador que pasa una semana en fail open sin que nadie lo sepa no es un limitador.

El diagrama de arquitectura

Los clientes de la API llegan a un balanceador de carga y a un API gateway. El gateway consulta un servicio de límites que actualiza contadores en Redis con un script Lua atómico. Una decisión envía lo que está bajo el límite a la API Orders y el resto a un 429 con Retry-After.

10 componentes · 8 conexiones

Abrir en Archboard
Contenido del diagrama
  • API clients
  • Load balancer
  • API gateway
  • Rate limit service
  • Redis: counters
  • Under the limit?
  • Orders API
  • 429 + Retry-After
  • Metrics
  • key + route
  • atomic Lua script
  • yes
  • no
  • allowed vs rejected
Un rate limiter distribuido detrás de un API gateway. Un servicio aplica las reglas, guarda los contadores en Redis con scripts atómicos, responde 429 con Retry-After cuando un cliente supera su límite e informa de las peticiones permitidas y rechazadas.

Cómo leerlo:

  • De izquierda a derecha va la ruta de la petición: los clientes, el balanceador de carga, el API gateway y el servicio de límites.
  • El servicio de límites es dueño de las reglas y toma una decisión por petición. Guarda los contadores en Redis y los actualiza con un script Lua atómico.
  • El nodo de decisión envía las peticiones permitidas a la API Orders. El resto recibe un 429 con Retry-After y nunca llega al backend.
  • Las métricas son discontinuas porque se informan de forma asíncrona. Los rechazos por regla son lo primero que hay que mirar cuando un cliente dice que está bloqueado.
  • El bloque Markdown enumera las reglas en el orden en que se comprueban, incluido el modo de fallo.

Qué poner en el diagrama

  • Las reglas y sus claves. «100/min» no significa nada sin «por clave de API» o «por IP».
  • Conteo local o global. La diferencia decide si los límites se cumplen entre instancias.
  • El modo de fallo. Fail open o fail closed debe estar en el diagrama, no solo en el código.
  • La ruta de rechazo. Muestra dónde se genera un 429 para que nadie suponga que el backend ve esas peticiones.

Para el propio gateway, el diagrama de arquitectura de un API gateway trata el enrutamiento, la autenticación y los demás filtros que se ejecutan junto al limitador. El ejemplo de diagrama de diseño de sistemas muestra cómo encaja un limitador en un diseño más grande.

Constrúyelo en Archboard

  1. Inicia sesión con Google o GitHub y crea un diagrama.
  2. Del pack Modelo C4, añade una Persona para los clientes, y luego del pack Nube un Balanceador de carga en la nube y una Puerta de enlace de API.
  3. Añade dos nodos Contenedor del pack Modelo C4: el servicio de límites y la API Orders. Añade Redis del pack Datos debajo del servicio de límites.
  4. Añade una Decisión y un Inicio / Fin del pack Diagrama de flujo para la rama de permitir o rechazar, y Métricas del pack DevOps.
  5. Etiqueta las aristas, dibuja discontinua la arista de métricas y añade un bloque Markdown del pack Básico con las reglas.

El ejemplo usa 10 nodos de diagrama, dentro del plan Free.

Preguntas frecuentes

¿Qué algoritmo de rate limiting debo usar? Token bucket para la mayoría de las APIs, porque permite las ráfagas cortas que producen los clientes reales. Usa un contador de ventana deslizante si necesitas un límite estricto por ventana sin el coste de un log completo de peticiones.

¿Debe el rate limiter ser un servicio aparte? No necesariamente. Un filtro del gateway con un Redis compartido basta para muchos sistemas. Un servicio aparte ayuda cuando muchos gateways y servicios comparten las mismas reglas.

¿Qué código de estado debe recibir una petición limitada? 429 Too Many Requests, con una cabecera Retry-After. No uses 503: dice a los clientes que tu servicio está caído, no que han pedido demasiado.

¿Cómo limito por usuario cuando las peticiones pasan por muchos servidores? Guarda los contadores en un almacén compartido como Redis, con el usuario o la clave de API como clave, y actualízalos de forma atómica. Los contadores locales por servidor solo sirven como tope aproximado.

¿Qué pasa si falla el propio rate limiter? Elígelo de antemano. Fail open para los límites de producto normales, fail closed para operaciones caras o propensas al abuso, y una alerta en ambos casos.

Empieza gratis — un puesto, sin tarjeta de crédito.

Artículos relacionados

← Volver al blog

Abre un lienzo en un minuto

Inicia sesión con Google o GitHub. Gratis para un asiento. Mejora el plan cuando se una el equipo.