5

A few situations in my app trigger a Clean up batch to run. It will process many records. In some situations multiple of such Cleanup batches will be triggered by independent user operations.

If the batch work on the same record we sometime see locking or currupt data.

Is there any means to sequence batches of the same type (class) without complicated scheduling solutions? Maybe with the FlexQueue and its Holding state?

6
  • We can run next batch from first batch finish method. So it will execute in sequence. Dec 12, 2018 at 12:00
  • Batch chaining only works when the first batch knows the second batch. This is not the case. Dec 12, 2018 at 12:07
  • 4
    I don't think there is, but I have some code I can share with you that does the job. My solution uses a custom object, Mutex__c, to ensure that only one instance of a batch class is running at a time. Typically, that instance processes all applicable records, and keeps re-queueing itself until everything is processed. Any new attempts to start a batch go via the Mutex__c object, so they do nothing if it's already running. It even goes so far as abstracting away the async implementation so you can run your code as batch/queuable based on metadata settings
    – Aidan
    Dec 12, 2018 at 12:11
  • 1
    Is the batch Querry Same? if yes you can querry ApexAsyncJob to see if any batch is running or is in holding state. If running then initiate the new batch by aborting the old one. If there is no batch waiting then create a new batch as usual Dec 12, 2018 at 12:16
  • 1
    @PranayJaiswal: Sound like a great idea: aborting the old one. You should add this as a solution with a code example. But for me this is not enough as the batch runs on different records. I might loose stuff. Dec 12, 2018 at 12:35

2 Answers 2

4

If the batch query is same, then it makes sense to abort the currently running batch and execute a new one which will also pick up records of last batch.

Also, if the batch is in Holding or Queued state that means the Start method was not called and thus when start method will be called, it will also pick up new records, meaning we don't need to run a new batch.

public static void addBatchIfNotRunning(Id apexBatchClassId){

    ApexAsyncJob asyncJobList = [Select Id From ApexAsyncJob where ApexClassId=:apexBatchClassId AND JobType='BatchApex' and Status In ('Processing','Preparing','Holding','Queued')];
    boolean isNeedToInitiateNewBatch = true;
    if(!asyncJobList.isEmpty()){
        for(ApexAsyncJob as : asyncJobList){
            if(as.Status == 'Processing' || as.Status == 'Preparing'){ //As start method of batch is running or run, it wont pick new records so abort it
                System.abortJob(as.Id);

            }else{
                //Batch is already queued or in Holding stage so it will pick up new records
                isNeedToInitiateNewBatch = false;
            }

        }
    }
    if(isNeedToInitiateNewBatch){
        //Initiate Batch
    }

}
3

Typical designs for this involve a temporary object. The idea is the code that kicks off the deletion inserts some sort of temporary record, and then checks if a batch is running or queued. If so, it skips calling the batch. The batch itself checks if there's any temporary records to process in its finish method, and if so, restarts the batch with a fresh query. This is similar to the Mutex idea from Aidan, but allows potential restarts of failed records; one temporary record per record to delete. Having this second object should mostly help resolve the problem, although specific versions of this might need additional work. It's kind of hard to predict ahead of time, as it depends on the types of relationships and objects involved. For example, you might want to upsert these temporary records somehow to avoid duplicates, which would slightly increase database contention but may reduce number of batches that need to run.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .