[SalesForce] Making updates to mass records using batch apex faster

I have written a batch apex which will update few fields in contacts and account objects of about 1.5 million records. I initiated it yesterday and in 10 hours it updated just 300k records. I wanted to check if this is quite usual, or if there is a way to make this batch update faster?

This is the code I am using to update mass records:

global class UpdateFields implements Database.Batchable<SObject>{
global String query;
global String Entity;
global String Email;

global Database.QueryLocator start(Database.BatchableContext BC){
    query = 'SELECT End_Date_180_Prior__c, Pardot_End_Date_180_Prior__c, Pardot_Related_C_Account__c, AccountId, Id FROM contact';
    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<Contact> scope){

    List<Contact> contcts = (List<Contact>)scope;

    Map<Id, Account> accts = new Map<Id, Account>([SELECT Related_C_Account_Text__c,
            Membership_Status__c
        FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE Id = :scope)]);

    for(Contact c : contcts){

        Account a = accts.get(c.AccountId);

        c.Pardot_End_Date_180_Prior__c              = c.End_Date_180_Prior__c;
        //updating 10 more fields here

        if(a != null) {
            c.Pardot_Related_C_Account__c             = a.Related_C_Account_Text__c;
            a.Pardot_Membership_Status__c             = a.Membership_Status__c;
        }            
    }

    update contcts;
    update accts.values(); // save changes to the accounts
}

global void finish(Database.BatchableContext BC) {
    /*Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    mail.setToAddresses(new String[] {email});
    mail.setReplyTo('user@company.org');
    mail.setSenderDisplayName('Account and Contacts Batch Processing');
    mail.setSubject('Batch Process Completed');
    mail.setPlainTextBody('Batch Process has completed');
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });*/
}
}

After the optimizations suggested by @sfdcfox, execute function of my batch now looks like this:

global void execute(Database.BatchableContext BC, List<Contact> scope){

List<Contact> contcts = new List<Contact>();

Map<Id, Account> accts = new Map<Id, Account>([SELECT Related_C_Account_Text__c,
        Membership_Status__c
    FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE Id = :scope)]);

for(Contact c : scope){

    Account a = accts.get(c.AccountId);

     contcts.add(new Contact(
            Id                                            = c.Id,
            Pardot_End_Date_180_Prior__c              = c.End_Date_180_Prior__c;
            Pardot_Related_C_Account__c               = a.Related_C_Account_Text__c;
            //updating 10 more fields here
     ));

    if(a != null) {
        a.Pardot_Membership_Status__c             = a.Membership_Status__c;
        //updating 1 more field here
    }            
}

update contcts;
update accts.values(); // save changes to the accounts
}

It now gives two errors to me:
1. Attempt to de-reference a null object – I am assuming this is because of the cross object mapping in the contacts
2. Apex CPU time limit exceeded – If I execute the batch with 200 records as the scope, I do not get this error.

Any ideas of what could resolve these issues in my case?

Best Answer

Based on your code, it's clear that there's not much you're going to do to improve performance. However, one major improvement would be to increase the scope size, assuming it does not cause you to violate any governor limits:

Database.executeBatch(new UpdateFields(), 2000); // Process 2000 per execute method

One minor optimization that may improve performance would be as follows:

 List<Contact> contcts = new List<Contact>();
 ...
 for(Contact c: scope) {
   contcts.add(
     new Contact(Id=c.Id, 
       Pardot_End_Date_180_Prior__c = c.End_Date_180_Prior__c,
       // more fields here
     ));
 }

The reason why this improves performance is by reducing the number of statements executed, which means fewer governor limit checks. This can result in a significant amount of savings at some cost to legibility.

From prior experiments, using field assignments instead of a constructor is approximately twice as slow. You'll have to decide if the performance benefit is worth it.

Related Topic