+ Start a Discussion
SeanCenoSeanCeno 

Heap Size Too Large Error

Hey Everyone,

Recently a new error has come up when one of my batch classes runs causing trades from the day before not to post. This code has worked fine since implemented over 6 months ago. I've never dealt with a Heap Size error before so I'm not sure how to fix it. From what I've researched it involves having to many objects, but the code seems pretty efficient. Unless it's the test class, I don't know where the problem would be. Please take a look and let me know if you can help!

Error:
Trades_CascadeAccounts: System.LimitException: Apex heap size too large: 6220442
Trades_CascadeAccounts: System.LimitException: Apex heap size too large: 6220442
Trades_CascadeAccounts: System.LimitException: Apex heap size too large: 6220442
Trades_CascadeAccounts: System.LimitException: Apex heap size too large: 6220442
Trades_CascadeAccounts: System.LimitException: Apex heap size too large: 6220442

Apex Class:

public class Account_RollupTrades {
    public Static Account_Setting__c setting = Account_Setting__c.getInstance();
    public Static boolean inprog = false;
   
    public static void execute (Set<Id> accountIds, List<Account> accountsList) {
        Map<Id, Account> accounts = new Map<Id, Account> (AccountsList);
        system.debug ('execute');
        if(setting.Disable_RollupTrades__c != true) {
            //Map<Id, Account> accounts = new Map<Id, Account>();
            for(Id accountId:accountIds) {
                system.debug(accountid);
                accounts.put(accountId,
                   new Account(
                       Id=accountId,
                       YTD_NIOR_I_Sales__c = 0,         YTD_NIOR_I_Shares__c = 0,         QTD_NIOR_I_Sales__c = 0,         QTD_NIOR_I_Shares__c = 0,
                       MTD_NIOR_I_Sales__c = 0,         MTD_NIOR_I_Shares__c = 0,         PY_NIOR_I_Sales__c = 0,          PY_NIOR_I_Shares__c = 0,
                       Total_NIOR_I_Sales__c = 0,       Total_NIOR_I_Shares__c = 0,       YTD_NS_Income_Sales__c = 0,      YTD_NS_Income_Shares__c = 0,
                       QTD_NS_Income_Sales__c = 0,      QTD_NS_Income_Shares__c = 0,      MTD_NS_Income_Sales__c = 0,      MTD_NS_Income_Shares__c = 0,
                       PY_NS_Income_Sales__c = 0,       PY_NS_Income_Shares__c = 0,       Total_NS_Income_Sales__c = 0,    Total_NS_Income_Shares__c = 0,
                       Total_NS_HI_Sales__c = 0,       Total_NS_HI_Shares__c = 0,       YTD_NS_HI_Sales__c = 0,         YTD_NS_HI_Shares__c = 0,
                       QTD_NS_HI_Sales__c = 0,         QTD_NS_HI_Shares__c = 0,         MTD_NS_HI_Sales__c = 0,         MTD_NS_HI_Shares__c = 0,
                       PY_NS_HI_Sales__c = 0,          PY_NS_HI_Shares__c = 0,          Total_NS_Income_II_Sales__c = 0, Total_NS_Income_II_Shares__c = 0,
                       YTD_NS_Income_II_Sales__c = 0,   YTD_NS_Income_II_Shares__c = 0,   QTD_NS_Income_II_Sales__c = 0,   QTD_NS_Income_II_Shares__c = 0,
                       MTD_NS_Income_II_Sales__c = 0,   MTD_NS_Income_II_Shares__c = 0,   PY_NS_Income_II_Sales__c = 0,    PY_NS_Income_II_Shares__c = 0,
                       Rollup_Trades__c = DateTime.now()
                   )
                            );
            }
           
        // Roll up the trades based on the Resolved Firm Trading ID field
        Trades__c[] tradesList = [
            select Dollar_Amount_of_the_transaction__c, Fund_Number__c, Number_of_Shares_of_the_transaction__c,
                Resolved_to_Rep_Trading_ID__c, Resolved_Firm_Trading_ID__c, Resolved_Firm_Trading_IDs__c,
                Trade_Date__c
              from Trades__c
             where Resolved_Firm_Trading_ID__c in :accountIds
               and Fund_Number__c in ('3910', '3911', '3912', '3915')       // NIOR I; NS Income; NS HI; NS Income II
               and Dollar_Amount_of_the_transaction__c != null      // prevents null pointers below
               and Number_of_Shares_of_the_transaction__c != null   // prevents null pointers below
               and Trade_Date__c != null                            // prevents null pointers below
               // Negative values are ignored for roll-up purposes
               and Dollar_Amount_of_the_transaction__c >= 0
               and Number_of_Shares_of_the_transaction__c >= 0
        ];
           
            Map<String, SObjectField[]>
                ytd = new map<string, sobjectfield[]> {
                    '3910' => new sobjectfield[] { account.YTD_NIOR_I_Sales__c , account.YTD_NIOR_I_Shares__c},
                    '3911' => new sobjectfield[] { account.YTD_NS_Income_Sales__c , account.YTD_NS_Income_Shares__c },
                    '3912' => new sobjectfield[] { account.YTD_NS_HI_Sales__c , account.YTD_NS_HI_Shares__c },
                    '3915' => new sobjectfield[] { account.YTD_NS_Income_II_Sales__c , account.YTD_NS_Income_II_Shares__c }   
                },
                qtd = new map<string, sobjectfield[]> {
                    '3910' => new sobjectfield[] { account.QTD_NIOR_I_Sales__c , account.QTD_NIOR_I_Shares__c},
                    '3911' => new sobjectfield[] { account.QTD_NS_Income_Sales__c , account.QTD_NS_Income_Shares__c },
                    '3912' => new sobjectfield[] { account.QTD_NS_HI_Sales__c , account.QTD_NS_HI_Shares__c },
                    '3915' => new sobjectfield[] { account.QTD_NS_Income_II_Sales__c , account.QTD_NS_Income_II_Shares__c }
                },
                mtd = new map<string, sobjectfield[]> {
                    '3910' => new sobjectfield[] { account.MTD_NIOR_I_Sales__c , account.MTD_NIOR_I_Shares__c},
                    '3911' => new sobjectfield[] { account.MTD_NS_Income_Sales__c , account.MTD_NS_Income_Shares__c },
                    '3912' => new sobjectfield[] { account.MTD_NS_HI_Sales__c , account.MTD_NS_HI_Shares__c },
                    '3915' => new sobjectfield[] { account.MTD_NS_Income_II_Sales__c , account.MTD_NS_Income_II_Shares__c }
                },
                py = new map<string, sobjectfield[]> {
                    '3910' => new sobjectfield[] { account.PY_NIOR_I_Sales__c , account.PY_NIOR_I_Shares__c},
                    '3911' => new sobjectfield[] { account.PY_NS_Income_Sales__c , account.PY_NS_Income_Shares__c },
                    '3912' => new sobjectfield[] { account.PY_NS_HI_Sales__c , account.PY_NS_HI_Shares__c },
                    '3915' => new sobjectfield[] { account.PY_NS_Income_II_Sales__c , account.PY_NS_Income_II_Shares__c }
                },
                total = new map<string, sobjectfield[]> {
                    '3910' => new sobjectfield[] { account.Total_NIOR_I_Sales__c , account.Total_NIOR_I_Shares__c},
                    '3911' => new sobjectfield[] { account.Total_NS_Income_Sales__c , account.Total_NS_Income_Shares__c },
                    '3912' => new sobjectfield[] { account.Total_NS_HI_Sales__c , account.Total_NS_HI_Shares__c },
                    '3915' => new sobjectfield[] { account.Total_NS_Income_II_Sales__c , account.Total_NS_Income_II_Shares__c }
                };

            for(trades__c trade:tradesList) {
                if(date.today().year() == trade.trade_date__c.year()) {
                    accounts.get(trade.Resolved_Firm_Trading_ID__c).put(ytd.get(trade.fund_number__c)[0], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(ytd.get(trade.fund_number__c)[0]))+trade.Dollar_Amount_of_The_Transaction__c);
                    accounts.get(trade.Resolved_Firm_Trading_ID__c).put(ytd.get(trade.fund_number__c)[1], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(ytd.get(trade.fund_number__c)[1]))+trade.Number_of_Shares_of_the_transaction__c);

                    if( (Decimal.ValueOf(date.today().month()).divide(3, 0) == Decimal.ValueOf(trade.trade_date__c.month()).divide(3, 0)) )   {
                        accounts.get(trade.Resolved_Firm_Trading_ID__c).put(qtd.get(trade.fund_number__c)[0], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(qtd.get(trade.fund_number__c)[0]))+trade.Dollar_Amount_of_The_Transaction__c);
                        accounts.get(trade.Resolved_Firm_Trading_ID__c).put(qtd.get(trade.fund_number__c)[1], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(qtd.get(trade.fund_number__c)[1]))+trade.Number_of_Shares_of_the_transaction__c);

                        if(date.today().month()==trade.trade_date__c.month()) {
                            accounts.get(trade.Resolved_Firm_Trading_ID__c).put(mtd.get(trade.fund_number__c)[0], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(mtd.get(trade.fund_number__c)[0]))+trade.Dollar_Amount_of_The_Transaction__c);
                            accounts.get(trade.Resolved_Firm_Trading_ID__c).put(mtd.get(trade.fund_number__c)[1], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(mtd.get(trade.fund_number__c)[1]))+trade.Number_of_Shares_of_the_transaction__c);
                        }
                    }
                } else if(date.today().year()-1==trade.trade_date__c.year()) {
                    accounts.get(trade.Resolved_Firm_Trading_ID__c).put(py.get(trade.fund_number__c)[0], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(py.get(trade.fund_number__c)[0]))+trade.Dollar_Amount_of_The_Transaction__c);
                    accounts.get(trade.Resolved_Firm_Trading_ID__c).put(py.get(trade.fund_number__c)[1], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(py.get(trade.fund_number__c)[1]))+trade.Number_of_Shares_of_the_transaction__c);
                }
                accounts.get(trade.Resolved_Firm_Trading_ID__c).put(total.get(trade.fund_number__c)[0], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(total.get(trade.fund_number__c)[0]))+trade.Dollar_Amount_of_The_Transaction__c);
                accounts.get(trade.Resolved_Firm_Trading_ID__c).put(total.get(trade.fund_number__c)[1], ((Decimal)accounts.get(trade.Resolved_Firm_Trading_ID__c).get(total.get(trade.fund_number__c)[1]))+trade.Number_of_Shares_of_the_transaction__c);
            }
        }
     inprog = true;
     update accounts.values();
     inprog = false;
    }
}


