If multiple users trigger the same behavior then there can be more batch processes, that can hit the governor limit. Asynchronous process is needed because there are large number of child records to be updated when parent record is updated. For a bulk update of 50 parent records there are 20000 child record to be updated. Future or queueable will fail because DML rows limit is 10000. So, how can I solve this problem.
Executing async processing from a trigger directly is not a good idea since:
- The limits on numbers of async processes that can be created in a transaction depend on whether you are already in an async process.
- A trigger can be called from both sync and async processing.
- A trigger will be called multiple times with chunks of the bulk update/insert/delete data.
Consider the following:
- I want to run an async process against records that were updated in a specific way so I write a trigger that collects the record IDs and then creates and enqueues a queueable, passing in the IDs.
- The trigger is called with 10000 records in a synchronous context. This means the trigger is called 50 times, each with 200 records, and if there is at least one relevant record in each chunk this will enqueue 50 queueables. #phew#! That's OK, we can enqueue 50 queueables in a single transaction (but note that no other queueables can now be added in that same transaction).
- The trigger is called with 201 records in an async context (e.g. from a future method). This means the trigger is called twice, the first with 200 records and the second with 1 record. If there is at least one relevant record in each chunk this will try to enqueue 2 queueables. #boom#! This fails because you can only enqueue a single queueable in an async transaction.
So how can you resolve this? In outline, we:
- Make sure records that need processing are appropriately marked as such.
- Publish a "publish after commit" platform event from the trigger just to kick-start the async processing (and indeed we ensure only a single platform event is published in any one transaction, so we don't get one per chunk, but just one for all the chunks).
- Have a trigger-based subscriber for the platform event that runs the logic OR that enqueues some queueable/executes some batch, depending on what needs to be done.
You can find more information about how we do these various bits in this previous answer. Instead of a batch you can use a Queueable. One of the advantages of the latter is the option to use a transaction Finalizer to recover from governor limit issues.
If you really want to use a batch, and again can flag the records as needing to be processed, you could consider implementing something like our "Adaptive Batch" pattern. This makes sure you only ever have a single instance of the given Batchable running or scheduled at any one time, and it can self-chain with a short delay to avoid burning through daily async limits if it hits limit issues or other failures.
- Have the trigger write the ids to a custom object Async_Request__c
- Have a scheduled job started (runs every few minutes, started by some service/headless user) whose execute() reads the first Async_Request__c and starts a batchable. If no Async_Request__c or batchable still in flight, do nothing.
- Have the batchable finish() read the next Async_Request__c, if any, and start the batchable.
Basically, serialize the batchables.