ガバナ制限は怖くない(デバッグ方法と減らし方)


目次

1.デバッグ方法

※ビューステートに関しては、デバッグ方法が少し特殊で、
分量が多いので別記事にしています。

SOQL(発行数・レコード数)、DML(発行数・レコード数)

デバッグログ(LIMIT_USAGE)(2022/03/28追記)

デバッグログをLIMIT_USAGEという単語で検索すると、各ガバナ消費数が見れます。

※デバッグレベルApexプロファイリングINFO以上の必要があります。(2022/04/02 追記)

開発者コンソール(Limitsタブ)

  1. Debugをクリック
  2. View Log Panelsをクリック
  3. Execution Overviewにチェック
  4. Limitsタブをクリック

※Limitsタブでガバナ消費を表示するには、デバッグレベルをデフォルトの設定より高くする必要があります。

【ガバナ消費が見れるデバッグレベルの設定例】

項目 デバッグレベル
データベース FINEST
ワークフロー FINER
入力規則 INFO
コールアウト FINER
Apexコード FINEST
Apexプロファイリング FINEST
Visualforce INFO
システム DEBUG
Wave FINER
NextBestAction INFO

Limitsクラスでのデバッグ文出力

デバッグだけじゃなく、ガバナ消費状況に合わせてSOQL・DMLを発行するのに使ったりもできます。

例)

// SOQLの発行数/SOQLの発行数上限
System.debug(String.valueOf(Limits.getQueries()) + '/' + String.valueOf(Limits.getLimitQueries()));

CPU時間(2022/03/28追記)

開発者コンソール(Timelineタブ)

  1. Debugをクリック
  2. View Log Panelsをクリック
  3. Execution Overviewにチェック
  4. Timelineタブをクリック
  • CPU時間に含まれるもの
    • APEX_CODE
    • WORKFLOW
    • VISUALFORCE
  • CPU時間に含まれないもの
    • DB
  • CPU時間に含まれるか分からないもの(分かる方がいたら教えていただけると嬉しいです)
    • VALIDATION
    • CALLOUT
    • CALLOUT

デバッグログ(CUMULATIVE_LIMIT_USAGE)

こちらは公式のヘルプに記載されていたのですが、
私の環境だと出力されずでした。

ヒープサイズ(2022/03/28追記)

Limitsクラスでのデバッグ文出力

2.ガバナ消費の減らし方

SOQL発行数

  • ループ内での発行を止める
    例)IN句で一括検索する
List<Account> acctMap = [SELECT Id FROM Account WHERE Id IN :acctIdList];
  • JavaScript Remotingを検討する
  • カスタム設定を使う
    • カスタム設定はSOQLを使わないでセレクトできる簡易オブジェクトみたいなもの
    • ただ動的具合は低いので、複雑度が低めな処理のときに使う印象
    • こちらの記事がカスタム設定の可能性を模索していて勉強になりました
  • バッチ・ future メソッド化を検討する

SOQLレコード数

  • 余分なレコードを取っているときはwhereやlimit句で絞り込む
  • SOQLを分割する
  • VisualforceページのreadOnly属性をtrueにする
    • ※そのページでの更新処理はできなくなる
  • ApexメソッドにReadOnlyアノテーションをつける
    • @RemoteActionアノテーションをつけるか、Webservice(外部サービスへの公開用キーワード)をつける必要がある
    • @RemoteActionアノテーションの使い方についてはこちらの記事が分かりやすかったです
  • バッチ化を検討する
    • 非同期で制限は緩和されないが、バッチだとスコープ・ジョブ・スケジュール単位でトランザクションを分割しやすい

DML発行数

  • ループ内での発行を止める
    例)コレクションに対してDMLを発行する
insert acctList;
  • @RemoteActionアノテーションをつけてトランザクションを分割する
  • バッチ化を検討する
    • 非同期で制限は緩和されないが、バッチだとスコープ・ジョブ・スケジュール単位でトランザクションを分割しやすい