Apex Trigger:

trigger Account_RollupTrades on Account (after update) {
    if(Account_RollupTrades.inprog == false) {
        set<ID> sID = new set<ID> (trigger.newMap.keySet());
        Account_RollupTrades.execute(sID, trigger.new);
    }
}

Best Answer chosen by SeanCeno
Blake TanonBlake Tanon
something like this, assuming updating an account is what triggers your code above.


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

    /*run this batch the first time with this
    Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Number_Of_Trades > XXXX','Trades_CascadeAccountsBatchable run 2'), 1);
    */

    global string query;
    global string nextBatch;

    global Trades_CascadeAccountsBatchable(string q, string nb){

        //what is the criteria for running this on an account?
        if(q != null)query = q;
        else query = 'select Id from Account where ParentId = null';

        //for the second run
        nextBatch = nb;
       

    }

    global Database.QueryLocator start(Database.BatchableContext batchableContext){
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext batchableContext, Account[] accountList) {
       
            account.Rollup_Trades__c = null;
           
            update accountList;
            //does this update make the rest of your code run?
    }

    global void finish(Database.BatchableContext batchableContext) {

        //if nextBatch is 'Trades_CascadeAccountsBatchable run 2' then run it
        if(nextBatch == 'Trades_CascadeAccountsBatchable run 2'){
            //put in here the query to pull the rest of the accounts, with lower number of trades
            Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Number_Of_Trades <= XXXX',null), 200);
        }

    }
}

