Planificador Dinámico para Procesos Asíncronos

Como hemos visto en las 2 entradas anteriores, las capacidades asíncronas de Salesforce y su planificación son de gran utilidad.

Aún así, vimos que podemos encontrar una carencia: si nuestros procesos asíncronos tiene condiciones funcionales o técnicas de las que depende lanzar su ejecución, no existe la posibilidad de configurar estas dependencias.

Mi objetivo es explicar la idea, ofreciendo una prueba de concepto funcional, para que puedas darle la vuelta, mejorarlo y ampliarlo para tus necesidades.

¿Cuál es la necesidad?

Veamos varios ejemplos de requerimientos que el planificador estándar no puede satisfacer:

  1. El proceso A debe ejecutarse a las 3.00 de la mañana con las máximas garantías de ejecución (es decir, debemos garantizar que a las 3.00, al menos queda 1 slot libre de los 5 disponibles) cuando tenemos otros tantos procesos en posible ejecución.
  2. El proceso C sólo debe ejecutarse cuando los procesos A y B hayan finalizado correctamente, pero no controlamos ni queremos controlar, su concurrencia, cual de los 2 finaliza primero, etc.
  3. Una variante del anterior es añadir una condición adicional. El proceso C solo debe ejecutarse no antes de las 3.00 de la madugrada, y además, solo si los procesos A y B se han ejecutado satisfactoriamente, no tenemos ni queremos tener control sobre la ejecución de los procesos A y B, ni de su duración.
  4. La ejecución de los procesos B y C son prioritarios sobre las de A y D, con lo que quiero que estos se ejecuten antes que esos procesos. Aunque el encolamiento FIFO,  garantiza este requerimiento para 2 procesos, ¿qué sucede en un escenario con diversas prioridades cambiantes con varios procesos? Deberíamos ejecutar código de reordenación, de cierta envergadura o actuaciones sobre las colas del sistema.
  5. Quiero poder cambiar las condiciones en caliente, sin necesidad de eliminar, reordenar o preocuparme de los aspectos de concurrencia del planificador estándar.

Adicionalmente, siempre hecho de menos la capacidad de concentrar la configuración de nuestros procesos Queueable, Scheduable y Batchable de forma centralizada, facilitando el mantenimiento, análisis de impacto, etc.

Si has trabajado en SOA o en un entorno de microservicios, este sería el equivalente al Registry (realmente muy útil para mantenimiento y reutilizar el trabajo realizado por otros compañeros).

Mi Propuesta

Construir un Planificador Dinámico con las siguientes características:

  1. Describir de los procesos con sus condiciones en un Custom Object. Esto pemite al administrador, centralizar, gestionar y modificar en caliente los procesos del sistema, SIN CAMBIOS EN EL CÓDIGO.
  2. Utilizar Platform Events para informar de la finalización de Procesos y así planificar nuevos procesos (planificación agresiva – procesar lo más rápido posible)
  3. El planificador, en cada ejecución, busca los procesos que son «mejores candidatos» a ejecutarse y no mantiene una planificación estática, dado que queremos que sea modificable en caliente.

Además debe permitir:

  1. Programar los procesos como siempre, implementar cualquier interfaz, mecanismos de Chaining, etc.
  2. Ejecución en tiempo mínimo (actualmente se ejecuta en menos de 1»)

Conceptos utilizados

Se definen 2 tipos de procesos:

Recurrente: deben ejecutarse cada X minutos. Estos procesos son los más prioritarios.  En el caso que haya una carga muy elevada, es posible que un proceso recurrente no pueda ejecutarse cuando debería.

Por ello, introduzco el concepto de «Recurrente Alarmado«, que es un proceso recurrente que debería haberse ejecutado. Los procesos alarmados, siempre son los más prioritarios, y son siempre los primeros candidatos para el planificador.

Single: son procesos no recurrentes, ejecutan tareas de largo procesamiento, implementando las interfaces Queuable ó Batchable.

Definición de un Custom Object para descripción de los procesos

El primer paso es proporcionar un mecanismo que permita describir las condiciones de ejecución de un proceso. Para ello, que más sencillo que crear un Custom Object para describir esas condiciones y características.

