1

I am writing a batchable apex class and am finding that every time I run it, some rows are not being processed.

The strange thing is when I look at the apex job monitoring page, the number of batches is correct when the job starts running, but the number of batches reduce right at the end by about the right number to explain the records that haven't been updated.

The number of records that are missed remains the same when I run the batch process multiple times.

When I single out a record that was missed to be updated by providing its id to the queryLocator, it updates fine.

I looked at the Limits but they all look fine. I created a really simple batchable process that is not doing anything fancy, it is just blanking out some fields to try and understand the problem. It's not doing any queries or loading any extra data.

I put some logging in and found that although I'd requested the batch size to be 30, there were lots of batches where only 10 records were processed. There seemed to be reliably either 30 or 10 records in the batch.

Here's my Batchable apex class:

global class NBTypeBatchUpdate implements Database.Batchable<sObject> {
global Database.QueryLocator start(Database.BatchableContext BC)
{
    String query = 'SELECT Id, Name, NBType__c ';
    query += 'FROM Opportunity ';

    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<sObject> scope)
{
    system.debug('Processing ' + scope.size() + ' opportunities');

    for (Opportunity opportunity : (List<Opportunity>)scope){
        system.debug('Processing ' + opportunity.Name);
        opportunity.NBType__c = null;
    }

    update scope;
}  

private static void PrintLimits() {
    system.debug('Queries: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
    system.debug('Query locator rows: ' + Limits.getQueryLocatorRows() + '/' + Limits.getLimitQueryLocatorRows());
    system.debug('CPU time: ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
    system.debug('Heap size: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
    system.debug('Heap size: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
}

global void finish(Database.BatchableContext BC)
{
}

}

  • 1
    Do you have any validation rules or triggers that may cause some records to not be updated? If you specify false as a second parameter to Database.update(), any records that cannot be updated because failing validation or triggers will not cause a rollback but the update will just exclude those records. – rael_kid Sep 22 '14 at 13:46
  • Nope, I switched them all off. All triggers, validation rules and workflows related to opportunity. – Helephant Sep 22 '14 at 14:45
3

Turned out that something had gone wrong with some of the records when creating the sandbox. It was a full sandbox with a copy of all the data from production.

In my case, I was able to reproduce the issue by creating a new column on Opportunity and a batch process that did nothing except update that column. I could see that some rows weren't being updated. Support were able to reproduce the issue in my sandbox as well, so they were able to diagnose and solve it.

Here was the cut down batch process I used to demonstrate the problem:

global class NBTypeBatchUpdate implements Database.Batchable<sObject> {
    global Database.QueryLocator start(Database.BatchableContext BC)
    {

        String query = 'SELECT Id, WasUpdatedByBatchUpdate__c ';
        query += 'FROM Opportunity ';

        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope)
    {
        system.debug('Processing ' + scope.size() + ' opportunities');

        for (Opportunity opportunity : (List<Opportunity>)scope){
            opportunity.WasUpdatedByBatchUpdate__c = true;
        }

        update scope;
    }  

    global void finish(Database.BatchableContext BC)
    {
    }
}

I didn't get any error messages from the update dml statement. The trouble records just didn't get updated.

Here's exactly what support told me (in case anyone else has this problem):

it seems this was an unfortunate consequence of the non blocking copy process used when refreshing a sandbox which left some data in an inconsistent state.

We ran scrutiny to fix the issue. It seems the issue is resolve for now.

  • congratulations -- I never would have thought of this as the issue and have now stored it into my consciousness. Did you have an exceptionally large fullcopy sandbox? – cropredy Oct 1 '14 at 18:00
  • We have about half a million opportunities. I wonder if it's because our Salesforce instance is so old. We've been using Salesforce since 2006. Hope it helps someone else! – Helephant Oct 7 '14 at 10:33
2

You could make your batch class more robust with better error reporting by doing the following

global class NBTypeBatchUpdate implements Database.Batchable<sObject>, Database.Stateful {

global String log = '';  // stateful variable to keep track of errors across batches


global Database.QueryLocator start(Database.BatchableContext BC)
{
String query = 'SELECT Id, Name, NBType__c ';
query += 'FROM Opportunity ';
query += 'WHERE ContractStartDate__c != null ';
query += 'AND ContractStartDate__c > 2014-01-01 ';
//query += 'AND NBType__c != null ';

return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<sObject> scope)
{
 system.debug('Processing ' + scope.size() + ' opportunities');

 for (Opportunity opportunity : (List<Opportunity>)scope){
    system.debug('Processing ' + opportunity.Name);
    opportunity.NBType__c = null;
}

Database.SaveResult[] srList = database.update(scope, false);  // allOrNone = false
for (Integer i = 0; i < srList.size(); i++)
     if (!srList[i].isSuccess())
         this.log += '\n Error in Opportunity: ' + scope[i].name + '. Error msg=' + 
                 srList[i].getErrors()[0].getMessage();

}

global void finish(Database.BatchableContext BC)
{
if (!String.isEmpty(this.log)) {
    Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
    email.setToAddresses(new List<String> {'myemail@foo.com');
    email.setCcAddresses(new List<String> {});
    email.setbccAddresses(new List<String> {});
    email.setSubject('error in batch process!');
    email.setPlainTextBody(this.log);    
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});
}

}
0

By using the following syntax for updating:

Database.update(scope, false);

You are returning at Database.SaveResult[] collection. However, you are not assigning that collection to a variable and then inspecting it to find the errors. So, basically it appears you will have some errors in your update but you are not finding out what they are. If you change your code slightly to:

update scope;

You can then rely on the error being surfaced to the Apex log and figure out what the issue is.

  • Thanks for the suggestion, but I've tried both update and Database.update() and got the same results. – Helephant Sep 22 '14 at 11:45
  • Also I've tried logging any failures, but haven't been able to see any. – Helephant Sep 22 '14 at 11:45
  • If you tried with update instead of database.update() that would surface errors anyway. I have nothing else to add I'm afraid. – executeanon Sep 22 '14 at 11:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.