[SalesForce] Build Custom Batch Queue to centrally manage Batches & circumvent the Max. 5 Batches in parallel

Just assume you had an app that would have to process huge amounts of records in an org in multiple ways each day. For each "way" you have a separate Batchable class and those are triggered independently by user actions. Depending on how many users and how much data you have you can easily run into one of the Batch related limits.

My ideas was to in any way centralize batch processing and by that reducing the risk of e.g. too many batches running in parallel.

I thought of having a ContinuousWorkerBatch class that checks in his start() method if work needs to be done (which type of work on which record). If so it passes the Ids of the records to process to his execute method otherwise skips the execute. In his finish method it would calls a new instance of itself. With Summer '13 we can use the new method to delay the new batch by scheduling it in a minute.

global class ContinuousWorkerBatch implements Database.Batchable<SObject>, Database.Stateful {

    private BatchQueueItem__c currentWork;
    private List<Id> recordIds;
    private BatchWorkerCommand command;

    public ContinuousWorkerBatch() {
        this.currentWork = [SELECT txt_CommandClassName__c, ltxt_IdsToProcess__c
                            FROM BatchQueueItem__c
                            ORDER BY CreatedDate ASC
                            LIMIT 1];                                  
        this.command = newFromName(this.currentWork.txt_CommandClassName__c);  
        this.recordIds = parseToList(this.currentWork.ltxt_IdsToProcess__c);                       
    }

    global List<SObject> start(Database.BatchableContext context) {
       return queryRecordsForIds(this.recordIds);
    }

    global void execute(Database.BatchableContext context, List<SObject> scope) {
        this.command.execute(scope);
        updateRecordIds(scope);
    }


    global void finish(Database.BatchableContext context) {
        ContinuousWorkerBatch same = new ContinuousWorkerBatch();

        if(this.recordIds.isEmpty()) {
            delete this.currentWork;
            Database.scheduleBatch(same, 1);
        }
        else {
            this.currentWork.ltxt_IdsToProcess__c = String.join(this.recordIds, ',');
            update this.currentWork;
            Database.executeBatch(same);
        }
    }
        ...
}

But the basic idea is that only a single batch is constantly running and not many are independently triggered and fighting against each other.

Work could be registered by adding record Ids and the name of a Command pattern like class in the database. Maybe just a long text field to be able to store thousands of pairs.

public interface BatchWorkerCommand {
    void execute(List<SObject> recordsToBeProcessed);
}

Order of execution could be respected and we would avoid race conditions between batches.

  1. Is this a stupid idea after a few glasses of wine 😉 or a feasible solution?
  2. Have you done something similar and can share some lessons learned?
  3. What what you change improve or take into account?

Any feedback is appreciated.

Best Answer

I've done this a few times. It works well. It can take a while to get the thing running end-to-end, since debugging can be arduous.

A few suggestions:

  • in your command table, add a "log" long text field and write back relevant status/debugging info there.
  • catch all exceptions from your batch manager and write them out to the log rather than blowing up.
  • you can use the Apex dynamic instantiation (reflection) to allow your command table to hold actual class names if you want to maximize flexibility
  • I use a number of common base classes for my batch jobs, that do certain common stuff like logging, recording start/stop times and record counts, self-rescheduling (via command table) for recurring jobs, etc.

Hope that helps.

Related Topic