概要とビジネスシーン
Batch Apex は、Salesforce プラットフォーム上で膨大なデータセットに対する複雑なビジネスロジックを非同期で効率的に処理するための強力なフレームワークです。Salesforce の厳しい Governor Limits(ガバナー制限)を回避しつつ、大量のレコードを小さなチャンク(バッチ)に分割して処理することで、一貫性と堅牢性を確保します。
実際のビジネスシーン
シーンA:金融業界 - 月次口座残高集計と手数料計算
ある大手銀行では、数百万件に及ぶ顧客の月次取引データに基づき、口座残高の集計と各種手数料(維持手数料、超過手数料など)の計算を毎月実行する必要があります。
- ビジネス課題:膨大な取引データをリアルタイムで処理することは不可能であり、同期処理ではガバナー制限に容易に抵触します。集計と計算には複雑なロジックが含まれます。
- ソリューション:Batch Apex を利用し、夜間バッチとして各顧客の取引記録を処理し、月次残高と手数料を計算して関連オブジェクトに集計します。
- 定量的効果:処理時間を従来の数日から数時間(約70%削減)に短縮し、計算の正確性を保証。顧客へのタイムリーな通知と請求が可能になりました。
シーンB:小売/Eコマース業界 - 顧客セグメンテーションに基づくマーケティングデータ更新
オンラインストアを運営する企業は、顧客の購買履歴、閲覧行動、CRMデータなどに基づいて、顧客を複数のセグメントに分類し、パーソナライズされたマーケティングキャンペーンを実施しています。
- ビジネス課題:顧客の行動パターンは常に変化するため、定期的に顧客セグメントを再評価し、CRM上の関連フィールドを更新する必要があります。手動での更新は非効率的で、一括更新ではガバナー制限の壁に直面します。
- ソリューション:Batch Apex を用いて週次で顧客データをスキャンし、最新の購買行動やインタラクションに基づき、顧客オブジェクトの「顧客セグメント」フィールドを自動更新します。
- 定量的効果:顧客セグメント更新の自動化により、マーケティングチームの作業負荷を月あたり約20時間削減。ターゲットキャンペーンの精度が向上し、コンバージョン率が平均15%増加しました。
シーンC:製造業 - IoTデバイスからのデータ集計と資産管理
複数の工場を持つ製造業者は、数千台のIoTデバイスから機器の状態データ(温度、稼働時間、エラーコードなど)を継続的に収集し、Salesforce 上の資産(Asset)オブジェクトに関連付けられたカスタムオブジェクトに保存しています。
- ビジネス課題:毎日数十万件発生するデバイスデータを取り込み、関連する資産オブジェクトに集計データ(平均温度、総稼働時間など)をロールアップし、異常値を検知する必要があります。
- ソリューション:Batch Apex を使用して、日次でデバイスデータを処理し、各資産に紐づく集計フィールドを更新します。異常値が検出された場合には、自動的にケースを作成するロジックも組み込みます。
- 定量的効果:データ処理の自動化により、エンジニアリングチームはデータ集計作業から解放され、より価値の高いデータ分析に集中できるようになりました。機器の予知保全が可能となり、予期せぬダウンタイムを年間10%削減しました。
技術原理とアーキテクチャ
Batch Apex は、大規模なデータセットを処理するために、Salesforce の非同期処理キュー上で動作します。Database.Batchable<SObject> インターフェースを実装することで、データ処理を複数の小さなトランザクションに分割し、それぞれのトランザクションが独自の Governor Limits 内で実行されるように設計されています。これにより、一度に多くのレコードを処理する際の制限を回避できます。
主要コンポーネントと依存関係
Batch Apex クラスは、以下の3つの必須メソッドを実装します。
start(Database.BatchableContext bc):バッチジョブの開始時に一度だけ実行されます。処理対象となるレコードの範囲を定義し、通常はDatabase.QueryLocatorを返します。これにより、SOQL クエリで最大5,000万件のレコードを処理できます。またはIterable<SObject>を返すこともできます。execute(Database.BatchableContext bc, List<SObject> scope):startメソッドで定義されたデータが、指定されたバッチサイズ(デフォルト200レコード)のリストに分割され、各バッチに対してこのメソッドが呼び出されます。ここで実際のビジネスロジックが実行されます。各呼び出しは独立したトランザクションとして扱われます。finish(Database.BatchableContext bc):すべてのバッチが処理された後に一度だけ実行されます。後処理(メール通知、ログ記録、次のバッチジョブのキューイングなど)に使用されます。
オプションで、バッチ処理中に状態を維持したい場合は Database.Stateful インターフェースを実装します。外部システムへのコールアウトが必要な場合は Database.AllowsCallouts インターフェースを実装します。
データフロー
| ステップ | 説明 | 実行されるメソッド/イベント |
|---|---|---|
| 1. ジョブの開始 | ユーザーまたはスケジュールされたApexがBatch Apexジョブをキューに登録し、処理を開始します。 | Database.executeBatch(MyBatchApexClass) または System.scheduleBatch(...) |
| 2. データ範囲の定義 | 処理対象となる全レコードを特定します。大規模なデータセットでも効率的に扱えるよう最適化されます。 | start() メソッドが実行され、Database.QueryLocator または Iterable<SObject> を返します。 |
| 3. データ分割と処理 | 定義されたデータセットが、指定されたバッチサイズ(デフォルト200レコード)に分割され、Salesforce の非同期キューに個別のジョブとして投入されます。 | execute() メソッドが各バッチに対して呼び出されます。各 execute() の呼び出しは独立したトランザクションです。 |
| 4. 最終処理 | すべてのバッチが正常に処理された後、後処理を行います。 | finish() メソッドが実行されます。エラー通知や集計レポートの生成などが行われます。 |
ソリューション比較と選定
Salesforce には非同期処理のための複数のオプションがあります。Batch Apex が常に最善の選択肢とは限りません。他のソリューションと比較して、その特性を理解することが重要です。
| ソリューション | 適用シーン | パフォーマンス | Governor Limits | 複雑度 |
|---|---|---|---|---|
| Batch Apex | 数千〜数千万件の大規模データセットに対するDML、複雑な計算、定期的なデータメンテナンス | レコード量に応じて処理時間が変動するが、非常に大規模なデータセットにも対応 | executeメソッドごとに新たな制限セットが適用され、大規模処理の制限を回避 |
中:Database.Batchable インターフェースの実装と複数メソッドの管理 |
| Future Method | 単一の非同期操作、コールアウトの実行、異なるコンテキストでの処理(例:トリガーからのコールアウト) | シンプルで高速(ただし保証なし)、コールアウトに特に適している | CPU時間、SOQLクエリ数など、1トランザクションの制限に準拠。ジョブ数制限あり。 | 低:@future アノテーションをつけるだけ |
| Queueable Apex | Future Method の進化版、ジョブチェイニング、SObject をパラメータとして渡す必要がある場合、より複雑な非同期フロー | Future Method と同様だが、より制御性が高い | Future Method と同様だが、System.enqueueJob ごとに新たな制限セット。ジョブ数制限あり。 |
中:Queueable インターフェースの実装、execute メソッドの定義 |
| Scheduled Apex | 定期的な処理(毎週月曜日の朝、毎晩など)、Batch Apex や Queueable Apex と組み合わせて使用 | 指定された時間に定期的に実行 | 実行されるApexタイプ(Batch, Queueable など)の制限に準拠 | 低〜中:Schedulable インターフェースの実装、Cron 式の理解 |
Batch Apex を使用すべき場合:
- ✅ 数千件を超える大量のレコードに対して、データ更新(DML)や複雑な計算ロジックを適用する必要がある場合。
- ✅ Salesforce の Governor Limits、特にSOQLクエリのレコード数やDMLステートメントの制限に抵触する可能性が高い処理。
- ✅ 定期的なデータクレンジング、データ移行、レポートデータの集計など、バッチ処理に適したユースケース。
- ✅ 処理の途中でエラーが発生した場合でも、残りのバッチは処理を続行し、エラーをロギングする堅牢性が必要な場合。
- ❌ 単一のレコードに対するシンプルな非同期処理や、わずかなレコード数に対するコールアウトが主な目的の場合(Future/Queueable Apex が適しています)。
- ❌ リアルタイム性が厳しく求められる処理(Batch Apexは非同期キューに依存するため、実行開始が保証されません)。
実装例
ここでは、特定の条件を満たす Account レコードを一括で更新する Batch Apex の完全なコード例を示します。この例では、過去90日以内に活動がなく、かつ「Status」が「Active」であるアカウントの「Status」を「Inactive」に更新し、同時にカスタムフィールド Last_Activity_Date__c を更新します。
public class AccountDeactivationBatch implements Database.Batchable<SObject>, Database.Stateful, Database.AllowsCallouts {
// 処理されたアカウントの数をカウントするための変数(Database.Stateful を利用)
public Integer recordsProcessed = 0;
/**
* start メソッド: バッチ処理の対象となるレコードを定義します。
* ここでは、最終活動日が90日以上前で、かつステータスが'Active'のアカウントを対象とします。
*
* @param bc Database.BatchableContext オブジェクト
* @return 処理対象となるレコードの Database.QueryLocator
*/
public Database.QueryLocator start(Database.BatchableContext bc) {
// 90日前の日付を計算
Date ninetyDaysAgo = System.today().addDays(-90);
// クエリロケーターを返します。
// WHERE句で絞り込むことで、不要なレコードの取得を避けます。
return Database.getQueryLocator(
'SELECT Id, Name, Status__c, Last_Activity_Date__c ' +
'FROM Account ' +
'WHERE Status__c = \'Active\' AND Last_Activity_Date__c < :ninetyDaysAgo'
);
}
/**
* execute メソッド: 各バッチ(チャンク)に対してビジネスロジックを実行します。
* ここでは、アカウントのステータスを'Inactive'に更新し、処理済みカウントを増やします。
*
* @param bc Database.BatchableContext オブジェクト
* @param scope 現在のバッチに含まれる SObject のリスト
*/
public void execute(Database.BatchableContext bc, List<Account> scope) {
List<Account> accountsToUpdate = new List<Account>();
for (Account acc : scope) {
// アカウントのステータスを'Inactive'に設定
acc.Status__c = 'Inactive';
// 最終活動日を今日のままにする、あるいは特定の日付に更新する(今回は現状維持またはToday)
// acc.Last_Activity_Date__c = System.today(); // 必要に応じて
accountsToUpdate.add(acc);
recordsProcessed++; // 処理されたレコード数をインクリメント
}
if (!accountsToUpdate.isEmpty()) {
try {
// 更新対象のアカウントリストを一括でDML操作
update accountsToUpdate;
System.debug('Successfully updated ' + accountsToUpdate.size() + ' accounts in this batch.');
} catch (DmlException e) {
System.debug('DML Exception for batch ' + bc.getJobId() + ': ' + e.getMessage());
// エラー処理ロジック(例:エラーログカスタムオブジェクトへの記録、通知)
}
}
}
/**
* finish メソッド: すべてのバッチが処理された後に一度だけ実行されます。
* 最終的な処理(結果の通知、次のバッチの起動など)を行います。
*
* @param bc Database.BatchableContext オブジェクト
*/
public void finish(Database.BatchableContext bc) {
System.debug('Batch Apex job ' + bc.getJobId() + ' has finished.');
System.debug('Total records processed: ' + recordsProcessed);
// 管理者へのメール通知
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'admin@example.com'}; // 管理者のメールアドレス
mail.setToAddresses(toAddresses);
mail.setSubject('Account Deactivation Batch Job Finished');
mail.setPlainTextBody('The Account Deactivation Batch Apex job (' + bc.getJobId() + ') has completed.\n' +
'Total records processed: ' + recordsProcessed);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
// 必要に応じて、別のバッチジョブやQueueableジョブをキューに追加することもできます。
// System.enqueueJob(new AnotherQueueableJob());
}
}
Batch Apex の実行方法:
開発者コンソール(Developer Console)の匿名実行ウィンドウ(Anonymous Apex Window)から、以下のコードを実行します。
// バッチジョブのインスタンスを作成 AccountDeactivationBatch myBatch = new AccountDeactivationBatch(); // バッチジョブを実行。第2引数はバッチサイズ(1〜2000)。デフォルトは200。 // ここでは50レコードずつ処理するように設定。 Database.executeBatch(myBatch, 50);
実行後、「設定」→「非同期 Apex ジョブ」または開発者コンソールの「Monitor」→「Apex Jobs」からジョブのステータスを監視できます。
注意事項とベストプラクティス
権限要件
- Batch Apex クラスを実行するユーザープロファイルまたは権限セットには、当該 Apex クラスへの「Apex クラスのアクセス」権限が必要です。
Database.executeBatch()を呼び出すユーザーには、上記の権限が必要です。- Batch Apex がアクセスするオブジェクトやフィールドに対しても、適切な参照(Read)および更新(Edit)権限が必要です。
Governor Limits
Batch Apex は Governor Limits を回避するための強力なツールですが、それでも特定の制限を受けます。
- 非同期 Apex 呼び出しの制限:各組織は1日あたり最大 250,000 回の非同期 Apex メソッド(
Database.executeBatch、System.enqueueJob、System.schedule、@futureメソッドの合計)を実行できます(2025年時点)。 - クエリ結果の最大数:
startメソッドでDatabase.QueryLocatorを使用する場合、最大 5,000万件のレコードを処理できます。Iterableを使用する場合は、メモリ制限に注意が必要です。 executeメソッド内の制限:executeメソッドは独立したトランザクションとして実行されるため、その内部でのSOQLクエリ数(100)、DMLステートメント数(150)、CPU時間(10,000 ms)などの制限は、通常の同期 Apex と同じです。- バッチサイズ:
Database.executeBatch()の第2引数で指定するバッチサイズは、デフォルトで200レコードです。通常は50〜200の間で調整するのが最適とされています。あまりに大きなバッチサイズはガバナー制限(特にCPU時間)に抵触しやすくなります。
エラー処理
executeメソッド内の例外処理:各executeメソッドの実行は独立したトランザクションであるため、バッチ処理中のエラーが他のバッチに影響を与えることはありません。try-catchブロックを使用して、特定のレコード処理中のエラーを捕捉し、ログに記録したり、失敗したレコードを別のカスタムオブジェクトに保存したりするメカニズムを実装することが重要です。finishメソッドでの通知:finishメソッドで、処理の概要(処理レコード数、エラー数など)を管理者へメールで通知することで、問題発生時に迅速に対応できます。- Apex ジョブの監視:Salesforce の「設定」→「非同期 Apex ジョブ」で、すべての Batch Apex ジョブのステータス(完了、失敗、処理中など)を監視できます。失敗したジョブの詳細ログを確認することも可能です。
パフォーマンス最適化
- SOQL クエリの最適化:
startメソッドの SOQL クエリは、必要なフィールドのみを選択し、インデックスが適用されたフィールドでフィルタリングすることで、パフォーマンスを向上させます。複雑な結合やサブクエリは避け、シンプルで効率的なクエリを心がけましょう。 - DML 操作のバルク化:
executeメソッド内で個々のレコードに対して DML(insert,update,delete)操作を実行するのではなく、処理対象のレコードをリストに収集し、一度に DML 操作を実行することで、DML ステートメントのガバナー制限を遵守し、パフォーマンスを向上させます。 - ループ内での SOQL/DML の回避:
forループ内で SOQL クエリや DML 操作を実行すると、ガバナー制限に抵触する可能性が非常に高くなります。必ずループの外でこれらの操作をバルクで行うように設計してください。 - バッチサイズの調整:データ量、レコードの複雑さ、実行するロジックに応じて、バッチサイズを最適化します。デフォルトの200が推奨されますが、CPU時間が厳しい場合は小さくし、DML数やSOQLが厳しい場合は大きくすることも検討します(ただし、メモリ制限に注意)。
よくある質問 FAQ
Q1:Batch Apex でコールアウト(外部システム連携)を呼び出せますか?
A1:はい、可能です。Batch Apex クラスに Database.AllowsCallouts インターフェースを実装することで、execute メソッド内で外部システムへのコールアウトを実行できます。ただし、コールアウトにもタイムアウトなどの特定のガバナー制限があるため、大量のコールアウトを行う場合は注意が必要です。
Q2:Batch Apex のデバッグはどのように行いますか?
A2:開発者コンソールの「Debug Logs」で監視するのが最も一般的です。ジョブID(bc.getJobId()で取得可能)でログをフィルタリングすると、特定のバッチの実行状況を追跡しやすくなります。また、finish メソッドで処理結果やエラー情報をメールで通知する仕組みを組み込むことで、非同期実行後のデバッグや監視を容易にできます。
Q3:Batch Apex の実行状況やパフォーマンスを監視するにはどうすればよいですか?
A3:Salesforce の「設定」メニューから「非同期 Apex ジョブ」にアクセスすると、キューに登録された、実行中、完了、失敗したすべての非同期ジョブ(Batch Apex を含む)のリストとステータスを確認できます。また、開発者コンソールの「Monitor」→「Apex Jobs」からも、各バッチの開始時間、完了時間、ステータス、処理されたバッチ数などの詳細な実行指標を追跡できます。
まとめと参考資料
Batch Apex は、Salesforce プラットフォーム上で大規模なデータセットを扱う開発者にとって不可欠な非同期処理フレームワークです。Governor Limits をインテリジェントに回避しながら、複雑なビジネスロジックを効率的かつ堅牢に実行する能力は、多くのエンタープライズソリューションの基盤となります。適切な設計とベストプラクティスに従うことで、その真価を最大限に引き出すことができます。
- 大規模データ処理:数千から数千万件のレコードを安定して処理できる。
- ガバナー制限の回避:処理を小分けにすることで、各トランザクションが独自の制限内で実行される。
- 非同期実行:ユーザー体験を損なうことなく、バックグラウンドで重い処理を実行。
- 堅牢なエラー処理:個々のバッチのエラーが全体に影響を与えず、ロギングと通知で問題を把握できる。
公式リソース:
- 📖 公式ドキュメント:Batch Apex
- 📖 公式ドキュメント:Database.Batchable インターフェース
- 🎓 Trailhead モジュール:Asynchronous Apex
- 🎓 Trailhead モジュール:Apex Basics & Database: Asynchronous Apex
コメント
コメントを投稿