¿Por qué utilizar colas de acciones?

Cómo el módulo Job Queue puede utilizarse para hacernos ganar tiempo

Odoo funciona de manera sincrónica en la mayoría de acciones que nuestros clientes realizan a través del ERP. Esto quiere decir que el sistema queda bloqueado hasta que finaliza aquello que se está haciendo. El tiempo es oro y, por eso, la rapidez es una de las características más valoradas a la hora de seleccionar entre diferentes opciones tecnológicas. A pesar de que Odoo mejora considerablemente en este y otros aspectos en cada una de sus versiones, sigue habiendo operaciones que requieren de una cantidad de tiempo importante. Importaciones de un gran número de elementos o el envío masivo de correos son algunos ejemplos de esas operaciones en las que Odoo llega incluso a sugerirnos que tomemos un café para hacer la espera más llevadera. Tener la posibilidad de incorporar algunas de estas acciones tan pesadas a una cola para que vayan ejecutándose en segundo plano, de manera asíncrona, es el objetivo del módulo Job Queue, proporcionado por la Odoo Community Association (OCA), la organización sin ánimo de lucro cuyo objetivo es potenciar la herramienta Odoo y compartir los avances y el conocimiento de esta plataforma. En este artículo se explican algunas de sus características y se presenta Asynchronous Invoice Email, un módulo creado por Sygel Technology que hace uso de las colas de acciones.


Sin entrar demasiado en detalles técnicos, y de manera esquemática, hay dos elementos clave en la aplicación de tareas asíncronas a través de Job Queue:

  • El decorador @job, que permite definir una función como susceptible de ponerse en una cola.

  • El método with_delay, a través del que llamamos a la función decorada con @job para indicar que queremos ponerla a la cola.

Simplemente a través de esos dos elementos puede empezar a construirse una cola de diferentes acciones que se irán ejecutando de manera totalmente transparente para el usuario, quien podrá seguir trabajando mientras las funciones se llevan a cabo en segundo plano. Sin embargo, para optimizar al máximo este módulo y sacarle todo el partido posible, pueden configurarse toda una serie de parámetros.


Configuración del método with_delay


Empecemos por ver qué parámetros podemos incluir en el método with_delay cuando llamamos a una función a través de él:

  • Prioridad, establecida a través de un número entero, siendo 0 la máxima prioridad y 10 la prioridad por defecto. Esto es útil para indicar que en la cola hay algunas acciones que deben ejecutarse antes que otras.

  • Tiempo estimado de llegada (ETA), para indicar que la acción retrasada no debe ejecutarse antes de un punto en el tiempo determinado. ¿Quieren enviarse presupuestos a varios clientes pero alguno de ellos podría sufrir alguna modificación en las próximas horas? Si es así, quizás deberíamos indicar que el envío se ponga a la cola pero que no se realice hasta pasadas unas horas.

  • Número de intentos máximos de ejecución antes de marcar el trabajo como fallado. Si no se indica lo contrario, de manera predefinida se establecerá el valor en 5 intentos. Existe la posibilidad de indicar intentos infinitos con el valor 0.

  • Descripción de la tarea.

  • Canal sobre el que se pondrá la tarea, concepto que se explicará más adelante.

  • Clave identificativa, que permite que, en el caso de especificarla, no se añada a la cola un trabajo si ya existe uno con la misma clave.


Configuración del decorador @job


En cuanto al decorador, también se le pueden definir algunas opciones:

  • Acción relacionada, ejecutable desde un botón en la vista del trabajo puesto en la cola. Normalmente esta acción devuelve una vista del registro relacionado con la acción, aunque pueden establecerse otros procedimientos.

  • Canal por defecto en el que se establecerá el trabajo. Puede seleccionarse otro canal diferente al indicado en el decorador @job en el momento de llamar la función con el método with_delay. Si en el decorador no se indica ninguno (ni tampoco se especifica en el método with_delay), el trabajo entrará en el canal base (llamado 'root').

  • Patrón de reintentos. Permite establecer diferentes intervalos de tiempo entre un intento fallido y el siguiente intento. Por ejemplo, podría indicarse que los 10 primeros intentos se realicen con un intervalo entre ellos de 5 segundos, que los siguientes 10 intentos tuvieran un intervalo de 30 segundos y que, a partir del siguiente, se intente relanzar la acción cada 2 minutos. Por defecto, la ejecución de los trabajos se vuelve a intentar cada 10 minutos.


Canales. ¿Para qué sirven?


Hay un aspecto muy interesante en el funcionamiento del módulo Job Queue que permite mejorar su eficiencia: el sistema de canales. Cada canal dispone de:

  • Una capacidad máxima de trabajos en ejecución

  • Una serie de trabajos en ejecución

  • Una cola de trabajos pendientes

