
※この記事にはアフィリエイトリンクを含みます。
Contents
はじめに:ガバナ制限は“バグ”じゃなくて“設計ミス”で刺さる
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側で持ちすぎ
対策
- まとめて持たない(分割、ストリーム的に扱う)
- 必要な項目だけ取る
- 文字列の巨大化を避ける
“事故らない設計”の共通ルール(これだけ守ればだいぶ勝てる)
- ループ内に危険物(SOQL/DML/外部通信)を置かない
- **一括処理(まとめて取る・まとめて更新する)**を基本にする
- 差分更新(変わってないのにupdateしない)
- 連鎖があるなら、入り口(どこで発火してるか)を特定する
- 大量データが想定される処理は、同期に詰め込まない(必要なら非同期)
すぐ使える:セルフチェック(実装レビュー用)
- ループ内SOQL/DMLがない
- Map化して get で引いている(入れ子ループで探してない)
- 更新は差分だけ(無駄updateなし)
- 大量データ時の件数増加を想像できている
- 連鎖(トリガ/フロー)がある前提で見ている
まとめ:ガバナ制限は「型」で潰す
ガバナ制限は、数字を覚えるより「事故パターン」を知って回避するのが早いです。
特に多いのは ループ内SOQL/DML と 連鎖による雪だるま。
ここを潰すだけで、UT/STの悲惨さはかなり減ります。
問い(ここで一回立ち止まる)
あなたの現場で一番多いのは、SOQL / DML / CPU のどれで刺さるケースですか?
.png)
-300x169.png)
