概要とビジネスシーン
Batch Apex は Salesforce プラットフォームが提供する非同期処理フレームワークで、数百万件にも及ぶ大量のレコードを、Salesforce の厳しい ガバナ制限 (Governor Limits) に抵触することなく効率的に処理することを可能にします。これにより、システムパフォーマンスを維持しつつ、データの一貫性と整合性を確保します。
実際のビジネスシーン
シーンA:金融業界 - 定期的な取引データ集計
- ビジネス課題:ある大手銀行では、毎日数百万件に及ぶ顧客の取引記録を処理し、月末に集計してレポートを作成する必要があります。従来の同期処理ではガバナ制限に達し、タイムアウトや処理失敗が頻発していました。
- ソリューション:Batch Apex を導入し、取引記録を日付や顧客IDごとに小さなチャンクに分割して処理。各チャンク内で集計ロジックを実行し、最終的なレポートデータを生成しました。
- 定量的効果:処理時間は従来の50%削減され、レポート生成の失敗率は0%になりました。これにより、より迅速かつ正確な経営判断が可能となりました。
シーンB:製造業界 - 在庫最適化のための定期データ更新
- ビジネス課題:グローバルに展開する製造業では、世界各地の工場から毎日数万点の製品在庫データが送られてきます。この膨大なデータをSalesforceの製品マスターに定期的に更新し、在庫の最適化とサプライチェーンの効率化を図る必要がありました。しかし、一括更新ではAPIコール数やDML操作のガバナ制限に達していました。
- ソリューション:Batch Apex を活用し、各工場の在庫データを製品カテゴリや地域ごとにバッチ処理。効率的なDML操作とエラーハンドリングを実装しました。また、外部システムとの連携が必要な場合は
Database.AllowsCallouts
インターフェースを実装して対応しました。 - 定量的効果:在庫データの更新処理時間が平均4時間から30分に短縮され、在庫精度が99.5%に向上。これにより、過剰在庫の削減と欠品リスクの最小化に貢献しました。
シーンC:ヘルスケア業界 - 患者エンゲージメント向上施策
- ビジネス課題:大規模な病院グループが、数十万人の患者データに基づいて、パーソナライズされた健康診断のリマインダーや健康増進コンテンツを定期的に送信したいと考えていました。個々の患者に合わせたメッセージ生成と外部連携システム(メール/SMS送信サービス)へのコールアウトが、通常のApexトランザクションでは処理しきれない規模でした。
- ソリューション:Batch Apex を利用して、患者レコードをバッチ処理し、各バッチ内でパーソナライズされたメッセージを作成。その後、
Database.AllowsCallouts
を実装したBatch Apexで外部のメール/SMS送信APIを呼び出し、メッセージを送信しました。 - 定量的効果:以前は手動またはスクリプトで数日かかっていたプロセスが、Batch Apexにより一晩で完了するようになり、患者へのタイムリーな情報提供が可能に。結果として、健康診断受診率が10%向上しました。
技術原理とアーキテクチャ
Batch Apex は、大規模なデータセットを Salesforce のガバナ制限(Governor Limits)に適合する小さなチャンク(バッチ)に自動的に分割し、それぞれを独立したトランザクションとして非同期に処理するメカニズムを提供します。
基礎的な動作メカニズム
Batch Apex は、
Database.Batchable<SObject>インターフェースを実装することで利用できます。このインターフェースは以下の3つのメソッドを定義しています。
start()
:バッチジョブの開始時に一度だけ実行されます。処理対象となるレコードのクエリ (`Database.QueryLocator`) またはイテレータ (`Iterable`) を返します。 execute()
:start()
メソッドが返したレコードセットが小さなチャンク(デフォルト200件、最大2000件)に分割され、各チャンクに対してこのメソッドが実行されます。各execute()
呼び出しは、独自のガバナ制限を持つ独立したトランザクションとして扱われます。finish()
:すべてのexecute()
メソッドが完了した後、一度だけ実行されます。バッチ処理全体の後処理(結果通知メールの送信、ログ記録など)に使用されます。
主要コンポーネントと依存関係
Database.BatchableContext
(bc):各Batch Apexメソッドに渡されるコンテキストオブジェクトで、現在のバッチジョブのIDなどの情報を提供します。Database.QueryLocator
:start()
メソッドでSOQLクエリを指定する際に使用します。最大5000万件のレコードを処理できます。Iterable<SObject>
:SOQLクエリではなく、カスタムのリストやデータソースからレコードを処理したい場合に使用します。Database.Stateful
:Batch Apex クラスがこのインターフェースを実装すると、各execute
呼び出し間でクラス変数の状態が保持されます。カウンターやエラーリストの集計に有用ですが、メモリ消費に注意が必要です。Database.AllowsCallouts
:外部Webサービスへのコールアウトをexecute
メソッド内で実行したい場合に、Batch Apex クラスがこのインターフェースを実装する必要があります。
データフロー
| ステップ | 処理内容 | 関連メソッド/コンポーネント |
|---|---|---|
| 1. ジョブの登録 | 開発者が Database.executeBatch()または System.scheduleBatch()で Batch Apex ジョブをキューに追加。 |
System.scheduleBatch, Database.executeBatch |
| 2. 初期化 | Salesforce が Batch Apex クラスのインスタンスを作成し、start()メソッドを実行。 |
start(), Database.QueryLocatorまたは Iterable<SObject> |
| 3. チャンク処理 | start()から返されたデータが複数のチャンクに分割され、各チャンクに対して execute()メソッドが個別のトランザクションで実行される。 |
execute(), List<SObject> |
| 4. 最終処理 | すべてのチャンクが処理された後、finish()メソッドが一度だけ実行され、後処理を行う。 |
finish() |
ソリューション比較と選定
Salesforce には非同期処理を行うための複数のオプションがあります。Batch Apex は大量データ処理に特化していますが、他のソリューションと比較して適切な選択をする必要があります。
| ソリューション | 適用シーン | パフォーマンス | Governor Limits | 複雑度 |
|---|---|---|---|---|
| Batch Apex | 数万〜数千万レコードにわたる大量データの一括更新、集計、削除など。ガバナ制限緩和が必須の長時間実行処理。 | 高いスケーラビリティ。チャンク分割により並列処理が可能。 | 最も多くのガバナ制限緩和。各チャンクが独立したトランザクション。 | 中〜高。インターフェースの実装と複数メソッドの記述が必要。 |
| Queueable Apex | 単一の非同期処理、複数の非同期処理をチェーンしたい場合、複雑な計算や外部コールアウトが必要な場合。数千〜数万レコード程度の処理。 | 中。非同期処理キューで順次実行。チェーンにより複数の処理を連結可能。 | Batch Apexよりは制限があるが、同期Apexよりは緩和。ヒープサイズ上限が大きい。 | 低〜中。1つのメソッドで完結しやすく、柔軟性が高い。 |
| Future Method | 単一の非同期メソッド呼び出し。主にコールアウトを実行したい場合や、DMLを別トランザクションで行いたい場合。 | 低。コールアウトやDMLの非同期実行をサポート。 | 最も制限が厳しい非同期処理。Batch ApexやQueueable Apexのようなガバナ制限緩和はない。 | 低。シンプルなメソッド定義。 |
Batch Apex を使用すべき場合
- ✅ **数万〜数千万レコード**といった大規模なデータセットを扱う必要がある場合。
- ✅ ガバナ制限、特にSOQLクエリ、DML操作、実行時間の制限に頻繁に達する可能性のある複雑なロジックを実行する場合。
- ✅ 処理の進捗状況を監視したり、処理完了後に後続の処理や通知を行いたい場合。
- ❌ **リアルタイム性が求められる単一のトランザクション**(例:ユーザーのアクションに即座に反応する必要がある場合)には不適用です。その場合は、Queueable ApexやPlatform Eventsなどの他の非同期メカニズムを検討すべきです。
実装例
ここでは、特定の条件を満たす
Accountレコードの
Descriptionフィールドを更新する Batch Apex の完全なコード例を示します。この例は、Salesforce の公式ドキュメントに記載されているパターンに沿っています。
// AccountBatchProcessor.cls - Batch Apex クラス
public class AccountBatchProcessor implements Database.Batchable<SObject>, Database.Stateful {
// 処理されたアカウントの数をカウントするためのクラス変数
// Database.Stateful を実装しているため、execute メソッド間で値が保持されます。
public Integer processedRecords = 0;
// start メソッド: バッチジョブの開始時に一度だけ実行され、処理対象のレコードセットを定義します。
// ここでは、特定の条件を満たす Account レコードを対象とします。
public Database.QueryLocator start(Database.BatchableContext bc) {
// 'Type' フィールドが 'Customer - Direct' の Account を対象とする
return Database.getQueryLocator('SELECT Id, Name, Description, Type FROM Account WHERE Type = \'Customer - Direct\'');
}
// execute メソッド: start メソッドで取得されたレコードがチャンクに分割され、
// 各チャンクに対してこのメソッドが実行されます。各実行は独立したトランザクションです。
public void execute(Database.BatchableContext bc, List<Account> scope) {
List<Account> accountsToUpdate = new List<Account>();
// 各 Account レコードをループ処理
for (Account acc : scope) {
// Description フィールドがまだ設定されていない、または特定の値でない場合に更新
if (acc.Description == null || !acc.Description.contains('Updated by Batch')) {
acc.Description = 'This account was updated by a batch process. Type: ' + acc.Type + ' [' + Datetime.now() + ']';
accountsToUpdate.add(acc);
}
}
// 更新対象の Account が存在する場合に DML 操作を実行
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
// 処理されたレコード数を更新
processedRecords += accountsToUpdate.size();
System.debug('Batch updated ' + accountsToUpdate.size() + ' accounts in this chunk. Total processed: ' + processedRecords);
}
}
// finish メソッド: すべての execute メソッドが完了した後に一度だけ実行されます。
// 後処理(例: 結果通知メールの送信、ログ記録)に使用されます。
public void finish(Database.BatchableContext bc) {
System.debug('Batch Job Id: ' + bc.getJobId() + ' completed. Total accounts processed: ' + processedRecords);
// 管理者への通知メールを送信(例)
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'admin@example.com'}; // 適切な管理者メールアドレスを設定
mail.setToAddresses(toAddresses);
mail.setSubject('Batch Apex Job Completed: Account Update');
mail.setPlainTextBody('The AccountBatchProcessor job with Id ' + bc.getJobId() + ' has completed successfully. Total records processed: ' + processedRecords + '.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
// BatchScheduler.cls - Batch Apex をスケジュール実行するためのクラス
// このクラスは Database.Schedulable インターフェースを実装します。
public class BatchScheduler implements Schedulable {
// execute メソッド: スケジュールされた時間に実行されます。
// ここで Batch Apex ジョブをインスタンス化し、実行キューに投入します。
public void execute(SchedulableContext sc) {
AccountBatchProcessor batchJob = new AccountBatchProcessor();
// チャンクサイズを明示的に指定する場合(例: 200)。省略した場合はデフォルト値(200)が適用されます。
Database.executeBatch(batchJob, 200);
}
}
実装ロジックの解析:
AccountBatchProcessor
クラス:Database.Batchable<SObject>
を実装することで、バッチ処理のフレームワークに従います。Database.Stateful
を実装することで、processedRecords
変数のようなクラスの状態をexecute
メソッド間、ひいては全バッチ処理を通じて保持できます。start()
メソッドで、処理対象となるAccount
レコードをSOQLクエリで指定します。ここでは、Type
が'Customer - Direct'
のアカウントのみを選択しています。execute()
メソッドは、start()
から返されたレコードがチャンクに分割された後に、各チャンクに対して実行されます。scope
引数には、そのチャンク内のAccount
レコードのリストが含まれます。各アカウントのDescription
を更新し、update
文でDML操作を行います。finish()
メソッドは、すべてのチャンク処理が完了した後に実行され、処理されたレコード数の合計をデバッグログに出力し、管理者へメールで通知します。
BatchScheduler
クラス:Schedulable
インターフェースを実装することで、このクラスをSalesforceのスケジュール機能で定期的に実行できるようにします。execute(SchedulableContext sc)
メソッド内で、AccountBatchProcessor
のインスタンスを作成し、Database.executeBatch(batchJob, 200)
を呼び出すことでバッチジョブをキューに投入します。第二引数はチャンクサイズを指定します。- このスケジューラは、匿名実行ウィンドウまたは設定UIから設定できます。例:
System.schedule('Daily Account Batch', '0 0 1 * * ?', new BatchScheduler());(毎日午前1時に実行)
注意事項とベストプラクティス
権限要件
Batch Apex ジョブをデプロイし、実行するためには、以下の権限が必要です。
Author Apex
またはCustomize Application
:Apex クラスを作成・編集するために必要です。API Enabled
:API経由でBatch Apexを呼び出す場合に必要です。- 通常、システム管理者のプロファイルにはこれらの権限が含まれています。限定的なユーザーに実行を許可する場合は、適切な権限セット (Permission Set) を作成し、割り当てることを推奨します。
Governor Limits (2024年版)
Batch Apex はガバナ制限を緩和しますが、完全に回避するわけではありません。以下の主要な制限に注意が必要です。
- 非同期 Apex 呼び出しの合計数:1日あたり最大 250,000 回(Batch Apex、Queueable Apex、Future Method の合計)。
QueryLocator
で取得可能なレコード数:最大 5000 万件。これを超える場合は、より高度なデータ移行ツールやPlatform Cacheなどの利用を検討します。execute()
メソッドごとの制限:各execute
メソッドは独立したトランザクションとして扱われ、以下の主要な制限が適用されます。- SOQL クエリの数:100回
- DML ステートメントの数:150回
- DML 操作で処理できるレコード数:10,000件
- ヒープサイズ:6MB(
start()
およびfinish()
メソッドでは 12MB) - CPU 時間:10,000ミリ秒
- 同時実行バッチジョブ:最大5つの Batch Apex ジョブが同時に実行できます。それ以上はキューに入ります。
エラー処理
execute()
メソッド内でのtry-catch
ブロック:個々のレコード処理で発生するエラーは、try-catch
ブロックで捕捉し、エラーレコードをスキップするか、エラーログに記録するようにします。これにより、1つのレコードのエラーがバッチ全体を失敗させることを防ぎます。Database.Stateful
を利用したエラー情報の集約:Database.Stateful
インターフェースを実装することで、クラス変数にエラー情報を収集し、finish()
メソッドでまとめて通知したり、カスタムオブジェクトに記録したりできます。- Platform Events を利用した非同期エラー通知:大規模なバッチ処理の場合、エラー情報を Platform Events で発行し、サブスクライバー(別のApexトリガーや外部システム)で非同期に処理することで、
finish()
メソッドのガバナ制限を回避し、より柔軟なエラー対応が可能です。
パフォーマンス最適化
- SOQL クエリの選択性の向上:
start()
メソッドのQueryLocator
で使用するSOQLクエリは、必ず 選択性の高い (Selective) フィールドを条件に指定し、インデックスが利用されるようにします。これにより、大規模データセットでのクエリパフォーマンスが劇的に向上します。 - DML 操作のバッチ化:
execute()
メソッド内でレコードを1件ずつ更新するのではなく、更新対象のレコードをリストに集め、update myAccountList;
のように一度にDML操作を実行します。これは バルク処理のベストプラクティス (Bulkification Best Practices) です。 - 適切なチャンクサイズの設定:
Database.executeBatch(batchJob, chunkSize);
のchunkSize
はデフォルトで200ですが、処理内容によっては最適な値が異なります。CPU負荷の高い処理では小さく(例: 50-100)、DML操作が主でシンプルなら大きく(例: 1000-2000)設定することで、オーバーヘッドを減らし、パフォーマンスを向上させることができます。 Database.Stateful
の慎重な使用:Database.Stateful
を実装すると、各チャンク処理後にBatch Apex クラスのインスタンスがシリアライズ・デシリアライズされるため、わずかなオーバーヘッドが発生します。また、クラス変数に大量のデータを保持するとヒープサイズ制限に達する可能性があります。必要な場合のみ利用し、保持するデータは最小限に抑えます。execute
メソッドの軽量化:execute
メソッド内で複雑なSOQLクエリやループを多用せず、可能な限り処理ロジックをシンプルに保つことで、CPU時間制限への抵触リスクを減らします。- Batch Apex は
Database.Batchable
インターフェースを実装し、start()
,execute()
,finish()
の3つのメソッドで処理フローを定義します。 - 数百万件に及ぶレコードを小さなチャンクに分割し、それぞれを独立したトランザクションとして処理することで、ガバナ制限を回避します。
Database.Stateful
やDatabase.AllowsCallouts
などのインターフェースを実装することで、より高度な機能(状態の保持、外部連携)を実現できます。- パフォーマンス最適化には、SOQLクエリの選択性向上、DML操作のバルク化、適切なチャンクサイズ設定が不可欠です。
- ジョブの監視とデバッグには、
Apex Jobs
とデバッグログが重要なツールとなります。 - 📖 公式ドキュメント:Batch Apex Overview
- 📖 公式ドキュメント:Using Batch Apex
- 🎓 Trailhead モジュール:Asynchronous Apex: Batch Apex Basics
よくある質問 FAQ
Q1:Batch Apex で外部 Web サービスへの Callout を呼び出せますか?
A1:はい、可能です。Batch Apex クラスが
Database.AllowsCalloutsインターフェースを実装することで、
execute()メソッド内で Callout を実行できます。ただし、各
execute()呼び出しは独立したトランザクションであるため、各チャンクで Callout のガバナ制限(例:合計100回)が適用される点に注意が必要です。
Q2:Batch Apex のデバッグ方法は?
A2:
Setupの
Debug Logsで、Batch Apex ジョブを実行するユーザーに対してデバッグログを設定します。特に
Apex Codeのレベルを
FINESTに設定すると詳細な情報が記録されます。
System.debug()ステートメントを
start(),
execute(),
finish()各メソッドの重要なポイントに配置し、処理の流れや変数の値を確認します。非同期ログは、ジョブ完了後に
Debug Logs画面で確認できます。
Q3:Batch Apex のパフォーマンスを監視するには?
A3:
Setupから
Apex Jobsページにアクセスし、実行中のBatch Apex ジョブのステータス (
Queued,
Processing,
Completed,
Failed)、処理済みレコード数 (
Number of Errors,
Total Batches,
Batches Processed)、実行時間などを確認できます。また、
Apex Flex Queueページでは、保留中の非同期ジョブの順序と状態を監視できます。パフォーマンスのボトルネックを特定するには、詳細なデバッグログとこれらの監視ツールを組み合わせて利用することが効果的です。
まとめと参考資料
Salesforce Batch Apex は、Salesforce プラットフォーム上で大規模なデータ処理を効率的かつガバナ制限に準拠して実行するための強力なツールです。その柔軟なフレームワークは、金融、製造、ヘルスケアといった多様な業界のビジネス課題を解決し、データの整合性を保ちながらビジネスプロセスを自動化します。適切な設計、実装、および最適化を行うことで、Salesforce の可能性を最大限に引き出すことができます。
公式リソース:
コメント
コメントを投稿