Guía 13

Fiabilidad de bots: límites de tasa, colas, idempotencia y monitorización

Un bot que funciona con diez usuarios suele fallar con diez mil. Las soluciones son conocidas: encolar, limitar, deduplicar, fijar tiempos de espera y vigilar.

Guía4 min de lecturaActualizado

Conoce los límites

Plataforma Límites típicos (consulta la documentación vigente) Señal
Telegram Aprox. 1 msg/s por chat, 20 msg/min por grupo, unos 30 msg/s en total 429 con parameters.retry_after
Discord Buckets por ruta más unas 50 pet/s globales; límites más estrictos en algunas acciones Cabeceras X-RateLimit-*, 429 con retry_after
Slack Niveles por método; chat.postMessage aprox. 1 msg/s por canal 429 con Retry-After
WhatsApp Rendimiento por número y niveles diarios de destinatarios únicos Códigos de error en la respuesta
APIs de IA Peticiones y tokens por minuto 429, cabeceras propias del proveedor

Los límites son un contrato. Respetarlos sale más barato que recuperarse de un bloqueo.

Maneja bien el 429

Cuando recibas un 429, espera el tiempo que indica el servidor (Retry-After, retry_after) antes de reintentar esa ruta. No reintentes de inmediato en bucle: alarga la penalización y puede provocar bloqueos por IP. Bibliotecas como discord.js y grammY (con su plugin throttler) lo gestionan por ti; si haces llamadas HTTP directas, centralízalas en un cliente que lo haga.

Limita los mensajes salientes

Un token bucket permite ráfagas cortas mientras impone una tasa media:

class TokenBucket {
  constructor(ratePerSec, burst) { this.rate = ratePerSec; this.cap = burst; this.tokens = burst; this.last = Date.now(); }
  async take() {
    for (;;) {
      const now = Date.now();
      this.tokens = Math.min(this.cap, this.tokens + ((now - this.last) / 1000) * this.rate);
      this.last = now;
      if (this.tokens >= 1) { this.tokens -= 1; return; }
      await new Promise(r => setTimeout(r, ((1 - this.tokens) / this.rate) * 1000));
    }
  }
}

const global = new TokenBucket(25, 25);          // por debajo del límite de difusión de Telegram
const perChat = new Map();                       // chatId -> TokenBucket(1, 1)

async function send(chatId, payload) {
  if (!perChat.has(chatId)) perChat.set(chatId, new TokenBucket(1, 1));
  await global.take(); await perChat.get(chatId).take();
  return telegram("sendMessage", { chat_id: chatId, ...payload });
}

Con varias instancias, guarda el bucket en un almacén compartido (Redis, un Durable Object) o envía todo a través de un único worker.

Colas

Pon en una cola todo lo lento o con picos: llamadas a IA, difusiones, procesamiento multimedia, llamadas a APIs de terceros. Ventajas: respuestas rápidas al webhook, carga suave, reintentos automáticos y una cola de mensajes fallidos para los que fallan una y otra vez, de modo que un elemento defectuoso no bloquee al resto. Haz idempotentes a los consumidores, porque las colas entregan al menos una vez.

Idempotencia y orden

  • Deduplica los eventos entrantes por ID (update_id, event_id de Slack, id de mensaje de WhatsApp, ID de interacción).
  • En conversaciones de varios pasos, guarda la transición de estado con un número de versión y rechaza actualizaciones obsoletas.
  • No supongas orden entre chats distintos; dentro de un chat, procesa en secuencia si el orden importa.

Tiempos de espera y circuit breakers

Toda llamada saliente necesita un tiempo de espera. Si una dependencia (un proveedor de IA, una base de datos) está fallando, deja de llamarla durante un rato (circuit breaker) y responde con un mensaje amable ("Tengo problemas ahora mismo, inténtalo en unos minutos") en lugar de acumular peticiones.

Apagado ordenado y despliegues

Ante SIGTERM, deja de aceptar trabajo nuevo, termina los manejadores en curso dentro de un plazo, vacía los registros y cierra conexiones. En bots de Gateway, evita ejecutar dos instancias con el mismo shard durante un despliegue. En bots de webhooks los despliegues son transparentes si la versión antigua y la nueva pueden procesar eventos.

Observabilidad

  • Registros estructurados (JSON) con ID de evento, plataforma, manejador, resultado, latencia y un hash del ID de usuario.
  • Métricas: eventos recibidos, errores de manejadores, errores de API salientes por código, número de 429, profundidad y antigüedad de la cola, tokens de IA usados.
  • Alertas: tasa de errores por encima de lo normal, antigüedad de la cola en aumento, ningún evento durante un periodo inusual (webhook roto), 401 repetidos (token revocado).
  • Comando de estado: un /status solo para administradores que muestre versión, tiempo activo y profundidad de la cola es muy útil durante un incidente.

Pruebas

  • Guarda payloads reales (limpios) como fixtures y ejecuta los manejadores contra ellos en pruebas unitarias.
  • Simula las APIs de la plataforma, incluidas respuestas 429 y 5xx, para probar los caminos de reintento.
  • Usa un bot de staging en un chat privado o servidor de pruebas antes de cada versión.
  • Haz pruebas de carga de las difusiones con el limitador activo, contra un simulador, nunca contra usuarios reales.

Comprueba lo aprendido

1. Ante un 429, ¿qué debe hacer el cliente?
2. ¿Qué contiene una cola de mensajes fallidos?