Transformando a event-driven un monolito con Celery y Redis

En este documento, se explica la incorporación de Redis en el sistema de un cliente que se especializa en recursos humanos; particularmente en un nuevo flujo de desarrollo que está en curso.

Andrés Milla
Flux IT Thoughts
Published in
5 min readApr 25, 2024

--

Contexto

Actualmente, nuestro cliente está buscando desacoplar el flujo de personas usuarias de la aplicación principal, ya que en este momento esta funcionalidad se implementa en un monolito en dónde se encuentra todo el flujo del sistema. Esto produce que, al contar con una gran cantidad de personas usuarias concurrentes, se provoquen downtimes muy prolongados y locks en la base de datos por simplemente consultas read-only.

Por lo tanto, para resolver este problema, se planteó desacoplar el flujo de personas usuarias en un nuevo servicio utilizando una arquitectura event-driven; particularmente con los servicios de SQS y SNS pertenecientes a AWS.

Incorporación de Redis como message broker

Como se decidió optar por una arquitectura event-driven, el núcleo del sistema son los eventos que emite la aplicación principal. Por lo tanto, este proceso debe estar lo más optimizado posible para evitar tiempo ocioso en la comunicación.

Aquí es donde surge Redis como posible opción, debido a sus grandes facultades como message broker. De Redis, se puede decir que es un almacén de datos en memoria de código abierto utilizado por millones de Developers para una variedad de propósitos, como base de datos, caché, motor de transmisión de datos y “message broker” (intermediario de mensajes).

En el pasado, nuestro cliente implementaba el SQS listener manejando el evento recibido en el mismo thread. Esto posee numerosas desventajas:

1.Bloqueo del hilo principal: Al manejar eventos en el mismo hilo, cualquier operación de larga duración o bloqueante (como operaciones de red) puede afectar negativamente a otros eventos en espera. Esto podría ralentizar la capacidad de respuesta de la aplicación.
Por ejemplo, en el diagrama adjunto podemos ver que la duración total del manejo de todos los eventos sería 60 + 60 + 30 = 150 segundos, ya que los eventos se manejan en cascada.

2. Escalabilidad limitada: Si se reciben gran cantidad de eventos, no podemos escalar la solución mediante infraestructura, ya todo se procesa sobre un thread. Evidentemente, esto es un gran problema, debido a que el cliente posee una gran cantidad de personas usuarias por minuto y justamente esta fue la razón por la cual se comenzó a desarrollar un nuevo servicio.

Para resolver este problema, se incorporó Celery al proyecto utilizando Redis como back end. Esto permitió spawnear un thread de Celery por cada evento que se recibe, tal como puede observarse en el diagrama adjunto.

Aquí se puede notar que los puntos problemáticos mencionados anteriormente fueron resueltos, ya que el hilo principal se bloqueará lo menos posible, debido a que solo tiene que asociar el thread al evento recibido; y, por otro lado, Celery facilita la escalabilidad horizontal, permitiendo la ejecución de tareas en varios nodos o procesos. Podemos tener varios workers ejecutándose en paralelo para manejar tareas concurrentemente. Esto mejora significativamente la escalabilidad de la aplicación, ya que podemos agregar o quitar workers según las necesidades de carga.

A su vez, cabe destacar que la recuperación de fallos es más sencilla, debido a que Celery incluye mecanismos integrados para manejar fallos y reintentar tareas en caso de error. Podemos configurar políticas de reintento y manejar errores de manera robusta, mejorando la resiliencia de nuestro sistema.

Por último, resaltamos que al utilizar Redis como back end, se puede administrar y visualizar la ejecución de las tareas. Esto es de suma utilidad para debugging y observabilidad; ya que, conectándonos a Redis, vamos a poder visualizar el estado de cada tarea creada y generar métricas asociadas.

Incorporación de Redis como mecanismo de caché

Tal como se comentó en la sección de “contexto”, el cliente deseaba reducir el uso de la base de datos, ya que la frecuentaban una gran cantidad de personas usuarias concurrentes.

Una técnica eficaz para resolver esta problemática es implementar un mecanismo de caché. La idea clave detrás de este mecanismo es almacenar de forma temporal la información que se solicita con frecuencia en un lugar más rápido, como en una memoria intermedia. Al hacer esto, evitamos buscar constantemente información en la base de datos cada vez que alguien necesita acceder a ellos. La caché actúa como un acceso directo, permitiendo que la aplicación obtenga de manera rápida la información desde la memoria en lugar de realizar operaciones lentas en la base de datos.

Para ello, se configuró Redis como instancia de caché, ya que es una opción popular para implementar caching debido a su rapidez, versatilidad y capacidad para manejar grandes cantidades de datos en entornos con numerosas personas usuarias concurrentes. En específico, se utilizó la librería https://github.com/jazzband/django-redis para introducir Redis como back end de caché en Django.

Esto logró reducir los accesos a base de datos de end points públicos (como catálogos) y almacenar información temporal que requiera una gran cantidad de tiempo para ser calculada.

Por ejemplo, para obtener los recursos para la persona usuaria es necesario llamar a un end point de un servicio, lo que provoca un gran tiempo ocioso de red. Al utilizar caching, estos recursos son cacheados por un tiempo determinado en la primera request y las personas usuarias restantes no van a sufrir el tiempo de overhead mencionado previamente.

Conclusión

En conclusión, este documento aborda estrategias clave para optimizar los tiempos de procesamiento de eventos en entornos con alta concurrencia. La implementación de la arquitectura de message broker de Redis en conjunto con Celery demuestra ser eficaz para gestionar la ejecución de tareas de manera distribuida, mejorando así la capacidad de respuesta del sistema.

Adicionalmente, se exploró cómo la integración de Redis como un mecanismo de caché desempeñó un papel fundamental en la reducción de la carga sobre la base de datos y la red. Al almacenar temporalmente datos comúnmente alojados en la memoria principal, Redis permitió minimizar la necesidad de accesos frecuentes a la base de datos principal, contribuyendo así a una mejora significativa en el rendimiento general de la aplicación.

Estas estrategias combinadas ofrecen una solución integral para enfrentar desafíos asociados con la escalabilidad y eficiencia en sistemas con una gran cantidad de personas usuarias concurrentes, destacando la importancia de la elección adecuada de herramientas como Redis para optimizar el manejo de datos y eventos en tiempo real.

Links y referencias

Conocé más sobre Flux IT: Website · Instagram · LinkedIn · Twitter · Dribbble · Breezy

--

--