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.
2 Answers
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.
-
-
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– cropredyCommented Apr 20, 2021 at 15:27 -
@cropredy I loved this question, I definitely learnt something that day.– sfdcfox ♦Commented Apr 20, 2021 at 15:44
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.