はじめに
現在携わっている案件で、環境の大幅整備が始まりました。
ざっくり書くと、下記の作業をしています。
- 親オブジェクトがほぼすべての項目を持っているので、項目を持つオブジェクトを適切に親子に割り振る
(親が400項目、子が60項目ほどでした・・) - ワークフロールールをフローに変換する
この作業に伴い、ひいこら言いながら諸々修正したのですが・・。
いざテストクラスを実行するとToo Many 101 Error
が発生。
結論としてはフローが増えたせいだったのですが・・。
せっかくなので、増え方について調べました。
前提
オブジェクトA、オブジェクトBを作成。
それぞれにトリガーやらフローやらを作成し、クエリ回数がどう増えていくかを検証する。
検証
トリガーのみ
オブジェクトAのトリガーを下記の通り作成した。
trigger ObjectATrigger on ObjectA__c (before insert, before update, after insert, after update) {
List<User> uList = [SELECT Id FROM User];
}
そのまま、1件レコードをインサートしたときに結果がこんな感じ。
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c();
System.debug(Limits.getQueries()); // 2
beforeで1回、afterで1回なので想定通り。
フローを1つ作成
beforeのタイミングで動作する、ユーザーを取得するだけのフローを作成。
結果は下記の通り。
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c();
System.debug(Limits.getQueries()); // 3
取得クエリが1つ増えたわけだから、まあこれも想定通り。
フローを2つに増やす
上と全く同じ動きのフローをもう一つ作成する。
結果は下記の通り。
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c();
System.debug(Limits.getQueries()); // 4
取得クエリが更に1つ増えたわけだから、これも想定通り。
フローを、afterのタイミングでオブジェクトBをinsertするフロー1つのみにする
上で作成したフローは一旦無効化し、afterのタイミングでオブジェクトBをinsertするフローを用意。
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c();
System.debug(Limits.getQueries()); // 4
ObjectAトリガーの、before/afterで2回
同じくObjectBトリガーのbefore/afterで2回の計4回。
afterのタイミングで同一のオブジェクトBをupdateするフロー2つにする
ちょっと飛んで、トリガーとは別の同一レコードに対して、2回更新処理をさせてみる。
同一のレコード更新を保証するため、オブジェクトAにオブジェクトBへの参照項目を追加した。
(何も考えず登録したら、だめな例の典型みたいな名前になった)
更新内容はどちらも同じく所有者 = User.Id
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c(
B__c = b.Id
);
System.debug(Limits.getQueries()); // 8
- オブジェクトAのbefore/afterで2回
- オブジェクトBのbefore/afterで2回 * 更新が2回 = 4回
の合計6回だと思っていた・・が、実際には8回。
推測
コンポーネントとしては更新一つだが、おそらく内部的にはSOQLで更新対象を取得
→ update
という流れなのだと思う。
なので
- オブジェクトAのbefore/afterで2回
- オブジェクトAのフローで2回
- オブジェクトBのbefore/afterで2回 * 更新が2回 = 4回
の計8回になったものと思われる。
おまけ:無限ループっぽくするとどうなる?
上のフローはそのままに、オブジェクトBのafterでオブジェクトAを更新するフローを作成する。
作成/更新時に自分に紐づくオブジェクトAを更新するだけの、シンプルなフロー。
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c(
B__c = b.Id
);
System.debug(Limits.getQueries()); // 14
無限ループとはならず、途中で止まった。
ログを見る限りだと、こんな感じ。
- オブジェクトAが登録される
- 1つめのフローによって、Bが更新される
- 上のフローでの更新をトリガーに、Aが更新される
このとき、Bは更新されない - 2つめのフローによって、Bが更新される
- 上のフローでの更新をトリガーに、Aが更新される
このとき、Bは更新されない
ということで、更新処理が自分に帰ってきた場合、更新せずに止まるようになってるっぽい。
Salesforceくんは賢いねぇ。
おまけのおまけ:トリガーも絡ませるとどうなる?
上の状態と合わせて、トリガー側でもオブジェクトAを更新させてみる。
trigger ObjectBTrigger on ObjectB__c (before insert, before update, after insert, after update) {
System.debug('ObjectBTrigger:start ' + Limits.getQueries());
List<User> uList = [SELECT Id FROM User];
if (Trigger.isAfter) {
update [SELECT Id FROM ObjectA__c WHERE B__c IN :Trigger.new];
}
}
System.debug(Limits.getQueries()); // 0
insert new ObjectA__c(
B__c = b.Id
);
System.debug(Limits.getQueries()); // 20
フローと同様に、帰ってきた分の更新がかかっていないみたい。
なので、増分としては予想通り。
おわりに
- 更新処理を複数フローに用意した場合、まとめて一回にするような処理はない
- 「レコードを更新」でトリガー以外のレコードを更新すると、実際には一度SOQLを実行しているため、カウントが増える点に注意
- よく考えると当たり前ではある・・。
- 相互で更新させても無限ループにはならないけど、カウントはもりもり増えるから気をつけよう
上に書いたものは、すべてトリガーでイメージしたら当たり前ではあるんですが・・。
それを踏まえて考えると、フローをたくさん用意するのってリスクがかなり大きい気がします。
じゃあもう、やっぱトリガーが良い・・。