Partial success and trigger firing

Yesterday, I was baffled by the following observed behavior:

  1. Insert six Contacts using Database.insert(cList,false), five of which had some validation error, one which succeeded
  2. The successful Contact, in the before insert trigger, derives the value of accountId, if null in the inserted record
  3. The system.assert to verify the defaulting of accountId failed, even though the debug log clearly showed that it was set

Further investigation of the debug log showed the before insert trigger for the successful Contact was executing twice, and in the second iteration, the defaulting of the accountId was not occurring.

Before insert trigger executed twice? Huh?

After some reductionist testing, I verified that this only happened when using optAllOrNothing = false in Database.insert(cList,false). That is, allow for partial successes. According to the documentation (in a section I had never read or paid attention to).

When errors occur because of a bulk DML call that originates from the SOAP API with default settings, or if the allOrNone parameter of a Database DML method was specified as false, the runtime engine attempts at least a partial save:

(1) During the first attempt, the runtime engine processes all records. Any record that generates an error due to issues such as validation rules or unique index violations is set aside.

(2) If there were errors during the first attempt, the runtime engine makes a second attempt that includes only those records that did not generate errors. All records that didn’t generate an error during the first attempt are processed, and if any record generates an error (perhaps because of race conditions) it is also set aside.

(3) If there were additional errors during the second attempt, the runtime engine makes a third and final attempt which includes only those records that didn’t generate errors during the first and second attempts. If any record generates an error, the entire operation fails with the error message, “Too many batch retries in the presence of Apex triggers and partial failures.”

It is the second point that is interesting – “the runtime engine makes a second attempt that includes only those records that did not generate errors” – ahhh, that is why the debug log showed two executions of before insert on the same record.

Now, why did the second attempt not default the Contact’s accountId using my Apex logic?

Answer: At the end of my before insert trigger handler, I set a static variable to prevent the trigger handler from re-executing (sort of a reflex action to avoid unnecessary SOQLs). Hence, when the second attempt at before insert was made, the static variable prevented the defaulting and the record saved successfully, except without a non-null AccountId. Hence the assertion failed.

A different portion of the SFDC Apex doc states:

When a DML call is made with partial success allowed, more than one attempt can be made to save the successful records if the initial attempt results in errors for some records. For example, an error can occur for a record when a user-validation rule fails. Triggers are fired during the first attempt and are fired again during subsequent attempts. Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger aren’t reset. DML calls allow partial success when you set the allOrNone parameter of a Database DML method to false or when you call the SOAP API with default settings. For more details, see Bulk DML Exception Handling.

Note the sentence: “Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger aren’t reset”.

So, while governor limits are not affected by the retry, the static variables remain persistent and hence the trigger handler derivation logic remained switched off as a result of the conclusion of the initial before insert trigger handler.

Removing the setting of the static variable solved the issue.

9 thoughts on “Partial success and trigger firing

  1. Sebastian

    Hi, thx for the post, i have a similar situation i tried the solution you said and works great, however not using the static variable to prevent the trigger handler from re-executing is not a option, there is any other workaround? thx in advance for you reply.

    Reply
    1. eric.kintzer@cropredy.com Post author

      Sebastian. StackExchange user sfdcfox had an interesting answer to a related question the other day that will resolve (I think) your issue. I wish I could find the answer to give you the link, it was in the last 10 days. Anyway, what he did was to test a static vbl for true at beginning of trigger, and if false, set to true and process trigger. At end of trigger, reset static vbl to false. Thus, the static vbl acts as a lock, but only for the duration of the trigger, not for the duration of the transaction. Thus, the multiple invocations of the same trigger via retry will not be hindered.

      Reply
      1. Sebastian

        Thx Eric for you reply i’ll find that post you talk about, and try to reset the variable, i’ll keep you update.

        Reply
  2. Arzoo Barmate

    Hey, So I have a record of a custom object being created twice i.e duplicated record in same transaction and I checked the debig which states the error first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, Too many retries of batch save in the presence of Apex triggers with failures: when triggers are present partial save requires that some subset of rows save without any errors in order to avoid inconsistent side effects from those triggers. Number of retries: 2: [].
    So it related to partial success of trigger saving the record?
    Thanks, please let me know.

    Reply
  3. Hariprasath

    Thanks for this post, greatly explained the issue.

    I have faced the similar kind of issue like this.

    I’ve created a before insert trigger that do enforce creating duplication record and assign some specific value to the non duplicate records field. The logic was working perfectly fine when there were no error records. However, if there are any error record in the bulk insertion the value assignment was not happening to the record.

    Is it a known behavior?

    Reply
    1. eric.kintzer@cropredy.com Post author

      Hariprasath — you’ll need to explain your issue in more detail. The doc is clear, if using allOrNothing = false, then records that fail are set aside, records that succeed are retried. If you have static variables that are preventing recursion, those static variables are not reset on the retry – so, in fact, no actual updates may occur in related records

      Reply
  4. Pingback: Database DML method executes twice in any record has been failed

Leave a Reply

Your email address will not be published. Required fields are marked *