3

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('[email protected]');
    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?

2
  • 2
    Can you post your code of batch apex what you are performing Dec 18, 2017 at 17:18
  • @SantanuBoral I have updated my post above and added the code I am using. Thank you for your response.
    – abc
    Dec 18, 2017 at 17:53

2 Answers 2

6

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.

3
  • Hey @sfdcfox thank you so much for your answer, however I get an error saying, "Cannot modify a collection while it is being iterated". Any idea on what might be wrong?
    – abc
    Dec 24, 2017 at 1:49
  • @abc I'd need to see what you did, but generally that happens when you try to add or remove an element in a list that's in a for-each loop. You should have a separate list to add the updates to.
    – sfdcfox
    Dec 24, 2017 at 22:09
  • Hey @sfdcfox thank you for getting back and Merry Christmas! I was getting this error because I was missing the id mapping - have resolved it now, however I have now landed into 2 new issues - attempt to de-reference a null object, and Apex CPU time limit exceeded. I have updated my post above and have added new execute function of my batch. Can you please take a look at it. Your help is much appreciated.
    – abc
    Dec 26, 2017 at 2:40
0

I haven't tested this, but it might be faster to update the account objects without doing a separate soql query to retrieve them. To do this, modify your start method to add the account fields you want to the query:

query = 'SELECT **Account.Membership_Status__c**, End_Date_180_Prior__c, Pardot_End_Date_180_Prior__c, Pardot_Related_C_Account__c, AccountId, Id FROM contact';

Then, update your batch class to:

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

    List<Contact> contcts = (List<Contact>)scope;
    List<Account> accts = new List<Account>();

    for(Contact c : contcts){


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

        if(c.AccountID != null) {
            c.Pardot_Related_C_Account__c             = a.Related_C_Account_Text__c;
            accts.add(new Account(ID = c.AccountID, Pardot_Membership_Status__c = a.Membership_Status__c));
        }            
    }

    update contcts;
    update accts; //Note that updating an empty list is a no-op, so you don't need to check if accts is empty
}

You should see if this improves your batch's performance in your org, though; logically retrieving lookup values in one query ought to be faster than running two queries, especially since the second query has a subquery, but I could be wrong.

1
  • Hey thanks @IllusiveBrian , this didnt work however now I have removed accounts completely from my code. I am now updating lesser fields only in contact object, however this is again taking a lot of time. Any suggestions on how to optimize it to such a point that it takes just an hour to update about atleast 200k records. Do we have any concept of multithreading which we can utilize, or anything else which might be useful.
    – abc
    Dec 29, 2017 at 21:55

You must log in to answer this question.

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