Contexto de Ejecución en Salesforce

Estás creando un nuevo trigger con la funcionalidad deseada. Lo has probado en tus scratch orgs, funciona correctamente, tus tests devuelven los resultados esperados.

Realizas el pase a la Sandbox de Integración, y PUF!! tu trigger finaliza con una Excepción de Límites superados. ¿Cómo puede ser?

Uno de tus compañeros, te pregunta: ¿Qué se está ejecutando dentro del Contexto de Ejecución al invocar el Trigger?

Si esta pregunta te suena a chino o no estás seguro de los que es el Contexto de Ejecución, quizás este artículo pueda ayudarte… y que sepas que no es culpa de nadie 😉

¿Qué es el Contexto de Ejecución en Salesforce?

El contexto de ejecución en Salesforce, a diferencia de otros lenguajes de programación muy comunes, es el espacio de vida de una transacción.

Al inicio de la transacción, Salesforce asigna unos límites, que va consumiendo a medida que va ejecutándose el código, y en caso de superarlos, la transacción finaliza con excepción.

Salesforce introduce el contexto de ejecución, para garantizar el carácter multi-tenant de la plataforma, limitando todas las transacciones de todos los clientes, aseguramos que alguna de ellas, secuestre los recursos compartidos de los servidores.

Vale, y ¿dónde está el problema?

Como aparece, contínuamente en los artículos anteriores sobre frameworks para triggers, las implicaciones del contexto de ejecución determinan el éxito de ejecución de nuestro código.

A continuación, he listado las implicaciones que conforman la situación:

  • La ejecución de Apex, se realiza habitualmente en modo Sistema, es decir, con visibilidad de todos los objetos/campos, sin aplicar FLS, y por tanto, el volumen de datos que tenemos disponible no es predecible (sobretodo en el futuro 😉 )
  • La ejecución del contexto se realiza en un único thread (esto es crucial, con sus variables, que serán usadas durante la vida del contexto, y destruidas al finalizar)
  • Para un contexto, puedes saber cuando empezará, pero no puedes tener la certeza de cuando finalizará,y por supuesto no sabes, cuando un contexto, puede acceder a tu código
  • Esto es debido a que no es posible saber con certeza, lo que se va a ejecutar dentro de un Contexto (toma!!), dado que los datos, la configuración y los mecanismos de ejecución de la ORG, cambian contínuamente
  • Como derivada de las anteriores, no podemos saber con exactitud la cantidad de recursos que consumiremos sobre los límites y dado que un contexto tiene limitados varios recursos: CPU, Heap, operaciones DML, etc., pues podemos tener un problema

Como ves, la situación no es ideal, porque tanto si administras, parametrizas, y/o desarrollas, lo único que puedes/debes hacer, es procurar que aquello que tu construyas, tenga el menor impacto posible en el contexto, utilizando de forma eficiente los recursos.

Veamos con detalle cada uno de estos puntos, para entender bien las implicaciones y llevarnos a medidas de contención del problema.

Inicio predecible, pero contenido y fin impredecible

El inicio de un contexto viene determinado por la ejecución de alguno de los siguientes eventos que pueden lanzar código Apex:

  1. La ejecución de un trigger
  2. La invocación a un método @Future
  3. La invocación a una clase que implemente alguna de las interfaces Queueable, Schedulable, Batchable
  4. La invocación a un Web Service que ofrecemos a sistemas externos
  5. La ejecución de una VisualForce o Lightning Page que en su Controller ejecute lógica de negocio
  6. Ejecución de Flows
  7. Ejecución de código Apex de forma anónima, etc.

Es decir, cuando se inicia código Apex por alguno de los eventos anteriores, se ejecuta siempre dentro de un contexto.

Pero, pero, pero, si después de crear un Contexto de Ejecución debido a alguno de los eventos anteriores, se ejecutan otras operaciones fruto del inicio de la transacción, se mantienen dentro del mismo contexto de ejecución, sin crear otro nuevo.

Es decir, si se lanza un trigger, que ejecuta un cambio en un registro de otro objeto, puede perfectamente provocar la ejecución de una regla de actualización de Workflow, que a su vez, invoca una callout a un servicio externo, que provoca un evento que invoca un trigger que lance un proceso Batchable, etc., todo queda dentro del mismo contexto de ejecución.

Esta situación queda muy bien reflejada si observamos detenidamente cuáles son y en qué orden se ejecutan las operaciones al invocar un trigger. Para ello te recomiendo que leas el artículo de Alba Ribas sobre Trigger and order of execution, y veas el gráfico de las operaciones que se realizan para ejecutar un trigger, que adjunto a continuación:

Diagrama de las operaciones que se llevan a cabo, durante la ejecución de un Trigger, procedente del blog de Alba Ribas – Kudos Alba. Fíjate la cantidad de operaciones que se llevan a cabo durante la ejecución de un Trigger.

En el equipo de proyecto tenemos Administradores, Desarrolladores, y Power Users, todos utilizando diferentes herramientas, que tienen afectación sobre los mismos datos, y por tanto tu código se ejecutará irremediablemente con otras unidades de ejecución que tu no has programado, e incluso recursivamente (en el caso de re-entradas en triggers – de tus triggers!).

Y aquí es donde, radica la problemática, porqué sino podemos predecir las operaciones ejecutadas en un contexto, y está sujeto a los límites de la plataforma…, ¿cómo podemos asegurar que se ejecutará sin sobrepasar los límites?

Pues veamos cuáles son los límites que nos pueden afectar.

Límites, siempre límites

