10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Apexトリガ二重起動抑止パターンと一括レコード処理時の破綻ケースについて

Last updated at Posted at 2021-08-11

Apexトリガにおいて、ワークフローやプロセス、およびその他のトリガによる再帰的な処理の発生を防ぐために、Apexトリガの二重起動抑止(=リエントランス制御)をコードに組み込むパターンがままある。

以下、代表的なコード。

SampleTriggerHandler.cls
public with sharing class SampleTriggerHandler {
  public static boolean triggerCalled = false;
  public static void handleBeforeUpdate(List<SampleObject__c> recs) {
    for (SampleObject__c rec: recs) {
      // 何らかの処理
    }
  }
}
SampleTriger.trigger
trigger SampleTrigger on SampleObject__c (before update) {
  if (!SampleTriggerHandler.triggerCalled) {
    SampleTriggerHandler.triggerCalled = true;
    SampleTriggerHandler.handleBeforeUpdate(Trigger.new);
  }
}

このようなトリガが設定されている場合、一括でのレコード処理を実施可能な環境下では不具合あるいはシステムの破綻をきたす可能性がある。

一括 DML 例外処理

デフォルト設定で SOAP API から発生した一括 DML コールが原因でエラーが発生した場合、または Database DML メソッドの allOrNone パラメータが false に指定されている場合は、ランタイムエンジンが少なくとも部分的な保存を試みます。
最初の試行で、ランタイムエンジンはすべてのレコードを処理します。入力規則や独自のインデックス違反などの問題によるエラーを生成したレコードは、除外されます。
1. 最初の試行でエラーが生じた場合、ランタイムエンジンは、エラーを生成しなかったレコードのみを含む 2 回目の試行を行います。
2. 最初の試行でエラーを生成しなかったすべてのレコードが処理され、競合の条件などが理由でエラーを生成したレコードがあれば、それも除外されます。
3. 2 回目の試行中に追加エラーがあった場合、ランタイムエンジンは、初回と 2 回目にエラーを生成しなかったレコードのみを含む 3 回目 (最後) の試行を行います。エラーを生成したレコードがある場合、操作全体は失敗し、エラーメッセージ「Too many batch retries in the presence of Apex triggers and partial failures (Apex トリガと部分的な失敗がある場合にバッチ試行の回数が多すぎます)」が表示されます。

つまり、一括処理対象の複数レコードの中に(入力規則チェック等で)エラーとなるレコードを含む場合、エラーとなるレコード以外の正常処理されたレコードに対しては2回目のDML処理が実行される。

上記のようなstatic変数によるフラグでの二重起動抑止が組み込まれている場合、2回目のDMLではすでにトリガの呼び出しフラグがセット済みであるため、二重起動抑止がされ、トリガハンドラのコードが実行されないまま正常系のレコードが保存される。これにより、保存時に必ずトリガが動作することを期待しているシステムは破綻した状態となる。

自分が試したPoCのコードは以下のレポジトリにおいておく。
https://github.com/stomita/dml-trigger-allornone-test

これらの詳細について解析したもの。
https://github.com/KrishnaKollu/poc-approaches-salesforce-trigger-recursion

In a nutshell, leveraging just static variables to manage trigger recursion is unsafe and could result in your trigger not firing consistently.

例えば、データローダーなどは動作的にallOrNone=falseの扱いとなっているかとおもうので、該当トリガを含むオブジェクトが一括ロードを行う対象となっている場合は顕著に発生する。

最近はリストビュー以外にもUIから一括で更新を行うツール類が増えてきているため、開発時に標準詳細画面からの単票更新のみを想定していると、このような問題に後から直面することになる。

残念ながらこれといって王道の回避方法があるわけではなく、可能であれば二重起動抑止制御を行わないで済むようなシンプルなトリガ構成としておくのが最良と思われる。ApexでSalesforceの開発を行う開発者におかれましてはぜひ注意されたい。

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?