Marcio Cunha

Limitación de Tasa Distribuida con Token Bucket en Arquitecturas Serverless

Aprenda a controlar el flujo de solicitudes en aplicaciones sin servidores fijos utilizando el algoritmo Token Bucket y bases de datos distribuidas.

Marcio Cunha•5 min
También disponible en:EnglishPortuguês
Resumen
  • Las arquitecturas serverless escalan infinitamente, pero dejan las APIs expuestas a picos de tráfico descontrolados y ataques de denegación de servicio.
  • El algoritmo Token Bucket funciona como un contenedor que almacena fichas liberadas a un ritmo constante para autorizar el acceso de los clientes.
  • Los sistemas sin estado exigen el uso de almacenamientos centrales de alta velocidad como Redis para sincronizar contadores entre múltiples instancias.
  • Garantizar la consistencia atómica en operaciones de lectura y escritura evita que accesos simultáneos burlen los límites de la lógica de negocio.
  • El principal compromiso implica aceptar latencias de red mínimas a cambio de una protección robusta de los servicios de back-end.

El Desafío de Controlar Accesos en Nubes Elásticas

Cuando construimos aplicaciones modernas en la nube utilizando funciones que se ejecutan bajo demanda, eliminamos la necesidad de gestionar servidores físicos o máquinas virtuales dedicadas. Esta flexibilidad permite que el sistema crezca instantáneamente para atender a millones de usuarios y se reduzca a cero cuando no hay tráfico. En la práctica, esto significa que pagamos solo por los milisegundos exactos en que el código está corriendo. Sin embargo, esta misma elasticidad abre brechas peligrosas para abusos, como robots automatizados extrayendo datos o ataques maliciosos de denegación de servicio que pueden inflar costos financieros de forma silenciosa.

Proteger estas puertas de entrada requiere mecanismos conocidos como limitación de tasa, que actúan como los guardias de seguridad de una discoteca limitando la entrada de personas por minuto. En entornos tradicionales con servidores fijos, mantener este control es relativamente simple porque el conteo de accesos puede residir en la memoria local de la máquina. En arquitecturas sin servidores fijos, las funciones de código se activan, procesan una solicitud y mueren en fracciones de segundo, esparciendo instancias por decenas de servidores en todo el mundo. Sin un punto central de coordinación, cada función decidiría por su cuenta quién entra, rompiendo por completo la regla de límite global.

Cómo Funciona el Algoritmo Token Bucket en la Práctica

Para resolver el dilema del control de flujo sin bloquear el sistema, los ingenieros utilizan algoritmos matemáticos inteligentes, siendo Token Bucket uno de los más populares y eficientes de la industria. Imagine un balde físico en la recepción de un evento que recibe fichas plásticas a un ritmo constante y predecible, digamos, diez fichas por segundo. Cuando un participante llega, debe retirar una ficha del balde para poder entrar. Si el balde está lleno, las fichas extras simplemente se desbordan y se pierden, estableciendo un techo máximo de capacidad acumulada.

Aplicando esta analogía al software, cada usuario o dirección IP posee un balde virtual almacenado en una base de datos de alto rendimiento. Cada solicitud HTTP entrante consume un token de este balde digital. Si hay fichas disponibles, la solicitud se procesa normalmente y la ficha es restada. Si el balde está vacío, el sistema rechaza inmediatamente el acceso devolviendo un código de error HTTP estándar que indica intentos excesivos. El gran triunfo de este modelo es permitir ráfagas legítimas de tráfico, ya que un usuario que estuvo inactivo acumula fichas y puede realizar varias acciones en rápida sucesión.

Superando el Desafío Distribuido con Almacenamiento Central

El talón de Aquiles de cualquier solución ejecutada en nubes elásticas es la sincronización de datos entre miles de instancias aisladas que procesan solicitudes simultáneamente. Si utilizamos variables locales en memoria dentro de cada función serverless, un usuario astuto podría disparar cien solicitudes y golpear cien instancias diferentes, burlando por completo el límite de tráfico establecido. En la práctica, esto requiere desacoplar el estado de control de la lógica de ejecución, trasladando el conteo de fichas a una base de datos distribuida externa y ultrarrápida.

