I am trying to understand what I can and can't do when chaining queueables. I have read several tutorials on this but it seems contradictory that I can't create more than 1 child job, but I can create up to 50 in a transaction.
Trailhead says the following, which is commonly referred to verbatim in various blogs on the subject. Emphasis mine.
You can add up to 50 jobs to the queue with System.enqueueJob in a single transaction.
When chaining jobs, you can add only one job from an executing job with System.enqueueJob, which means that only one child job can exist for each parent queueable job. Starting multiple child jobs from the same queueable job is a no-no.
No limit is enforced on the depth of chained jobs, which means that you can chain one job to another job and repeat this process with each new child job to link it to a new child job. However, for Developer Edition and Trial orgs, the maximum stack depth for chained jobs is 5, which means that you can chain jobs four times and the maximum number of jobs in the chain is 5, including the initial parent queueable job.
I find this contradictory that you can add up to 50 jobs in one transaction, but you can't start one job from the other.
I am trying to implement a pattern where I keep things that need to be synchronous synchronous, but do everything else using Queueable
. This results in a pattern where I do something like the following:
In the Trigger
if (Trigger.isBefore && Trigger.isUpdate) {
ContactTriggerHandler.fireMyProcess(Trigger.oldMap, Trigger.newMap);
}
In the Trigger Handler
public static void fireMyProcess(Map<Id,Contact> oldMap, Map<Id,Contact> newMap)
{
List<Contact> contacts = new List<Contact>();
for (Contact c : newMap.values())
if (should be processed asynchronously)
contacts.add(c);
jobId = System.enqueueJob(new ContactTriggerHandlerQueueable(contacts));
}
Then, a Queueable
process that actually implements my logic.
public class ContactTriggerHandlerQueueable implements Queueable {
public ContactTriggerHandlerQueueable(List<Contact> contacts)
{
// Save off my Contacts into a class variable
}
public void execute(QueueableContext context) {
// My business Logic
}
}
Assume here that I have a similar pattern for Account triggers. There are no callouts.
The problem with this approach is that when my business logic updates an Account, the Account trigger fires, and another Queueable
is created, but I get the error : Too many queueable jobs added to the queue: 2
So this violates the no-child-Queueables rule - my Contact Queueable
ends up creating an Account Queueable
which is considered illegal chaining.
I then tried a similar pattern where I queued up a Queueable
inside of a Queueable
:
public ContactTriggerHandlerQueueable(List<Contact> contacts)
{
// Save off my Contacts into a class variable
}
public void execute(QueueableContext context) {
jobId = System.enqueueJob(new SomeOtherTriggerHandlerQueueable(contacts));
}
This is far less preferable than the 1st pattern because (1) it requires every trigger to be aware whether it was called from a Queueable
using Limits.getQueueableJobs()
and (2) the preceding logic must explicitly invoke the Queueable
, when normally the downstream trigger would pick it up.
This too fails, perhaps because there is a trigger that's creating a Queueable
downstream.
That said, how do I practically utilize the feature to add up to 50 jobs in a transaction, but not start multiple jobs from the same job? I am really struggling to reconcile these two concepts since they seem contradictory.