Escalando III: Fiabilidad
Si te aseguraste de tener un adecuado manejo de errores para tu bot, básicamente estás listo para ir. Todos los errores que se espera que ocurran (llamadas a la API que fallan, solicitudes de red que fallan, consultas a la base de datos que fallan, middleware que falla, etc) son capturados.
Deberías asegurarte de siempre esperar
todas las promesas, o al menos llamar a catch
en ellas si alguna vez no quieres esperar
cosas. Usa una regla de linting para asegurarte de que no puedes olvidar esto.
Apagado correcto
Para los bots que utilizan long polling, hay una cosa más a considerar. Como vas a detener tu instancia durante la operación en algún momento de nuevo, deberías considerar la captura de eventos SIGTERM
y SIGINT
, y llamar a bot
(long polling incorporado) o detener tu bot a través de su manejador (grammY runner):
Simple long polling
import { Bot } from "grammy";
const bot = new Bot("");
// Detener el bot cuando el proceso de Node.js
// está a punto de terminar
process.once("SIGINT", () => bot.stop());
process.once("SIGTERM", () => bot.stop());
await bot.start();
2
3
4
5
6
7
8
9
10
const { Bot } = require("grammy");
const bot = new Bot("");
// Detener el bot cuando el proceso de Node.js
// está a punto de terminar
process.once("SIGINT", () => bot.stop());
process.once("SIGTERM", () => bot.stop());
await bot.start();
2
3
4
5
6
7
8
9
10
import { Bot } from "https://deno.land/x/grammy@v1.30.0/mod.ts";
const bot = new Bot("");
// Detener el bot cuando el proceso de Deno
// está a punto de terminar
Deno.addSignalListener("SIGINT", () => bot.stop());
Deno.addSignalListener("SIGTERM", () => bot.stop());
await bot.start();
2
3
4
5
6
7
8
9
10
Usando grammY runner
import { Bot } from "grammy";
import { run } from "@grammyjs/runner";
const bot = new Bot("");
const runner = run(bot);
// Detener el bot cuando el proceso de Node.js
// está a punto de terminar
const stopRunner = () => runner.isRunning() && runner.stop();
process.once("SIGINT", stopRunner);
process.once("SIGTERM", stopRunner);
2
3
4
5
6
7
8
9
10
11
12
const { Bot } = require("grammy");
const { run } = require("@grammyjs/runner");
const bot = new Bot("");
const runner = run(bot);
// Detener el bot cuando el proceso de Node.js
// está a punto de terminar
const stopRunner = () => runner.isRunning() && runner.stop();
process.once("SIGINT", stopRunner);
process.once("SIGTERM", stopRunner);
2
3
4
5
6
7
8
9
10
11
12
import { Bot } from "https://deno.land/x/grammy@v1.30.0/mod.ts";
import { run } from "https://deno.land/x/grammy_runner@v2.0.3/mod.ts";
const bot = new Bot("");
const runner = run(bot);
// Detener el bot cuando el proceso de Deno
// está a punto de terminar
const stopRunner = () => runner.isRunning() && runner.stop();
Deno.addSignalListener("SIGINT", stopRunner);
Deno.addSignalListener("SIGTERM", stopRunner);
2
3
4
5
6
7
8
9
10
11
12
Eso es básicamente todo lo que hay que hacer para la fiabilidad, su instancia debería:registrado: nunca™️ fallar ahora.
Garantías de fiabilidad
¿Qué pasa si tu bot está procesando transacciones financieras y debes considerar un escenario de kill
donde la CPU se rompe físicamente o hay un corte de energía en el centro de datos? Si por alguna razón alguien o algo realmente mata el proceso, la cosa se complica un poco más.
En esencia, los bots no pueden garantizar una ejecución exacta de su middleware. Lee esta discusión en Git
¿Sólo te interesa codificar un bot de Telegram? (/advanced/flood)
Webhook
Si estás ejecutando tu bot con webhooks, el servidor de la API del bot reintentará entregar actualizaciones a tu bot si no responde con OK
a tiempo. Esto define el comportamiento del sistema de forma exhaustiva—si necesitas evitar el procesamiento de actualizaciones duplicadas, deberás construir tu propia desduplicación basada en update
. grammY no hace esto por ti, pero siéntete libre de PR si crees que alguien más podría beneficiarse de esto.
Long Polling
El long polling es más interesante. El long polling incorporado básicamente vuelve a ejecutar el lote de actualización más reciente que fue obtenido pero no pudo completarse.
Ten en cuenta que si detienes adecuadamente tu bot con
bot
, el desplazamiento de la actualización se sincronizará con los servidores de Telegram llamando a.stop get
con el desplazamiento correcto pero sin procesar los datos de la actualización.Updates
En otras palabras, nunca perderás ninguna actualización, sin embargo, puede ocurrir que vuelvas a procesar hasta 100 actualizaciones que hayas visto antes. Como las llamadas a send
no son idempotentes, los usuarios pueden recibir mensajes duplicados de tu bot. Sin embargo, se garantiza el procesamiento de al menos una vez.
grammY Runner
Si estás utilizando el grammY runner en modo concurrente, la siguiente llamada a get
se realiza potencialmente antes de que tu middleware procese la primera actualización del lote actual. Por lo tanto, el desplazamiento de la actualización es confirmado prematuramente. Este es el coste de una gran concurrencia, y desafortunadamente, no puede evitarse sin reducir tanto el rendimiento como la capacidad de respuesta. Como resultado, si tu instancia es eliminada en el momento correcto (equivocado), podría ocurrir que hasta 100 actualizaciones no puedan ser obtenidas de nuevo porque Telegram las considera confirmadas. Esto lleva a la pérdida de datos.
Si es crucial prevenir esto, deberías usar las fuentes y sumideros del paquete grammY runner para componer tu propio pipeline de actualización que pase todas las actualizaciones a través de una cola de mensajes primero.
Básicamente tendrías que crear un sink que empuje a la cola, e iniciar un corredor que sólo alimente tu cola de mensajes.
A continuación, tendría que crear un source que se alimente de la cola de mensajes de nuevo. Efectivamente, ejecutará dos instancias diferentes del corredor grammY.
Este vago borrador descrito arriba sólo ha sido esbozado pero no implementado, según nuestro conocimiento. Por favor, ponte en contacto con el grupo de Telegram si tienes alguna pregunta o si intentas esto y puedes compartir tu progreso.
Por otro lado, si tu bot está bajo una gran carga y el sondeo de actualizaciones se ralentiza debido a las restricciones de carga automática, aumentan las posibilidades de que algunas actualizaciones sean recuperadas de nuevo, lo que lleva a la duplicación de mensajes de nuevo. Por lo tanto, el precio de la concurrencia total es que no se puede garantizar el procesamiento al menos una vez ni como máximo una vez.