DMLレコード数

  • DML処理が不要なレコードが処理に混じっているときは排除する
  • DMLを分割する
  • バッチ化を検討する
    • 非同期で制限は緩和されないが、バッチだとスコープ・ジョブ・スケジュール単位でトランザクションを分割しやすい

CPU時間

  • バッチ・ future メソッド化を検討する
  • マップベースクエリを検討する
  • 2層以上のループ内でListの突き合わせをしている場合は、マップを使ってループの階層を浅くする

例)

List<Account> acctList = [select Name from Account];
List<Contact> conList = [select AccountId from Contact];

// これを作りたい
Map<String, Contact> acctNameConObjMap = new Map<String, Contact>();

for(Account acct : acctList){
    for(Contact con : conList){
        if(acct.Id == con.AccountId){
            acctNameConObjMap.put(acct.Name, con);
        }
    }
}

// ↓マップを使うと、、、

Map<Id, Account> acctMap = new Map<Id, Account>(acctList);
Map<Id, Contact> acctIdconObjMap = new Map<Id, Contact>();
for (Contact con : conList) {
    acctIdconObjMap.put(con.AccountId, con);
}

for(Id acctId : acctMap.keySet()){
    if(acctIdconObjMap.containsKey(acctId)) acctNameConObjMap.put(acctMap.get(acctId).Name, acctIdconObjMap.get(acctId));
}

処理の内容に意味はないので頭に入りづらいかもしれませんが、
仮にacctListが10個・conListが10個だった場合、ループ内の処理が動く回数は以下のようになります。

  • Listの突き合わせ:100回
  • Map:20回

補足

CPUガバナには、DB処理時間は含まれません。

※一応アプリケーションサーバ上でのDB処理時間はガバナを食うんですが、
メインの時間はDBサーバ上の処理が占めてるので、結果DB処理は気にしなくていいという感じです。

ヒープサイズ

  • sObjectのセレクト項目数を減らす
  • SOQL For ループを使う
  • 使わない変数のメモリは解放する(nullを代入する)
  • バッチ・ future メソッド化を検討する

ビューステート

分量が多いのでデバッグ方法・減らし方ともに別記事にしています。

3.バッチについて

バッチはスケジューリングしかできないわけじゃない

どうしても画面から容量大きめな処理をしたい!ってときは、画面からバッチを動かすこともできます。

例)

// 画面から呼ぶ関数
public void btnAction(){
    Batch_Class batchClass = new Batch_Class();
    Database.executeBatch(batchClass, BATCH_SIZE);
}

バッチが動いてる間はボタンを非活性にして、
「実行中…」みたいなテキストを出してあげると親切ですね。

バッチの実行状況はApexジョブオブジェクトから取れるので、
それを使うと実行状況を把握した処理が作れます。

バッチでも落ちる場合

バッチ(非同期)も万能なわけではありません。
数万〜数千万レコードあるオブジェクトを扱うと落ちるときもあります。

そんなときは以下を検討してみると良いです。

  • スコープを減らす
    • スコープを減らすとトランザクションが分割され、ガバナ消費がリセットされます
  • ジョブを分割する
    • 同じスケジュールクラス内でもジョブを分割できます(最大100ジョブまで)
  • バッチを分割する

まとめ

個人的にはガバナ制限は嫌いじゃないです。
適切な設計に導かれるので。

ガバナに引っかかるときは、以下のように設計かコードに改善点があることが多い気がします。

  • モジュールの責務が大きすぎる
  • 冗長な処理の書き方をしている

他のプラットフォームだと無茶して実装することができるのですが、
ガバナ制限があると改善せざるを得ないので、ある程度ソフトウェアの品質向上につながるな〜と思っています。

ガバナ制限以外にもApexの設計にはいくつか注意点があるので、どこかで記事にするつもりです。

読んでいただきありがとうございました。

参考文献