All Answers

SeanCenoSeanCeno
Apex Class with Error:

public class Trades_CascadeAccounts {
    public Trades__c[] tradesOldList { set; get; }
    public Trades__c[] tradesNewList { set; get; }
    public Map<Id, Trades__c> tradesOldListMap { set; get; }
   
    public Trades_CascadeAccounts(Trades__c[] tradesOldList, Trades__c[] tradesNewList) {
        this.tradesNewList = tradesNewList == null ? new Trades__c[0] : tradesNewList.clone();
        this.tradesOldList = tradesOldList == null ? new Trades__c[0] : tradesOldList.clone();
        this.tradesOldListMap = new Map<Id, Trades__c>(this.tradesOldList);
    }
   
    public void execute() {
        Set<Id> tradesAccountIds = new Set<Id> {};
        for(Trades__c tradesNew : tradesNewList) {

   Trades__c tradesOld = tradesOldListMap.containsKey(tradesNew.Id) ? tradesOldListMap.get(tradesNew.Id) : new Trades__c();

   tradesAccountIds.addAll(new Set<Id> { tradesOld.Resolved_Firm_Trading_ID__c, tradesNew.Resolved_Firm_Trading_ID__c });
        }
  tradesAccountIds.remove(null);

        Account[] accountList = new Account[0];
       
        for(Id accountId : tradesAccountIds) {

   accountList.add(new Account(Id=AccountId,Rollup_Trades__c=null));
        }
       
        update accountList;
        System.debug('Total Number of script statements allowed in this apex code context: ' +  Limits.getLimitScriptStatements());
    }
}
Blake TanonBlake Tanon
One(or a few) of your account probably has a large number of trade records and it is creating a large heap sze because you summing trade values one by one.  How many account records are being updated per transaction?

I work for a fund raising company also, we ran into this sort of problem about a year ago and moved this sort of aggragetion to batchs rather than having them run in a trigger.  
SeanCenoSeanCeno
Hey thanks for your response. For National Accounts, which is what this deals with, we probably have around 60 active firms. The Apex Job itself is showing that it ran 80 batches this morning. I just find it strange as our individual Contacts batch job, which runs right after this Accounts batch, doesn't fail and it is a much higher volume, but I understand what you're saying. 

