目次
1.デバッグ方法
※ビューステートに関しては、デバッグ方法が少し特殊で、
分量が多いので別記事にしています。
SOQL(発行数・レコード数)、DML(発行数・レコード数)
デバッグログ(LIMIT_USAGE)(2022/03/28追記)
デバッグログをLIMIT_USAGE
という単語で検索すると、各ガバナ消費数が見れます。
※デバッグレベルApexプロファイリング
がINFO
以上の必要があります。(2022/04/02 追記)
開発者コンソール(Limitsタブ)
- Debugをクリック
- View Log Panelsをクリック
- Execution Overviewにチェック
- 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タブ)
- Debugをクリック
- View Log Panelsをクリック
- Execution Overviewにチェック
- 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を検討する
- カスタム設定を使う
- バッチ・ 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の設計にはいくつか注意点があるので、どこかで記事にするつもりです。
読んでいただきありがとうございました。