ガバナ制限の種類と「やりがちな事故パターン」を、現場目線でまとめた

※この記事にはアフィリエイトリンクを含みます。

はじめに:ガバナ制限は“バグ”じゃなくて“設計ミス”で刺さる

Salesforce開発でつらい瞬間の代表がこれです。

  • UT/STで突然 System.LimitException
  • 本番データ量でだけ再現(テストでは動いてたのに…)
  • 直すときは「処理の流れ」から見直しになりがち

で、問題はここ。

ガバナ制限は「ルール違反」というより、資源(検索・更新・実行時間)を食い過ぎる設計が原因で刺さります。

この記事は、ガバナ制限の種類と、現場で本当に起きがちな事故パターンを、セットで整理します。

※上限値(何回まで等)は実行条件(同期/非同期など)や環境で変わるので、この記事では「どれが危ないか」に寄せます。

まず押さえる:代表的なガバナ制限の種類(現場で刺さる順)

1) SOQL(検索)

症状

  • Too many SOQL queries
  • Too many query rows

何が起きてるか(ざっくり)

  • 検索の回数が多い、または一度に拾いすぎてる

2) DML(登録・更新・削除)

症状

  • Too many DML statements
  • Too many DML rows

何が起きてるか

  • 更新の回数が多い、またはまとめずに更新している

3) CPU時間(Apex実行時間)

症状

  • Apex CPU time limit exceeded

何が起きてるか

  • ループが重い、無駄が多い、同期で大量データを処理している

4) ヒープ(メモリ)

症状

  • Apex heap size too large

何が起きてるか

  • 大量のレコードや文字列をメモリに抱えすぎ(JSON巨大、画像っぽいデータ等)

5) コールアウト(外部通信)

症状

  • Too many callouts
  • Callout from triggers are currently not supported(条件による)

何が起きてるか

  • 外部API呼び出しが多い/呼ぶ場所が悪い(同期・トリガ内など)

事故パターン:現場で“やりがち”なやつ(ここが本題)

パターンA:ループの中でSOQL / DML(王道の事故)

よくあるコード(悪い例)

for (Account a : Trigger.new) {
Account src = [SELECT Id, Industry FROM Account WHERE Id = :a.Id]; // ループ内SOQL
src.Name = src.Name + '!';
update src; // ループ内DML
}

何が問題か

  • データが増えるほど回数が増えて、上限に一直線

直し方(型)

  • まとめて取る(SOQLは1回)
  • まとめて更新する(DMLは1回)
Set<Id> ids = new Set<Id>();
for (Account a : Trigger.new) ids.add(a.Id);

Map<Id, Account> m = new Map<Id, Account>(
[SELECT Id, Name, Industry FROM Account WHERE Id IN :ids]
);

List<Account> upd = new List<Account>();
for (Account a : Trigger.new) {
Account src = m.get(a.Id);
src.Name = src.Name + '!';
upd.add(src);
}
if (!upd.isEmpty()) update upd;

パターンB:トリガ/フローの“連鎖”で雪だるま(原因が見えにくい)

現場あるある

  • 画面操作は1回なのに、裏で トリガ → フロー → ワークフロー相当 → さらに更新… みたいに連鎖して、SOQL/DML/CPUが積み上がる

兆候

  • 「自分のコードは一括処理にしたのに、まだ刺さる」
  • あるレコード種類だけ再現する

対策(型)

  • 連鎖の入り口を特定して、不要な更新を減らす
  • 「値が変わってないのにupdate」をやめる(差分更新)
  • 必要なら処理を 非同期(後で実行) に逃がす(※やりすぎ注意)

パターンC:テストデータが少なすぎて、STで爆発

現場あるある

  • UTは20件で通る
  • STは2万件で死ぬ

原因

  • “データ量が増えた時の設計”になっていない
  • 例えば、一覧更新・一括処理・移行データ投入などで発火して刺さる

対策

  • テストでも「多めの件数」を作って一度は回す
  • 「1件ずつ処理していい」前提を捨てる(最初から一括処理で書く)

パターンD:CPU時間だけが異常に高い(回数じゃなく“重さ”で死ぬ)

原因の型

  • 二重ループ(入れ子)で件数×件数の計算をしている
  • 同じ計算を毎回やってる(Mapにキャッシュしてない)
  • 文字列結合をループでやってる(ログ生成など)

対策(チェック)

  • 「入れ子ループ」になってないか
  • 検索結果をMap化して、getで引く形になっているか
  • 不要な処理(ログ、変換、整形)を削れるか

パターンE:ヒープ(メモリ)で落ちる(地味に詰む)

原因の型

  • 大量レコードをListに抱えたまま、さらに加工して増殖
  • JSONを巨大文字列で組み立てる
  • 添付っぽいデータをApex側で持ちすぎ

対策

  • まとめて持たない(分割、ストリーム的に扱う)
  • 必要な項目だけ取る
  • 文字列の巨大化を避ける

“事故らない設計”の共通ルール(これだけ守ればだいぶ勝てる)

  1. ループ内に危険物(SOQL/DML/外部通信)を置かない
  2. **一括処理(まとめて取る・まとめて更新する)**を基本にする
  3. 差分更新(変わってないのにupdateしない)
  4. 連鎖があるなら、入り口(どこで発火してるか)を特定する
  5. 大量データが想定される処理は、同期に詰め込まない(必要なら非同期)

すぐ使える:セルフチェック(実装レビュー用)

  • ループ内SOQL/DMLがない
  • Map化して get で引いている(入れ子ループで探してない)
  • 更新は差分だけ(無駄updateなし)
  • 大量データ時の件数増加を想像できている
  • 連鎖(トリガ/フロー)がある前提で見ている

まとめ:ガバナ制限は「型」で潰す

ガバナ制限は、数字を覚えるより「事故パターン」を知って回避するのが早いです。

特に多いのは ループ内SOQL/DML連鎖による雪だるま

ここを潰すだけで、UT/STの悲惨さはかなり減ります。

問い(ここで一回立ち止まる)

あなたの現場で一番多いのは、SOQL / DML / CPU のどれで刺さるケースですか?

次に読む
ガバナ制限に当たったときの『原因特定の手順』だけまとめました(デバッグログ/Limitの見方)。
ガバナ制限に当たったときの調べ方(デバッグログ/Limitの見方) →
おすすめの記事