Pub/Sub con Redis
Durante esta semana tuve que hacer refactoring de una parte del sistema en el que trabajo tenemos un portal de cliente en el que los usuarios pueden seleccionar reportes a descargar y se genera un pdf o un zip con toda la información requerida.
El problema era que estos reportes pueden llegar a ser bastante grandes y estaban dando timeouts al generarlos online durante el request HTTP, dejando a los usuarios en espera con una pantalla en blanco sin saber cual fue el error cuando falla.
Hicimos algunas optimizaciones para generar el reporte más rápido pero aún así podía demorar algunos minutos, queríamos hacer algo que no le deje al usuario sin feedback, ya teníamos la opción de poner la generación de este reporte en una cola para que sea consumida por unos procesos en background, pero el usuario no sabía que estaba haciendo el sistema durante su espera hasta recibir el email de que el reporte ha finalizado.
Así que decidimos siempre generar los reportes en background, pero dándole al usuario un monitoreo del proceso mientras se genera. Para esto investigue las opciones existentes para mostrar mensajes generados durante el proceso de creación del reporte. La opción que mejor se acoplaba era enviar mensajes desde el servidor web usando SSE (Server Sent Events) de tal forma que no se necesite hacer demasiados requests haciendo polling, pero hubo que hacer algunas configuraciones en nginx para no hacer buffering de la respuesta de la aplicación pero una vez hecho eso, desde el front end se crea un EventSource y en cada mensaje se muestra información en pantalla. Primera parte resuelta, pero ya que el proceso que está generando el reporte no tiene forma de comunicarse con el servidor para mostrar el porcentaje de avance se necesita un middleman para emparejar ambos, es aquí donde necesitaba un sistema PUB/SUB (Publisher/subscriber). Si hubiera estado usando postgres esto ya lo tiene resuelto, usando LISTEN and NOTIFY. Pero como usamos mariadb en este proyecto hubo que agregar un nuevo componente al sistema.
El componente fue redis, sabía de este pero no lo había usado antes, es un almacenamiento en memoria de diferentes estructuras de datos, se puede usar como base de datos, cache o broker de mensajes. La característica que estamos usando en este momento se llama Pub/Sub , y lo que permite es publicar mensajes a un canal específico y otros procesos pueden suscribirse a escuchar mensajes de un canal o canales que le interesa.
Para implementarlo simplemente agregue redis como dependencia en mi docker-compose.yml e
instale el paquete php-redis para poder tener las clases que me permitan conectarme al servidor de redis,
una vez hecho eso el código es bastante simple, en la clase que maneja los procesos de background
simplemente se hace un $redis->publish('job_1', "Iniciando reporte");
y en el controller
que maneja los SSE se hace un $redis->listen(['job_1'], fn ...);
donde fn es el callback que se llama
cada vez que se envía un nuevo mensaje al canal denominado job_1, este nombre de canal es
dinámico.
Este sistema ha estado trabajando muy bien desde la puesta a producción, el consumo de memoria de redis tampoco es elevado, le puse 1GB de RAM pero solo consume 3 MB.