La opción tecnológica más común para este rol es Redis, un sistema de almacenamiento en memoria RAM conocido por su velocidad asombrosa de lectura y escritura. Cuando se activa una función serverless, realiza una consulta rápida a Redis para verificar el saldo actual de tokens del cliente. Para evitar que solicitudes concurrentes alteren el mismo saldo incorrectamente, utilizamos operaciones atómicas o scripts ejecutados directamente en el servidor de caché. Esto garantiza que dos solicitudes que llegan exactamente en el mismo milisegundo no puedan consumir la misma ficha residual, manteniendo la integridad matemática de la regla de control.

Implementando la Lógica en Código Funcional

A continuación presentamos un ejemplo práctico utilizando JavaScript en una función serverless que interactúa con Redis para aplicar la lógica de Token Bucket. El código calcula cuántos tokens deben reponerse en función del tiempo transcurrido desde la última solicitud del usuario antes de permitir o negar el acceso.

const redis = require('redis');
const client = redis.createClient();

async function checkRateLimit(userId) {
  const key = `rate:bucket:${userId}`;
  const now = Date.now();
  const capacity = 10;
  const refillRate = 1; // tokens per second
  
  let bucket = await client.hGetAll(key);
  let tokens = bucket.tokens ? parseFloat(bucket.tokens) : capacity;
  let lastUpdate = bucket.lastUpdate ? parseInt(bucket.lastUpdate) : now;
  
  const elapsed = (now - lastUpdate) / 1000;
  tokens = Math.min(capacity, tokens + elapsed * refillRate);
  
  if (tokens < 1) {
    return { allowed: false, remaining: 0 };
  }
  
  tokens -= 1;
  await client.hSet(key, { tokens, lastUpdate: now });
  return { allowed: true, remaining: Math.floor(tokens) };
}

Este fragmento de código demuestra la simplicidad conceptual combinada con el rigor técnico necesario para entornos de alto volumen. Al almacenar el estado de forma estructurada en Redis, garantizamos que cualquier nueva instancia de función serverless activada a continuación tenga acceso inmediato y preciso al historial reciente de ese usuario específico.

Consideraciones Operacionales y Compromisos de Arquitectura

Adoptar una estrategia distribuida de control de acceso aporta ventajas de seguridad innegables, pero también introduce costos y complejidades operacionales que deben evaluarse con precaución. El primer compromiso relevante es la adición de latencia en la red. Mientras que las funciones serverless se ejecutan típicamente cerca del usuario en bordes geográficamente distribuidos, la base de datos centralizada puede ubicarse en una región específica, añadiendo docenas de milisegundos preciosos al tiempo total de respuesta de la API.

Otro punto crítico de atención se refiere a la resiliencia y la tolerancia a fallos. Si la base de datos centralizada de caché sufre una indisponibilidad temporal, toda la cadena de microservicios podría volverse inaccesible si la aplicación carece de un mecanismo de respaldo adecuado. En escenarios de alta criticidad, los ingenieros optan frecuentemente por permitir el tráfico temporalmente durante fallos en Redis, priorizando la disponibilidad del negocio por encima del bloqueo estricto de abusos durante breves ventanas de inestabilidad técnica. Para complementar esta implementación en entornos de producción, consulte también nuestros análisis técnicos sobre Limitacion de Trafico Distribuida en Serverless con Token Bucket y Redis Cluster y Aislamiento de Microservicios y Limitación de Tráfico con Cubo de Tokens.

Consideraciones Finales

Implementar control de tasa distribuido utilizando el algoritmo Token Bucket en entornos sin servidores fijos exige una alineación cuidadosa entre escalabilidad, consistencia de datos y velocidad de respuesta. Aunque la infraestructura elástica trae desafíos inéditos de sincronización, el uso de almacenamiento en memoria de alto rendimiento resuelve el problema de forma elegante y segura. Dominar estos conceptos garantiza que sus aplicaciones permanezcan protegidas, resilientes y financieramente previsibles incluso bajo picos extremos de acceso global.