Optimizando la Entrega de Mensajes: De Cronjobs a Redis
En el corazón de muchas operaciones de TI, encontramos sistemas de mensajería que necesitan entregar información puntual a través de diversos canales, entre ellos Push Notifications, Emails, Webhooks, SMS, etc.
Normalmente y la forma más sencilla de abordar este reto es guardando los mensajes en una base de datos y haciendo consultas periódicas para evaluar si los mensajes deben enviarse en el momento de que la consulta fue realizada. Sin embargo, con el crecimiento en volumen y expectativas de rendimiento, surge une nueva necesidad de optimizar. ¿Pero cómo pasamos de un sistema reactivo, basado en cronjobs, a un sistema proactivo y eficiente? Para ello Redis es el primer frente y aquí te contaré como cambié la estrategia de un sistema basado en pulls hacia la base de datos hacia Pub/Sub utilizando Redis.
El Reto de los Cronjobs y Consultas Constantes a la Base de Datos
Inicialmente, el sistema que evalué dependía de queues y cronjobs, donde el queue recibía mensajes y a su vez decidía si se enviaran en el momento o los almacenaba en la base de datos para ser enviados despues, para ello se ejecutaban cada minuto una tarea que consultaba en la base de datos buscando mensajes pendientes de envío. Aunque funcional, esta metodología presentaba varios inconvenientes:
- Carga en la Base de Datos: Cada consulta ejerce presión adicional sobre la base de datos, lo que puede ser costoso y poco escalable.
- Latencia: La naturaleza periódica de los cronjobs significa que los mensajes sólo se verifican en intervalos definidos, no en tiempo real.
- Complejidad de Mantenimiento: A medida que el sistema crece mediante nuevas notificaciones, la gestión de conexiones a la base de datos se vuelve más compleja y propensa a errores.
Explorando Estrategias para la Optimización del Envío de Mensajes
Antes de llegar a Redis, pasé por otras estrategias que pudieron haber ayudado a mejorar el desempeño del sistema, tales como fueron:
Triggers de Base de Datos
Los triggers de base de datos son procedimientos automáticos que se activan o se ejecutan en respuesta a ciertos eventos en una tabla de la base de datos, como inserciones, actualizaciones o eliminaciones. Por ejemplo, cuando un nuevo mensaje se encola en una tabla específica, un trigger podría ejecutarse automáticamente para iniciar un proceso de envío de mensajes.
Ventajas: Los triggers funcionan en tiempo real y pueden reducir la necesidad de sondear la base de datos para detectar nuevos mensajes. Esto simplifica la lógica de la aplicación al delegar la responsabilidad de iniciar acciones al sistema de gestión de bases de datos (DBMS).
Desventajas: Sin embargo, pueden llevar a una lógica de aplicación dispersa, ya que parte de ella reside fuera del código de la aplicación y en la base de datos. Esto puede hacer que el sistema sea más difícil de entender y mantener. Además, los triggers pueden afectar negativamente el rendimiento de la base de datos, especialmente si se ejecutan operaciones complejas o largas, y no son tan escalables en sistemas con un volumen de datos muy alto, donde el rendimiento y la capacidad de respuesta son críticos.
Colas de Mensajes con Retraso
Las colas de mensajes con retraso son estructuras de datos que permiten a los desarrolladores encolar mensajes que deben ser procesados después de un cierto período de tiempo, en lugar de inmediatamente. Esto se logra estableciendo un tiempo de espera antes de que el mensaje esté disponible para ser consumido por el trabajador o el servicio que procesará el mensaje.
Ventajas: Esta funcionalidad es útil para las tareas programadas que no necesitan ejecutarse inmediatamente, sino en un momento específico en el futuro, como enviar recordatorios o realizar acciones que están condicionadas por tiempo. Pueden ayudar a aliviar la carga inmediata sobre otros sistemas, como bases de datos, al no requerir que los mensajes se procesen instantáneamente.
Desventajas: Requieren un mecanismo para monitorear y ejecutar los mensajes una vez que su retraso ha expirado, lo que puede añadir complejidad a la infraestructura del sistema. Además, si no se manejan correctamente, pueden surgir problemas como la saturación de la cola, donde los mensajes se acumulan más rápido de lo que pueden ser procesados. También hay que considerar la integración con sistemas de colas existentes o la necesidad de introducir nuevos servicios, lo que puede tener implicaciones en términos de costos y mantenimiento. Generalmente este tipo de alternativas no ofrecen reintentos por lo que se deben de manejar por separado.
Sistemas de Eventos en Tiempo Real
Los sistemas de eventos en tiempo real se refieren a tecnologías y arquitecturas que permiten la comunicación instantánea entre emisores y receptores de mensajes. Estos sistemas detectan eventos o cambios en el estado de un sistema y transmiten esta información inmediatamente a los interesados, permitiendo reacciones casi instantáneas.
Ventajas: Proporcionan actualizaciones en vivo sin la necesidad de sondeo o interrogación constante de la base de datos, lo que reduce la latencia y mejora la capacidad de respuesta del sistema. Son ideales para aplicaciones que requieren interactividad y actualizaciones en tiempo real, como chat en vivo, juegos en línea, y dashboards en tiempo real.
Desventajas: Aunque potentes, los sistemas de eventos en tiempo real pueden ser complejos de implementar y mantener. Requieren una conexión persistente y un manejo cuidadoso de los recursos del servidor para evitar sobrecargas. Además, la garantía de entrega de mensajes puede ser un desafío, ya que la pérdida de conexión podría significar la pérdida de mensajes importantes si no se implementan mecanismos de respaldo adecuados.
Colas Priorizadas
Las colas priorizadas son una estructura de datos especializada utilizada en sistemas de mensajería y de colas de trabajo que permite gestionar mensajes o tareas basándose en su prioridad. En lugar de procesar en un orden de llegada (primero en entrar, primero en salir), este sistema permite que ciertos mensajes tengan preferencia sobre otros.
Ventajas: Son útiles cuando se necesita asegurar que tareas críticas sean procesadas antes que tareas menos urgentes, mejorando así la eficiencia y la efectividad del procesamiento de mensajes. Esto es particularmente beneficioso en entornos donde los recursos son limitados y deben ser asignados a las tareas más importantes primero.
Desventajas: La gestión de las prioridades puede volverse complicada, especialmente si el volumen de mensajes es alto o si las reglas de prioridad cambian con frecuencia. Además, si no se diseña cuidadosamente, puede conducir a la inanición de mensajes de baja prioridad, donde nunca se llegan a procesar porque siempre hay mensajes de mayor prioridad en cola. Esto requiere un diseño cuidadoso y una gestión continua para asegurar un balance adecuado en el procesamiento de las tareas.
Servicios de Colas de Mensajes Avanzados
Los servicios de colas de mensajes avanzados son sistemas de software que ofrecen funcionalidades sofisticadas para manejar la transmisión y el procesamiento de mensajes entre diferentes partes de una aplicación. Ejemplos prominentes incluyen Apache Kafka, RabbitMQ y Amazon SQS, que proporcionan capacidades más allá de las colas simples, tales como tolerancia a fallos, escalabilidad horizontal, replicación y persistencia de mensajes.
Ventajas: Estos sistemas están diseñados para manejar altos volúmenes de datos y ofrecen garantías fuertes en cuanto a la entrega y el orden de los mensajes. También son altamente configurables y pueden ser ajustados para satisfacer los requisitos de rendimiento de casi cualquier aplicación de nivel empresarial.
Desventajas: La complejidad y la sobrecarga de gestión son las principales desventajas. Requieren una configuración inicial significativa y una gestión continua. Además, pueden ser excesivos para aplicaciones más pequeñas o para equipos que no cuentan con la experiencia necesaria para implementarlos y mantenerlos eficientemente. También, el costo puede ser un factor, especialmente para soluciones basadas en la nube que cobran por el volumen de mensajes y el ancho de banda utilizado.
Redis y BullMQ: Proactividad y Resilencia
La elección de Redis, complementado por BullMQ, no fue casualidad. Redis ya era una parte de pila tecnológica utilizada internamente, por lo que la experiencia y el conocimiento previo jugaron un papel crucial en la decisión, ambas herramientas se pueden clasificar dentro de las siguientes categorías mencionadas previamente como:
-
Sistemas de Eventos en Tiempo Real: Redis, con sus capacidades de publicación/subscripción (pub/sub), puede manejar eventos en tiempo real permitiendo que los sistemas reactiven inmediatamente a ciertas acciones o señales.
-
Servicios de Colas de Mensajes Avanzados: BullMQ es un avanzado sistema de colas de trabajo que se construye sobre Redis. Ofrece una serie de funcionalidades robustas para la gestión de colas, incluyendo priorización de tareas, programación, y manejo de reintentos, todo lo cual se considera parte de las características que se esperan de un servicio de colas avanzado.
Redis no es un sistema de colas de mensajes con retraso per se, pero puede ser configurado para actuar como uno, especialmente cuando se usa en conjunto con BullMQ. BullMQ utiliza las capacidades de Redis para implementar características como retrasos en los trabajos y programaciones repetitivas, que son típicas en los servicios de colas de mensajes con retraso.
Por lo tanto, Redis en combinación con BullMQ ofrece un conjunto de herramientas que cubre múltiples categorías de sistemas de mensajería y procesamiento de tareas en segundo plano, brindando tanto la capacidad de eventos en tiempo real como las funciones avanzadas de manejo de colas de mensajes.
Consejos y Aprendizajes
La migración de un sistema basado en cronjobs a una arquitectura basada en Redis y BullMQ fue un proceso bastante rápido, del cual salieron valiosas lecciones que pueden beneficiar a otros devs en su búsqueda de eficiencia y escalabilidad.
Desacoplando Cronjobs e Implementando el Pub/Sub
La decisión de alejarse de los cronjobs fue fundamental, el antiguo sistema, basado en la ejecución periódica y constante de tareas programadas, representaba una carga pesada para la base de datos, la meta era clara: eliminar el sondeo constante y adoptar un enfoque más proactivo y eficiente.
Consejo: En lugar de hacer que tu servicio de fondo verifique nuevas tareas, invierte en una arquitectura que notifique a tus servicios de manera proactiva. El patrón pub/sub de Redis es perfecto para este propósito, reduciendo la carga y mejorando la responsividad.
Optimización de la Lógica de Mensajería
Anteriormente, la lógica del sistema dependía del tiempo de ejecución del cronjob para determinar si era el momento de enviar un mensaje. Ahora, al encolar mensajes en Redis, se establece su tiempo de entrega, de tal forma que se deja que BullMQ se encargue del resto. El mensaje se entrega al worker adecuado justo a tiempo, sin la necesidad de sondeos repetitivos.
Consejo: Evalúa tu lógica de mensajería actual y busca oportunidades para optimizar. Al encolar mensajes con un tiempo de entrega programado, se puede confiar en la precisión y eficiencia del sistema de colas para manejar la entrega.
Manejando Mensajes en Grupo
Durante este proceso de migración, descubrí la existencia de mensajes que desencadenaban otros mensajes, claro que evalué este tipo de mensajes "especiales" buscando en la base de datos la periocidad y cantidad de mensajes con que se enviaban. La forma en que decidí resolverlo fue, verificar y validar la existencia de múltiples mensajes, agrupándolos y agregando una nueva propiedad que contuviera los identificadores necesarios para enviarlos luego de forma individual. Esto simplificó enormemente el proceso de envío, ya que ahora, un único mensaje se encarga de informar a todos los usuarios relevantes.
Consejo: Si te enfrentas a una situación similar, considera la agrupación de mensajes. Esto puede reducir significativamente el número de operaciones y la complejidad, lo que resulta en un rendimiento mejorado y una lógica más limpia.
Monitoreo y Manejo de Errores
La implementación de un sistema de monitoreo de errores y reintentos mejoró la resiliencia, anteriormente no se tenía una visión clara de los fallos ni una manera sistemática de manejarlos, por lo que después de los cambios realizados, cada mensaje ofrece la oportunidad de ser reenviado automáticamente en caso de un problema, asegurando la entrega.
Consejo: No subestimes el poder de un buen sistema de monitoreo y manejo de errores. Esto no solo mejora la confiabilidad del servicio, sino que también proporciona datos valiosos para la optimización continua.
Reflexión Final
El actual sistema en si no era tan complejo y tomó apenas una semana en realizar la migración, el mayor reto que se presentó no fue el pasar de cronjobs hacia pub/sub, sino que fue el hecho de intentar agrupar mensajes, ya que se tuvo que separar funciones grandes en algunas más pequeñas para mantener la lógica anterior en los nuevos cambios, crear nuevos métodos y consultas para manejar los mensajes, todo para garantizar una transición sin problemas.
Definitivamente, el camino hacia la optimización tuvo un éxito notable, pero es solo un paso en un camino de mejora continua. La implementación ha probado ser más que una solución temporal que por ahora a ayudado a ahorrar recursos; elevando el rendimiento de la aplicación a un nuevo nivel. Quizás más adelante y dependiendo de la demanda y crecimiento de mensajes, esto cambie a algo más robusto utilizando nuevas herramientas, pero mientras tanto esta solución es limpia, ligera y efectiva.
Happy coding! :D
Photo by Chris Briggs on Unsplash