Existe un canal base llamado 'root', des del que se ejecutarán los diferentes trabajos, y una serie de subcanales, que irán pasando los trabajos que contienen hasta que lleguen al canal root, desde el que se realiza la ejecución real. A continuación se presenta un ejemplo que ilustra de qué manera este sistema puede resultar útil.


 
Vemos que disponemos de dos subcanales de root: 'root.a' y 'root.b'. El canal root.a solo puede ejecutar un trabajo a la vez y, por eso, tres de los trabajos puestos en la cola de este canal tendrán que esperar a que el espacio 'ejecución' quede libre. El canal root.b tiene capacidad para tres trabajos simultáneos y solo dos están en ejecución, por lo que tendría espacio para ejecutar uno más. Que los subcanales root.a y root.b tengan trabajos en ejecución no quiere decir, necesariamente, que estén ejecutándose, sino que lo que indica es que esos trabajos se han pasado a un canal de un nivel inferior: el canal base 'root' en este caso. Puesto que el canal root tiene capacidad para ejecutar 3 trabajos y sus subcanales solo le han pasado 3 trabajos, todos ellos están en ejecución y la cola está vacía. Si, por ejemplo, se añadiera un nuevo trabajo a través del canal root.b, puesto que tiene capacidad de ejecución, este pasaría al canal root, pero se pondría a la cola hasta que alguno de los que se encuentra en ejecución terminase.

¿Por qué este sistema puede resultar útil? Imaginemos que tenemos dos tipos de acciones que pueden ejecutarse en segundo plano y una de esas acciones es mucho más pesada que la otra. A través de los canales podríamos compensar estos pesos, haciendo que el canal al que se pasen las acciones más pesadas admita menos procesos en ejecución que el canal que absorbe las acciones más ligeras. Volviendo al gráfico anterior, la acción pesada podría pasarse al canal root.a para asegurarnos de que solo se ejecuta una a la vez, mientras que las acciones ligeras las pasaríamos por root.b.

Configuración del servidor


Para poder utilizar el módulo Job Queue es importante tener en cuenta los siguiente:

  • hay que iniciar Odoo con --load=web,web_kanban,queue_job

  • el número de workers debe establecer en un valor mayor a 1

NOTA: si se está trabajando con varias bases de datos, habrá que cambiar la base de datos por defecto para hacer referencia a la que estamos utilizando.

Hay además una serie de valores de servidor que pueden configurarse:

  • "ODOO_QUEUE_JOB_CHANNELS=root:4"(o cualquier otra configuración de canal), predefinido: "root:1"

  • "ODOO_QUEUE_JOB_SCHEME=https", predefinido: "http"

  • "ODOO_QUEUE_JOB_HOST=load-balancer", predefinido: "http_interface" o "localhost" si no está definido

  • "ODOO_QUEUE_JOB_PORT=443", predefinido: "http_port" o 8069 si no está definido

  • "ODOO_QUEUE_JOB_HTTP_AUTH_USER=jobrunner", predefinido: vacío

  • "ODOO_QUEUE_JOB_HTTP_AUTH_PASSWORD=s3cr3t", predefinido: vacío

  • "ODOO_QUEUE_JOB_JOBRUNNER_DB_HOST=master-db", predefinido: "db_host" o "False" si no está definido

  • "ODOO_QUEUE_JOB_JOBRUNNER_DB_PORT=5432", predefinido: "db_port" o "False" si no está definido

Ejemplo: Asynchronous Invoice Email

A continuación se introduce el módulo Asynchronous Invoice Email, desarrollado por Sygel, a través del que se verá una aplicación del Job Queue. Asynchronous Invoice Email permite que el envío de emails con facturas a clientes se realice de manera asíncrona. El módulo puede descargarse desde este repositorio.

Odoo 13 nos permite enviar a nuestros clientes facturas por email de manera masiva, a la vez que generamos un documento PDF conjunto. El problema es que, en el caso de seleccionar muchas facturas, será necesario enviar muchos correos electrónicos y, puesto que esta acción se realiza de manera síncrona por defecto, el sistema se bloquea hasta que todos los mensajes se han enviado.

El módulo Asynchronous Invoice Email está diseñado para resolver este problema. Así, mientras que la generación del PDF descargable en nuestro ordenador se generará de manera sincrónica, el envío de los emails con las facturas a nuestros clientes se realizará en segundo plano. Para ello se ponen a la cola tantos jobs como idiomas utilizados por los clientes a los que se les desea enviar la factura por correo electrónico y los envíos se producen de forma asíncrona. De este modo, el sistema no se bloquea y el usuario puede continuar trabajando.

Imaginemos que queremos enviar por correo electrónico a nuestros clientes las cuatro facturas que aparecen en la siguiente captura de pantalla. El modo de proceder sería el mismo que ofrece Odoo de base: seleccionaríamos las facturas y nos dirigiríamos a Actions > Send & Print. Hay que tener en cuenta que uno de nuestros clientes tiene asignado el inglés como idioma, mientras que el otro está configurado en español.


En la siguiente vista, si marcamos la opción 'Email' para indicar que queremos enviar las facturas por correo electrónico y tenemos instalado el módulo Asynchronous Invoice Email, el envío de los correos se realiza de manera asíncrona, mientras que el resto de acciones, en caso de seleccionarlas, continúan realizándose de manera sincrónica.


Al pulsar 'Send', se añaden dos trabajos a la cola (uno por cada idioma de los utilizados por los clientes), que se ejecutan en segundo plano. Se utiliza, además, un canal específico para las acciones de envío masivo de facturas por email llamado 'root.invoice_email'.


En Sygel trabajamos cada día para adaptar a diferentes escenarios las opciones que tanto Odoo como la Odoo Comunity Association (OCA) ofrecen. Somos conscientes de que cada empresa tiene unas características específicas y, por ello, el trato personalizado se traduce en la implantación del ERP para que cumpla tanto con las expectativas como con las necesidades de nuestros clientes. A la vez, compartimos nuestros avances con el resto de la comunidad, para que cada día se beneficie más gente.