5

I know that the CPU limit is global and shared by all executions within a transaction and my understanding was that this was the only limit that was shared (global). After reviewing the documentation again recently, my interpretation of it is that heap size is as well. I am hoping someone can answer definitively here. Thanks in advance.

13

Heap Size is Complicated™. The limit is shared globally, but only for globally accessible data and the current local data. This means that as long as you're properly encapsulating your data, you should be okay, even with multiple packages and deeply nested transactions. Here's an arbitrary example of this:

Account Trigger

trigger AccountTrigger on Account (after update) {
    Utils.doContactUpdate([select id from contact where accountid = :trigger.new]);
}

Contact Trigger

trigger ContactTrigger on Contact (after update) {
    System.debug('Contact before:'+limits.getHeapSize());
    string s2 = '*'.repeat(3000000);
    System.debug('Contact after:'+limits.getHeapSize());
}

Utils

public class Utils {
    public static void doUpdate(Contact[] records) {    
        string s1 = '*'.repeat(3000001);
        System.debug('Account before:'+limits.getHeapSize());
        update records;
        System.debug('Account after:'+limits.getHeapSize());
    }
}

Here, we've allocated a total of 6,000,001 "bytes" of data, which means we should exceed the limit, right?

(Note: because of how heap is checked, no governor limit would be thrown, since it is violated only briefly.)

In actuality, we never went over even 3.1MB of heap, even though the "s1" is clearly still held in memory in the Account trigger while the contacts are processing; some of the heap is "set aside" to be picked up later.

Here's the debug logs from this run:

Account before:3001511
Contact before:1433
Contact after:3001432
Account after:3001513

What happened? The local storage from the account trigger didn't count towards the heap size of the contact trigger. With a few small changes, we can alter this behavior.

Contact Trigger

trigger ContactTrigger on Contact (after update) {
    System.debug('Contact before:'+limits.getHeapSize());
    utils.s2 = '*'.repeat(3000000);
    System.debug('Contact after:'+limits.getHeapSize());
}

Utils

public class Utils {
    public static string s1, s2;
    public static void doUpdate(Contact[] records) {    
        s1 = '*'.repeat(3000001);
        System.debug('Account before:'+limits.getHeapSize());
        update records;
        System.debug('Account after:'+limits.getHeapSize());
    }
}

Here, we get a different story:

Account before:3001511
Contact before:3001430
Contact after:6001429
Account after:6001513

By using shared data memory, we've gone over the heap limit, which would eventually cause something to crash (possibly even a trigger in a different namespace).

So, in general, keep your static memory usage to a minimum and share your heap with managed packages and other triggers in your own code in the same transaction.


tl;dr Heap is a globally shared resource, but only memory you can actually access at a particular instant in time matters for the calculation of heap usage, such as in recursive DML calls.

3
  • sfdcfox - Thanks for that great explanation, very helpful!
    – bobbyt
    Apr 3 '18 at 23:13
  • The limit is shared globally, but only for globally accessible data and the current local data - I did not know this! The example was illuminating
    – cropredy
    Apr 20 at 15:27
  • @cropredy I loved this question, I definitely learnt something that day.
    – sfdcfox
    Apr 20 at 15:44
1

There's actually quite a few limits that are cumulative across a transaction, and that does include heap size. Note though that the heap size limit is different based on whether the transaction is synchronous (6MB) or asynchronous (12MB).

A smaller number of transaction limits, including the SOQL and DML statement limits, can be modified in the sense that managed packages from the AppExchange receive their own set of limits that raises the allowable total across the whole transaction. However, there's still a maximum limit that applies across the whole transaction, regardless of the number of managed packages involved:

There’s also a limit on the cumulative number of operations that can be made across namespaces in a transaction. This cumulative limit is 11 times the per-namespace limit.

For SOQL, for example, the cumulative or global limit is 1100 queries.

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.