So let's say I wanted to run a batch for a single firm that usually has the most trades every day (LPL). How would one go about including that firm in one batch and not another? Or how do I manage the heap size on my existing batch?
Blake TanonBlake Tanon
LPL is one of our largest also, whats your batch size now, 200?  Just cut it in half and see if it works.  What I found is that with firms that size, when you're trying to get rep counts, trade counts, material counts etc it's best to run them in a batch with a size of 1 - the rest can be done at 200.  I added a checkbox to these accounts to run them first @ 1 then all else @ 200.  I hate doing manual updating, but this list really won't change much - I even checked off other large firms so if/when we sign them we're good to go.


You can also split the batch into 2 seperate runs, change the the batch's query to get only accounts with > xxx number of trades, runs then run the same again with <= xxx in the query.
SeanCenoSeanCeno
Yeah right now it's at 200. That's a very interesting concept and I think it will work. I'll see if I can pull it off ha! Thanks!
SeanCenoSeanCeno
Something like this?

global class Trades_CascadeAccountsBatchable implements Database.Batchable<sObject>, Database.Stateful {
    global Database.QueryLocator start(Database.BatchableContext batchableContext){
        return Database.getQueryLocator('select Id from Account where ParentId = null');
    }
    global void execute(Database.BatchableContext batchableContext, Account[] accountList) {
        for(Account account : accountList)
            account.Rollup_Trades__c = null;
            Database.executeBatch(account.Rollup_Trades__c = null, 50);
            update accountList;
    }
    global void finish(Database.BatchableContext batchableContext) {
    }
}
Blake TanonBlake Tanon
something like this, assuming updating an account is what triggers your code above.


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

    /*run this batch the first time with this
    Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Number_Of_Trades > XXXX','Trades_CascadeAccountsBatchable run 2'), 1);
    */

    global string query;
    global string nextBatch;

    global Trades_CascadeAccountsBatchable(string q, string nb){

        //what is the criteria for running this on an account?
        if(q != null)query = q;
        else query = 'select Id from Account where ParentId = null';

        //for the second run
        nextBatch = nb;
       

    }

    global Database.QueryLocator start(Database.BatchableContext batchableContext){
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext batchableContext, Account[] accountList) {
       
            account.Rollup_Trades__c = null;
           
            update accountList;
            //does this update make the rest of your code run?
    }

    global void finish(Database.BatchableContext batchableContext) {

        //if nextBatch is 'Trades_CascadeAccountsBatchable run 2' then run it
        if(nextBatch == 'Trades_CascadeAccountsBatchable run 2'){
            //put in here the query to pull the rest of the accounts, with lower number of trades
            Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Number_Of_Trades <= XXXX',null), 200);
        }

    }
}
This was selected as the best answer
SeanCenoSeanCeno
That seems much more logical. We don't have a field that tracks number of trades, but we do have Total Sales so I set it to above 75MM. Hopefully that will diferentiate enough. Does this look ok?

global class Trades_CascadeAccountsBatchable implements Database.Batchable<sObject>, Database.Stateful {
    //run this batch the first time with this
    Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Total_Sales__c > 75000000','Trades_CascadeAccountsBatchable run 2'), 1);

    global string query;
    global string nextBatch;

    global Trades_CascadeAccountsBatchable(string q, string nb){

        //what is the criteria for running this on an account?
        if(q != null)query = q;
        else query = 'select Id from Account where ParentId = null';

        //for the second run
        nextBatch = nb;
    }

    global Database.QueryLocator start(Database.BatchableContext batchableContext){
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext batchableContext, Account[] accountList) {
        for(Account account : accountList)
            account.Rollup_Trades__c = null;
        update accountList;
    }
    global void finish(Database.BatchableContext batchableContext) {
        //if nextBatch is 'Trades_CascadeAccountsBatchable run 2' then run it
        if(nextBatch == 'Trades_CascadeAccountsBatchable run 2'){
            //put in here the query to pull the rest of the accounts, with lower number of trades
            Database.executeBatch(new Trades_CascadeAccountsBatchable('SELECT id FROM Account WHERE Total_Sales__c <= 75000000',null), 200);
        }

    }
}
SeanCenoSeanCeno
I seem to be getting a compile error on line 3
"Expecting a right paranthesis, found 'new'."
Blake TanonBlake Tanon
Add 'ID idBatch = ' before the Database.executeBatch

ID idBatch = Database.executeBatch(......
SeanCenoSeanCeno
Oh haha may bad!
Andray GaustAndray Gaust
How can you short trade data due to this algorithm? And can i apply this on my website Daniel Pessin (https://danielpessintrades.com/).