2

I'm facing the error similar to given here (First error: Apex heap size too large: 12000354) to avoid this what I'm trying to do is, I'm inserting the csv file when my first batch run and then in sub-sequent batch, I'm reading the already inserted csv file and updating the same.

Attachment att = [Select Id, body from Attachment Where Id = 'some Id'];
string attchbody =att.body.toString();
string[] eachrecords = attchbody.split('\n');

Now my question is using this technique will it reduce the heap size. As no. of records in attchbody will be same as present records in finish method.

What are the alternative ways of doing this, as I don't want to insert multiple files for the batch.

  • 1
    Is the CSV file itself larger than 12 megabytes? – David Reed Jun 18 '18 at 12:57
  • Do you need to perform operations on each line of the csv file, and then update it with the results of those operations on each line? – IllusiveBrian Jun 18 '18 at 15:10
  • @David: It can be, as of now before that I'm getting this error – salesforce Developer Jun 18 '18 at 17:05
  • @IllusiveBrian: I'm processing records in my batch class and I need to store those processed records in csv file – salesforce Developer Jun 18 '18 at 17:06
  • 2
    @salesforceDeveloper Unless you have a fixed number of records, any solution to this is just a band-aid; I don't believe Salesforce allows for something like a file pointer so at some point the code has to load the whole file to update it. I'd really recommend you consider revising your requirements to allow for multiple 10 MB files instead. – IllusiveBrian Jun 18 '18 at 18:07
4

I'm inserting the csv file when my first batch run and then in sub-sequent batch, I'm reading the already inserted csv file and updating the same.

In which case, the amount of memory to hold the string will continue to grow with each subsequent batch that runs. If that's the case, it sounds as though you're going to want to monitor the heap by using getLimitHeapSize() and getHeapSize(). Unlike visualforce, I don't think using transient variables will help solve your issue, but it can't hurt either.

If you're creating the final attachment as the sum of a number of chained batch jobs (as opposed to batches in a single job), you'd have the ability to pass the heap used in the previous batch to the next job as a string; updating the string in the finish method of each batch before passing it to the next. I don't believe you can do that with stateful, but you could if the jobs were chained batches (I've done similar in other contexts). I mention that since you show individual queries as opposed to query pointers. If you posted more of your actual code, better guidance could be provided.

EDIT

As requested, below is an example of how to pass a value from one batch job to another using a string variable, manipulated in the finish method and then passed to the next job. Note that this can only be done where you're passing the value where you know the batch will execute once and not in multiple batches for this to work as intended.

public class ClaimUpdateBatch implements Database.Batchable<sObject>{
private final String initialState; // the variable used to pass the state from one batch to another
private final string queryObject;
private final string latestDate;
// the lines below are used to assemble the query strings for each batch    
public string queryStr1A = 'SELECT Id, Claim__c FROM Filing__c WHERE Filed__c <= ';
public string queryStr1B = ' AND Outcome__c = null AND Locked__c = false';
public string queryStr2A = 'SELECT Id, Filing__r.Filed__c, Claim_Update__c FROM Investigation__c WHERE Claim__r.Claim_Filed__c <= ';
public string queryStr2B = '  AND Investigation_Outcome__c = null AND Locked__c = false';
public string queryStr3A = 'SELECT Id, Claim_Update__c FROM Litigation__c WHERE Actual_Signature__c = null AND (Draft_Order__c <= ';
public string queryStr3B = ' OR Request_Filed__c <= '; 
public string queryStr3C = ') AND Locked__c = false';
string query;
public string queryString1;
public string queryString2;
public string queryString3;
public map<string,string>numToObject;
public integer emailAllowed;  
@TestVisible public Id bid;
@TestVisible public Id batchId;
@TestVisible public Id bid1;

    public ClaimUpdateBatch(String initialState, string queryObject, string latestDate) {
        this.initialState = initialState;
        this.latestDate = latestDate;
        this.queryObject = queryObject;

        queryString1 = queryStr1A + latestDate + queryStr1B;
        queryString2 = queryStr2A + latestDate + queryStr2B;
        queryString3 = queryStr3A + latestDate + queryStr3B + latestDate + queryStr3C;

        getNumToObject();
        checkLimits();        
    }


