9

Backing way up and asking the smart/dumb question here - what is the usefulness of this, if the transaction is still synchronous, and therefore subject to the same governor limits regardless of chunk size? In fact, doesn't chunking records and firing triggers multiple times actually increase the potential for hitting limits such as queries and DML?

Obviously using batch apex is useful because it creates separate transactions with their own renewed set of limits for each. But why chunk regular synchronous triggers?

7
  • There's another important limit you haven't mentioned: heap. I suggest that this chunking is intended to avoid heap issues. Recall that the records received by the trigger are fully populated so if there are a lot of records you will quickly use up all the available heap space.
    – Phil W
    Commented Sep 27, 2022 at 8:06
  • 2
    BTW, unless this is answered by an actual Salesforce employee it can only be supposition or hearsay. As such I'm voting to close as opinion based.
    – Phil W
    Commented Sep 27, 2022 at 8:43
  • Thanks Phil, but if heap is still a single limit for the whole transaction, how does chunking records help? Wouldn't it, if anything, actually make it worse?
    – number41
    Commented Sep 27, 2022 at 14:47
  • The heap allocation can be chunked up too, similar to what happens in SOQL for loops. Think of it this way; the fully populated records are only constructed and held in memory for the period of time for the execution of the chunk in the trigger to which they belong.
    – Phil W
    Commented Sep 27, 2022 at 15:09
  • 2
    @PhilW By the way, I did some experimentation with heap usage if you want to read more about it. Basically, the heap only includes currently accessible objects in memory (e.g. in a nested trigger, the heap from other triggers don't count).
    – sfdcfox
    Commented Sep 28, 2022 at 9:59

1 Answer 1

12

The original 1.0 API also only supported a single record at the time for a DML operation. There was a batch mode, but it was a separate call that just performed the DML one record a time until it was done. By version 5.0/Spring '07, the limit of operations per API transaction had been raised from 100 to 200. It's hard to tell when the limit was 100, but it's been 200 since.

The original implementation of Apex triggers could only handle one record at a time:

trigger TriggerName on Account (before insert) {
  Account record = Trigger.new;
}

It presumably took a long time to create Apex to begin with, so it makes sense that if the API itself only had single DML operations, Apex triggers would necessarily suffer that limit as well. The move to 100 records to begin with was the anticipation that Apex would support lists of records for DML operations, but triggers lagged behind the main runtime, it seems.

Next, bulk triggers were introduced sometime in Spring '07. You had to specify the keyword bulk to get this behavior. Triggers after May 07, 2007 that had not been bulkified were disabled.

trigger TriggerName on Account bulk (before insert) {
  for(Account record: Trigger.new) {
    ...
  }
}

These triggers supported the previous limit of 100 records per DML transaction. Around the same time, in Spring 2007, it was announced that the limit per DML transaction was being increased from 100 to 200.

It's important to note that the underlying database is what drove this limitation. The original batch mode was provided by the Salesforce runtime to provide the ability to update many records at once in the database, at a time when the database itself could only do one record at a time.

When the database was updated to handle 100 records at a time, the Salesforce runtime was then updated to provide these newer features, and the APIs that called the Salesforce runtime were also freed from the restrictions. At this point, Apex chunked triggers into batches of 100. This fix came out at the same time that the Salesforce runtime could now handle 200 records in a transaction.

Initially, because of the version skew, the Salesforce runtime would detect if any 100-limited triggers were involved, and if so, chunk the 200-limited chunks into up to 2 100-limited batches so these triggers would still work without possible errors. Otherwise, it could use the newer database interfaces that supported 200 records per call.

As the older API versions are being forever deprecated, it means that the Salesforce runtime can be purged of the original single-record-only database methods and the 100-record-only database methods in favor of always using the 200-record-only database methods.

The number 200 is somewhat arbitrary, but it was chosen as a balance between performance and resource allocation. Remember, the Salesforce runtime is handling most of the non-database work, calling the database up to a few hundred times per transaction. The database itself is optimized for performance, so the batches need to remain small.

So, Apex batches records into groups of 200 because the Salesforce runtime has to call database methods ("Stored Procedures") that are limited to 200 records per DML operation for performance reasons. Triggers themselves are called by the Salesforce runtime, not the underlying database, as far as I understand it.

There's a concept of "marshalling" in Apex code. This means that the Apex runtime is a virtual machine running in the Salesforce runtime written in Java, and when certain methods are called in Apex, those are executed as particular functions written in Java. The Salesforce runtime is also responsible for most governor limit enforcement, translating SOQL into Oracle database calls, etc.

Most of this information was gleaned from publicly-available resources and some educated guesses from what I've learned over the years with Salesforce technology. Of course, I don't know all the internals, as I've been been a Salesforce Engineer (R&D) over at salesforce.com, but this should serve as a close-enough approximation.

In conclusion, the limits that are in place are of a technical nature, the limits placed on the underlying Oracle databases and the Salesforce runtime, to ensure that everyone has equal access to resources in a multi-tenant environment.

While it's true that larger transactions might use fewer overall governor limits, it would also require updating the core database methods to support these larger DML operations, rather than just the Salesforce runtime. This would require careful consideration of the resources already being used and the impact of increasing that limit.

There would presumably be a lot technical debt to clean up to allow larger DML operations. I trust that the Salesforce Engineers that chose these limits were not arbitrary in their decision-making, as I don't know nearly enough about Oracle database optimization to say anything further.

You must log in to answer this question.

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