As explained in the last 2 entries, asynchronous processing and execution planning capabilities on Salesforce are very useful and powerful.
But nonetheless, in some complex scenarios, you will find some limitations that can be overcome easily.
Let me explain how, with some Apex Developing and using standard capabilities, you can get a quite powerful Dynamic Planner to sort out those limitations.
My goal on this entry is to describe the current limitations on the platform, explain how they can be solved, and hope they are useful and make some contribution to improve your knowledge in this key area.
What is missing?
Let me explain some use cases that the current platform can’t satisfy:
- Process A must be run at 3.00 o’clock in the morning, with maximum priority. If you’ve got a lot of processes in the queue, as there can be only 5 active jobs at a time, you can’t guarantee process A will start at the scheduled time. So, scheduling is not enough.
- Process C must be executed after processes A and B have finished. The current planner cannot take this into account. Only accepting some degradation on performance using chained jobs could be achieved (but you don’t want that, do you?)
- A slightly different variant than 1 and 2 use cases, is as follows: Process C must be executed after processes A and B have finished and not before 3.00 o’clock in the morning.
- Process B and C have priority over A and D. So, we want to run B and C, before A and D. With the standard planner this will be easily achieved, but what about when you have 20 processes where their priorities change depending on events occurring on the ORG?
If you have ever worked on SOA platforms or microservices, you enjoyed the use of a Registry component, where all services are listed and contracts and interfaces are explained. That is a piece of software that is missing from Salesforce for async processing and it could be handy to have it on the Platform.
My proposal is to write an Apex Dynamic Planner (ADP), to solve the issues explained in the previous use cases. Its characteristics are:
- Describe processes and its conditions using a Custom Object, accessible on real time, to allow hot changes.
- Squeeze the platform performance trying to plan always 5 active processes but without using the queue. ADP, in every run, will try to get the most adequate processes to fill the 5 active jobs limit. The rest of the processes will not be executed until there is 1 slot free. By doing this, the planner allows you to change process configuration at real time and in every execution it will choose always the best candidates.
- As is essential to squeeze the platform, every process must notify when it finishes, throwing a platform event. A trigger listening that event, will notify the ADP that one slot could be potentially free (if the developer used chained processes, the next process will start immediately after the one that just finished and then ADP will not perform any action).
Also ADP must:
- Allow to write async apex code as always:
- Develop any async process with any current interface (Schedulable, Batchable, Queueable)
- Chaining is allowed
- Not all processes must follow the ADP, so if any developer run an async process outside the ADP scope, this will not break the plan.
- Run in minimum time. A heavy planner is not a good planner (currently the ADP takes less than 1 second)
Concepts and terms used
The ADP allows you to define 2 process types:
Recurrent: some organizations may need async processes to run recurrently every X minutes. Usually, those processes are quite important so they are top-priority. If you want to squeeze the efficiency of the ORG to the max, ADP will always try to have 5 async processes running. So, it is possible that a recurrent process can’t be run when it should. To solve this problem, we set an alarm flag to that process and program the ADP to consider the Alarmed Recurrent processes the top priority processes.
Single: a single process is a non-recurrent process. Probably those are the most common async processes in all ORGS.
Show me the money: how to describe a process and its conditions
This is very easy done, I use a Custom Object to describe the minimum fields that will provide the ADP with the information needed to check conditions.
My Custom object is Process__c, which contains:
- Name: the APEX class name to run the process
- Schedule Type: indicates Recurrent or Single (you already know that)
- Do not Run Before Time: time before the process can’t be run
- Ancestors List: which processed must be run and finished before this one
- Priority: I just used values 1 and 2, but I am sure you will think something more interesting and challenging
- Refresh Window: do not run again the process until X minutes have passed. This allow you to have single processes run recurrently (what a mix!!)
- Alarmed: indicates if a recurrent process is alarmed
In this same object, I included some execution related information useful for the planner. I know that this information could be extracted using the AsyncApexJob, but for the sake of the explanation and clarity of code I cheated a bit.
I promise that if I have some time in the near weeks, I will change it as there are some very concrete situations where this approach could not be the best.
Those fields are:
- Last Execution Date: last execution date for this process on ADP (this could potentially be inaccurate if you run the same process using ADP and outside of it – AsyncApexJob to the rescue)
- Status: 3 values – **null**, **Executed** or **Planned**, meaning not managed yet, process finished or already planned, respectively.
- Alarmed?: a recurrent process that should be executed but it couldn’t because there was not a free slot.
How ADP works?
I already showed the main algorithm above, let’s see it in code:
First Alarmed Recurrent Processes are planned
Non-Alarmed recurrent processes follow:
And finally the Single processes are planned:
For single processes, ADP checks all conditions, make decisions based on priorities, so, on every planner execution the most adequate single processes are selected.
I empathize this point, because this is key to allow hot changes on the conditions.
Using Platform Events to notify a process ended
A process must throw an event to notify the ADP that one slot “maybe” is free, so that another process can be executed.
But, Why you say “maybe”?
I say it, because if your process use Chaining, or any other async process is executed in the ORG, the ADP will not find a free slot. This is not a problem for the ADP, as it will find that no slots are available, and it will not try to run any new process.
To throw the event just add the follow snippet at the end of the execute() or finish() methods (Queueable or Batchable respectively):
EventSender.sendEventBus(processName, processName + ' Executed');
Dynamic process creation
Do not worry about process creation, the ADP takes advantage of the magic Type Apex Type to do so (I really like this phrase). Code as follows:
… and this really works?
So far it does. A Logger Custom Object contains all the relevant info that explains what the planner is doing.
I must confess
All that glitters is not gold and there is a piece that don’t match and probably can be confusing.
If you, like me, want to squeeze the blue cloud, but have Scheduled processes in your org concurrently running with the ADP, the later can delay the execution of the Scheduled jobs.
As it will try to have 5 active processes, when the time arrives to the scheduled process to run, it will be no free slots available. But is this different with the standard planner? No, you’re right, but as the ADP is custom APEX based, could look at the future scheduled processes, check AsyncApexJob for consumed times, and prevent and predict, that a slot needs to be available at a given time.
This requires quiet a lot more code to be written, and again I think for clarity code sake will not be beneficial (but you know, you can improve it if you like).
For many of us the FIFO planner in the platform is excellent and reliable to say the least.
Again, the platform is so open, powerful and accessible, that can be improved, with some lines of code to get an ADP that can solve the use cases explained above.
I want to give special thanks to Alba Rivas (@Alba_ARivas), who encouraged me to write this post in english and gave ideas for new ones, Gracias Alba!!
All available at this repo
All code is publicly available in this Bitbucket repo.