    public void checkLimits(){
        Integer emailMax = limits.getLimitEmailInvocations();
        Integer emailActual = limits.getEmailInvocations();
        emailAllowed = emailMax - emailActual;
        if(emailAllowed > 2) Messaging.reserveSingleEmailCapacity(2);
    }

    public void getNumToObject(){
        // map of the string value to the sobject string the batch will operate on
        numToObject = new map<string,string>();
        numToObject.put('1','Filing__c');
        numToObject.put('2','Investigation__c');
        numToObject.put('3','Litigation__c');      
    }


    @TestVisible  public Database.QueryLocator start(Database.BatchableContext BC) {
        bid = Test.isRunningTest()? '707000000000000AAA': BC.getJobId();
        If(initialState == '1' && numToObject.get(initialState)=='Filing__c') query = queryString1; 
        If(initialState == '2' && numToObject.get(initialState)=='Investigation__c') query = queryString2;
        If(initialState == '3' && numToObject.get(initialState)=='Litigation__c') query = queryString3;
        system.debug('query = ' + query);

        return Database.getQueryLocator(query);

    }


    public void execute(Database.BatchableContext BC,
        List<sObject> scope) {

        if(this.initialState == '1'){ 
            list<Petition__c>ToUpdate = new list<Filing__c>();
            for(sObject s : scope){Filing__c f = (Filing__c)s;
                f.Claim_Update__c = !f.Claim_Update__c;   
                ToUpdate.add(f);    
            }
            update toupdate;    
        } else if(this.initialState == '2'){ 
            list<Investigation__c>ToUpdate = new list<Investigation__c>();
            for(sObject s : scope){Investigation__c n = (Investigation__c)s;
                n.Claim_Update__c=!n.Claim_Update__c;
                ToUpdate.add(n);    
            }
            update toupdate;    
        } else if(this.initialState == '3'){ 
            list<Litigation__c>ToUpdate = new list<Litigation__c>();
            for(sObject s : scope){Litigation__c l = (Litigation__c)s;
                l.Claim_Update__c=!l.Claim_Update__c;
                ToUpdate.add(l);    
            }
            update toupdate;    
        }

    }

    @TestVisible public void finish(Database.BatchableContext BC) {
        system.debug('emailAllowed = ' + emailAllowed);
        system.debug('app.Primary_Admin__c = ' + app.Primary_Admin__c);
        system.debug('app.Org_Admin__c = ' + app.Org_Admin__c);
        bid = Test.isRunningTest()? '707000000000000AAA': BC.getJobId();
        if(emailAllowed>1 ){
            AsyncApexJob aaj = new AsyncApexJob();
            if(bid != '707000000000000AAA'){
                aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
                    FROM AsyncApexJob WHERE Id = :bid ];
            }   
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[]{app.Primary_Admin__c};
            string[] ccAddresses = new string[]{app.Org_Admin__c}; 
            mail.setToAddresses(toAddresses);
            mail.setCcAddresses(ccAddresses);
            mail.setReplyTo('noreply@xxxx.com');
            mail.setSenderDisplayName('Claims Update Batch Processing Job number ' + aaj.Id );
            mail.setSubject('Claims Update Process Completed');
            mail.setPlainTextBody('Batch Process ' + aaj.Id + ' has completed on '+ numToObject.get(initialState) + ' object records with a Status of ' + aaj.Status + 
            '. There were ' + aaj.JobItemsProcessed + ' job items processed with ' + aaj.NumberOfErrors + ' errors.'  );
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
        }
        // below is the means of manipulating the string in this batch when creating the next batch.
        // could use a similar technique to capture the heap and send it to the next batch
        // start next job
        if(integer.valueof(initialState)<3){
            string nextInitialState = string.valueOf(integer.valueOf(initialState) +1);
            system.debug(nextInitialState);
            string nextQueryObject = numToObject.get(nextInitialState);
            batchId = Database.executeBatch(new ClaimUpdateBatch(nextInitialState, nextQueryObject, latestDate));
            System.debug('Returned batch process ID: ' + batchId);
        }
            if(null != batchId) bid1 = batchId;
    }

} 
| improve this answer | |
  • could you please explain more about chained batches, how this is possible using chained batches? – salesforce Developer Jun 18 '18 at 17:12
  • See my edited post with an example as requested – crmprogdev Jun 20 '18 at 12:41

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.