En mi caso he creado el objeto Process__c, que consta de los siguientes campos de configuración:

  1. El nombre del proceso, que por convención debe ser el nombre de la clase que lo implementa en APEX
  2. Schedule Type: indica si el proceso es Recurrente ó Single, es decir que no es recurrente.
  3. Do not Run Before Time: indica que el proceso no puede lanzarse hasta la hora indicada.
  4. Ancestors List: lista de procesos que deben haber finalizado previamente (los llamo procesos Ancestros, el orden en qué finalizaron no es relevante (puede ser una mejora posible)
  5. Priority: prioridad de cada proceso. En mi caso opté por 2 prioridades: 1 y 2. (otra mejora posible 🙂 )
  6. Refresh Window: es la ventana de refresco, tanto para los procesos recurrentes como de los Single. Un proceso puede tener una ventana de ejecución de 5′, ejecutándose cada 5′ y otro de 24h, para que se ejecute una vez al día.

En este mismo objeto se registra información básica de lo que les sucede a los procesos mediante campos con información de ejecución.

Estos campos no son estrictamente necesarios, y podrían obtenerse consultando el objeto AsyncApexJob, he preferido registrarlo en la tabla (si  no te convence, ningún problema, modifica el código para que esto no sea así, no es complejo).

Estos campos son:

  1. Last Execution Date: fecha de la última ejecución del proceso.
  2. Status: que puede ser – **null**, **Executed** o **Planned** indicando que el proceso se ha ejecutado, o planificado para su ejecución, respectivamente. Si el valor es null, es que no se cumple ninguna de las anteriores.
  3. Alarmed?: indica si un proceso recurrente debería haberse ejecutado, pero que  debido a que ya existían el máximo de procesos activos, no pudo. Estos procesos, pasan a ser top-prioritarios y el planificador intenta ejecutarlos lo antes posible.

Nota: explotando la información disponible en AsyncApexJob podríamos realizar métodos más complejos de planificación: como planificar a futuro, valorando la carga pendiente y los tiempos medios anteriores, pero no tengo este tipo de necesidades tan complejas y no las he explorado (puedes ampliarlo con estas capacidades ampliando el código existente).

Funcionamiento

Como he comentado, el objetivo del planificador es aprovechar al máximo la capacidad de 5 procesos de la plataforma pero nunca  sobrepasarla. Para ello su algoritmo es:

Planifica los procesos Recurrentes Alarmados en mi primer lugar

Planifica los procesos Recurrentes no Alarmados

Planifica los procesos Single

Comprueba si se cumplen las condiciones de ejecución del proceso (no iniciarse antes de tal hora y sus padres se han ejecutado) y en caso positivo lanza la ejecución.

Lanzamiento de un Platform Event notificando re-planificación

Definimos un evento, que todos los procesos Single envían al finalizar. La idea es muy sencilla: cuando un  proceso finaliza, muy probablemente, se habrá liberado 1 slot disponible de ejecución para iniciar un nuevo proceso, que debemos aprovechar.

¿Por qué dices muy probablemente? Si el proceso finalizado lanza otro proceso vía Chaining éste ocupará un slot de los 5 procesos activos, y no se liberará ningún slot, ya que se ocupará de immediato por el proceso hijo lanzado.
Además si cualquier usuario, lanza otro procesos asíncrono, que no está contemplado en el  control del planificador, también podría  ocurrir esta situación, por eso el probablemente.

Tenemos un trigger que está escuchando este evento, y relanza la ejecución del planificador.

En la última linea de execute ó finish para Queueable o Batchable repectivamente añade:

EventSender.sendEventBus(processName, processName + ' Executed'); que invoca un evento de la clase EventSender, una clase helper muy simple.

Otra clase helper que he implementado es un Logger que permite centralizar los mensajes de todos los procesos, dado que se generan diferentes contextos y no quedaría reflejado en el Log.

Creación dinámica de los procesos

La creación del proceso, se realiza mediante la clase Type, pudiendo crear un proceso con su nombre (por eso registramos los procesos con su nombre en el objeto Custom).

¿Cuál es el resultado?

El resultado es una planificación con hasta 5 procesos en  ejecución, de forma priorizada, que notifican su finalización,  intentando aprovechar slots de ejecución que queden disponibles, y permitiendo que procesos recurrentes o de ejecución solitaria.

Quizás hubiera sido perfecto que…

Si tenemos una ejecución con 5 procesos ejecutándose y tenemos un proceso Schedulable que pudiera iniciarse mientras estos 5 procesos se están ejecutando, no podrá iniciarse.

Aunque con el planificador estándar ocurre la misma situación, me hubiera gustado paliar esta situación, pero requiere mucho cálculo, complicando el código y aunque posible, no es mi objetivo complicar tanto la idea.

Conclusiones

Las capacidades de creación procesos asíncronos en Salesforce, depende de la necesidad de cada proceso.

Para muchos de nosotros, y en la mayoría de los casos, el planificador estándar de Salesforce, es excelente.

Quizás lo que más hecho en falta, es la centralización de la configuración centralizada de los procesos, ya que cuando lo tenemos disperso en código, donde participan varios compañeros, se vuelve complejo tener la foto global de lo que pasa.

También sería muy interesante disponer de diagramas que mostraran lo que pasó en un período de tiempo.

Para los proyectos donde debamos aprovechar al máximo la plataforma y mejorar el planificador estándar,  tenemos las capacidades de ampliarlo/modificarlo según nuestras necesidades, gracias a todos los mecanismos disponibles, de los cuales he intentado mostrar algunos en este artículo.

Espero que te sea de ayuda y si has llegado hasta aquí, felicidades!!!

Repositorio

Todo los artefactos, están disponibles para su uso y fork en este repositorio de Bitbucket.

Anuncio publicitario

Un comentario sobre “Planificador Dinámico para Procesos Asíncronos

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.