0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フローが増えたら、合わせてクエリ回数が爆増したので調べた

Last updated at Posted at 2025-01-14

はじめに

現在携わっている案件で、環境の大幅整備が始まりました。
ざっくり書くと、下記の作業をしています。

  • 親オブジェクトがほぼすべての項目を持っているので、項目を持つオブジェクトを適切に親子に割り振る
    (親が400項目、子が60項目ほどでした・・)
  • ワークフロールールをフローに変換する

この作業に伴い、ひいこら言いながら諸々修正したのですが・・。
いざテストクラスを実行するとToo Many 101 Errorが発生。

結論としてはフローが増えたせいだったのですが・・。

せっかくなので、増え方について調べました。

前提

オブジェクトA、オブジェクトBを作成。
それぞれにトリガーやらフローやらを作成し、クエリ回数がどう増えていくかを検証する。

検証

トリガーのみ

オブジェクトAのトリガーを下記の通り作成した。

ObjectATrigger.trigger
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のタイミングで動作する、ユーザーを取得するだけのフローを作成。
image.png

結果は下記の通り。

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するフローを用意。
image.png

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回

推測

今回作成したフローがこちら。
image.png

コンポーネントとしては更新一つだが、おそらく内部的にはSOQLで更新対象を取得updateという流れなのだと思う。
なので

  • オブジェクトAのbefore/afterで2回
  • オブジェクトAのフローで2回
  • オブジェクトBのbefore/afterで2回 * 更新が2回 = 4回

の計8回になったものと思われる。

おまけ:無限ループっぽくするとどうなる?

上のフローはそのままに、オブジェクトBのafterでオブジェクトAを更新するフローを作成する。
作成/更新時に自分に紐づくオブジェクトAを更新するだけの、シンプルなフロー。

image.png

System.debug(Limits.getQueries()); // 0
insert new ObjectA__c(
    B__c = b.Id
);
System.debug(Limits.getQueries()); // 14

無限ループとはならず、途中で止まった。
ログを見る限りだと、こんな感じ。

  1. オブジェクトAが登録される
  2. 1つめのフローによって、Bが更新される
  3. 上のフローでの更新をトリガーに、Aが更新される
    このとき、Bは更新されない
  4. 2つめのフローによって、Bが更新される
  5. 上のフローでの更新をトリガーに、Aが更新される
    このとき、Bは更新されない

ということで、更新処理が自分に帰ってきた場合、更新せずに止まるようになってるっぽい。
Salesforceくんは賢いねぇ。

おまけのおまけ:トリガーも絡ませるとどうなる?

上の状態と合わせて、トリガー側でもオブジェクトAを更新させてみる。

ObjectBTrigger.trigger
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を実行しているため、カウントが増える点に注意
    • よく考えると当たり前ではある・・。
  • 相互で更新させても無限ループにはならないけど、カウントはもりもり増えるから気をつけよう

上に書いたものは、すべてトリガーでイメージしたら当たり前ではあるんですが・・。
それを踏まえて考えると、フローをたくさん用意するのってリスクがかなり大きい気がします。

じゃあもう、やっぱトリガーが良い・・。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?