Muchos, pero no todos los Governor Limits, se reinician al iniciarse un contexto de ejecución. Los límites más importantes, relativos al contexto, que afectan al diseño y ejecución son los siguientes:

  1. Número de operaciones SOQL
  2. Número de operaciones DML (insert, update, delete, etc., pero también rollback, runAs, publicar eventos, etc)
  3. Tiempo utilizado de CPU, tanto para operaciones síncronas como asíncronas
  4. Tamaño del Heap Size
  5. Llamadas efectuadas a servicios externos mediante Callouts
  6. Tiempo de espera acumulado para Callouts
  7. Número de Apex jobs encolados mediante System.enqueueJob

Puedes encontrar el listado completo en la Apex Developer Guide.

Entendida la situación, ¿cómo lo solucionamos?

Ahí está el tema, no hay qué solucionar nada, la estrategia es entender, diseñar y  desarrollar teniendo siempre presente, que el contexto de ejecución de nuestros procesos no está dedicado exclusivamente a nuestro código, sinó que casi con total seguridad será compartido.

Aunque pueda parecer extraño, los límites aseguran que podemos ejecutar nuestros procesos sin que otros clientes secuestren los recursos de la platafoma, y que nosotros nos vemos obligados a utilizar los recursos de forma eficiente.

Es decir, no podemos programar, crear workflows, invocar servicios, etc., pensando que si nuestro código funciona de manera unitaria, el conjunto funcionará correctamente, sino qué, debemos realizarlo con la convicción que provocaremos que otros procesos se ejecutan ó que nuestro código se ejecutará a causa de otro en cualquier momento (la re-entrada de Triggers es un muy buen ejemplo de esta situación).

Pautas para eficientar el uso de recursos en el Contexto de ejecución

Aquí dejo algunas de las que creo pueden ser buenas pautas para un uso racional de los recursos durante la ejecución del contexto:

  1. Todas las buenas prácticas que se comentan en la Developer Guide y documentación de Salesforce (parece repetitivo, pero es crucial)
  2. Usar Frameworks de triggers que nos aseguren la gestión de las re-entradas, el orden de ejecución, un solo trigger por objeto, unificar operaciones DML, etc.
  3. Bulkificar siempre nuestro código, sin excepción, aunque ahora pueda parecer innecesario
  4. Cachear resultados de las operaciones SOQL, para evitar repetirlas
  5. Incluir información de los objetos relacionados en las Queries, para evitar ejecutar varias SOQL, utilizando subqueries y mecanismos avanzados de SOQL
  6. Usar variables estáticas, para cachear resultados, siempre que no contengan enormes cantidades de datos, evitando así repetir Queries
  7. Usar variables estáticas, para cachear resultados, siempre que no contengan enormes cantidades de datos, para realizar operaciones DMLs al final del contexto
  8. Utilizar los mecanismos asíncronos de la plataforma para minimizar el tiempo de CPU síncrono usado
  9. Priorizar el uso de Maps con Ids para el tratamiento de conjuntos de registros, cuando sea posible, son las estructuras más eficientes y los benchmarks de varios programadores así lo atestiguan
  10. Trabajar sobre el mínimo conjunto de registros que nos sea posible, disminuye el tiempo de CPU y el Heap utilizado
  11. Realizar Tests con volumetria semejante a Producción (en este caso, las proyecciones pueden ser malas consejeras, lo mejor es utilizar volumetria real)
  12. Realizar la batería más amplia posible de tests de integración, ya que en esa situación donde realmente podremos ver el contenido completo del contexto de ejecución
  13. Plantearnos la viabilidad de realizar una batida de Test con @isTest(SeeAllData=true) en un entorno con volumetría productiva, y así evaluar los resultados y el log generado (esto no contradice la buena práctica habitual)
  14. Utilizar startTest y stopTest y los métodos de la clase Limits (getLimit_Recurso y get_Recurso) para obtener el consumo de recursos consumidos durante la ejecución

Seguro que se te ocurren otras más basadas en tu experiencia, pero en general, la premisa es: las operaciones que tu llevas a cabo, se ejecutarán dentro de un contexto, compartiendo recursos con otras operaciones, y por tanto deben utilizar el mínimo conjunto de recursos posibles y debes test tan pronto como sea posible, la situación más semejante a la que te encontrarás en Producción.

Log del Contexto de Ejecución

Una de las maneras más útiles de entender lo que ha pasado en un contexto de ejecución, es el Log. Este log, permite tanto a un Administrador como a un programador entender las diferentes operaciones que se han llevado a cabo.

Básicamente en el existen 2 pares de etiquetas cruciales:

  1. EXECUTION_STARTED y EXECUTION_FINISHED: marca el inicio y fin del contexto de ejecución
  2. CODE_UNIT_STARTED y CODE_UNIT_FINISHED: marca el inicio y el fin de una operación dentro del contexto

Por ejemplo, a continuación podemos ver partes del log correspondiente a la ejecución de un trigger en mi ORG personal:

Observa que se inicia y finaliza un contexto de ejecución como inicio y final de la ejecución, y como durante la ejecución del Trigger, por ejemplo, se ha invocado a una Workflow que había configurado, y que ni recordaba haberlo configurado.

Durante la ejecución del Trigger, el log también nos puede informar del uso del Heap:

El uso de la clase Limits, es muy útil para volcar información en el Log que nos permite analizar el consumo de los recursos.

Conclusiones

Es erróneo pensar que mi código funcionará aislado. Todo lo contrario, nuestro código se ejecutará con código que no hemos escrito, con procesos que no hemos diseñado nosotros, y que activará otras funcionalidades que quizás ni conocemos.

Por tanto, debemos actuar utilizando el mínimo conjunto de recursos, y realizando test de datos y configuración integrada.

Espero que esta entrada te haya sido de ayuda y te permita disminuir situaciones inesperadas en el futuro.

Enlaces interesantes

One response to “Contexto de Ejecución en Salesforce

Responder

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. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.