via pixabay.com
はじめに
Salesforceで大量データをする時バッチ処理で実行します。定期的に行う処理の場合はスケジュールでバッチ起動を設定してバッチ処理をスケジュール起動する際、Salesforceの標準画面からの設定では「最短でも1時間に1回」「24時間常に稼働」という指定しかできません。しかし実務上、以下のように標準機能では要件を満たせないケースが多々発生します。
・リードの自動割り当て: 営業時間内(9:00〜18:00)のみ、15分間隔で担当者ルーティングを行いたい。
・外部システム連携: 当日出荷の締め切りに間に合わせるため、日中は10分間隔で受注データを基幹システムに連携したい。
・仮予約の自動キャンセル: 支払い待ちの枠を解放するため、24時間・15分おきに期限切れチェックを行いたい。
・リードの自動割り当て: 営業時間内(9:00〜18:00)のみ、15分間隔で担当者ルーティングを行いたい。
・外部システム連携: 当日出荷の締め切りに間に合わせるため、日中は10分間隔で受注データを基幹システムに連携したい。
・仮予約の自動キャンセル: 支払い待ちの枠を解放するため、24時間・15分おきに期限切れチェックを行いたい。
本記事では、このような「1時間に1回より短い間隔」や「時間帯の範囲指定」が求められる要件に対し、どのようにスケジュールを設定・実装すべきか、その設計パターンと注意事項をまとめました。
入門編:Apexスケジューラーの基本
バッチを定期実行するには、メインの処理を記述したバッチクラス(Database.Batchable)とは別に、スケジュールクラス(Schedulable)を作成し、その execute メソッドの中でバッチを呼び出すのが基本です。スケジュールは設定画面の「Apex クラスをスケジュール」から登録します。
Apex クラスをスケジュール
スケジュールの登録方法には、主に次の2通りがあります。
1.スケジュールビルダーを使用して登録
「Apex をスケジュール」画面の「次を使用してスケジュール」で「スケジュールビルダー」を選択します。「Apex の実行をスケジュール」が表示されるため、頻度・開始~終了期間・希望開始時刻を設定し保存ボタンを押下します。
スケジュールビルダーでは感覚的にApexスケジュールを登録できる反面、頻度は最短でも1日に1回となり、時間設定も1時間刻みでしか設定することができません。
スケジュールビルダーでは感覚的にApexスケジュールを登録できる反面、頻度は最短でも1日に1回となり、時間設定も1時間刻みでしか設定することができません。
スケジュールビルダー設定画面
2.Cron式を使用して登録
「Apex をスケジュール」画面の「次を使用してスケジュール」で「Cron 式」を選択します。「Cron 式」が表示されるため、以下の基本フォーマットに従ってCron式を設定し保存ボタンを押下します。
Cron式では柔軟な時間・間隔の指定が可能で、1日に複数回実行する設定にすることも可能です。
【Cron式の基本フォーマット】
秒 分 時 日 月 曜日 (年)
例:0 15 10 * * ? (毎日10時15分に実行)
Cron式では柔軟な時間・間隔の指定が可能で、1日に複数回実行する設定にすることも可能です。
【Cron式の基本フォーマット】
秒 分 時 日 月 曜日 (年)
例:0 15 10 * * ? (毎日10時15分に実行)
Cron式設定画面
応用編:Apexクラスからのスケジュール設定
「営業時間内(9:00〜18:00)のみ、10分間隔で実行したい」といった要件を満たすためには、通常のスケジュール登録1つでは対応できません。主に次の2つの設計パターンが考えられます。
1.Cron式で複数スケジュールをあらかじめ一括登録する
1時間内の実行タイミング(00分、10分、20分...)ごとに、別々のスケジュール済みジョブとして登録する手法です。
【メリット】
各起動トリガーが独立しているため、1回のバッチが何らかの理由で停止しても、次の10分後の処理は確実に実行される(処理が完全に止まらない)。
【デメリット】
Salesforce組織全体で「スケジュール済みジョブは最大100件まで」というガバナ制限がある。10分おきに設定するとそれだけで6枠消費し、他のシステム連携のスケジュール枠を圧迫するリスクがある。
【メリット】
各起動トリガーが独立しているため、1回のバッチが何らかの理由で停止しても、次の10分後の処理は確実に実行される(処理が完全に止まらない)。
【デメリット】
Salesforce組織全体で「スケジュール済みジョブは最大100件まで」というガバナ制限がある。10分おきに設定するとそれだけで6枠消費し、他のシステム連携のスケジュール枠を圧迫するリスクがある。
スケジュール済ジョブ画面
2.バッチの finish メソッドから次回を起動する(チェイニング)
最初の1回だけスケジュール登録を行い、バッチが完了した後の finish メソッド内で、数分後に再度バッチを起動(System.scheduleBatch を使用)する手法です。
【メリット】
「前の処理が終わってからX分後」に起動するため、処理時間が延びても多重起動(重複実行)のリスクが全くない。また、スケジュールの枠も消費しない。
【デメリット】
バッチ処理が予期せぬエラー等で異常終了した場合、次回の起動予約が行われずに連鎖が途切れてしまう。別途、1時間に1回「連鎖が止まっていないか監視・再起動する」親スケジュールを組むなどのフェイルセーフ設計が必要。
【メリット】
「前の処理が終わってからX分後」に起動するため、処理時間が延びても多重起動(重複実行)のリスクが全くない。また、スケジュールの枠も消費しない。
【デメリット】
バッチ処理が予期せぬエラー等で異常終了した場合、次回の起動予約が行われずに連鎖が途切れてしまう。別途、1時間に1回「連鎖が止まっていないか監視・再起動する」親スケジュールを組むなどのフェイルセーフ設計が必要。
パターン1・2のメリット・デメリット
実装上の注意事項
上記のような高頻度バッチを実装する際は、システムに負荷をかけないための次の考慮が不可欠です。
1.多重起動(同時実行)の防止
パターン1などで「10分間隔のスケジュールだが、データ量が多くて処理に15分かかってしまった」場合、前のバッチが実行中にもかかわらず新しいバッチが起動してしまい、データのロック競合や予期せぬエラーを引き起こします。スケジュールクラスの冒頭で AsyncApexJob を検索し、排他制御を入れるのがベストプラクティスです。
global class MyBatchScheduler implements Schedulable {
global void execute(SchedulableContext sc) {
// 同じバッチが「待機中(Queued)」または「実行中(Processing)」かチェック
Integer activeJobs = [SELECT COUNT() FROM AsyncApexJob
WHERE JobType = 'BatchApex'
AND ApexClass.Name = 'MyBatch'
AND Status IN ('Queued', 'Processing', 'Preparing')];
// 既に実行中の場合は、新しいバッチを起動せずにスキップする
if (activeJobs > 0) {
System.debug('すでにバッチが実行中のため起動をスキップします');
return;
}
}
}実行中チェック処理
2.ガバナ制限と1日の非同期実行回数
バッチ(非同期処理)は、1日に実行できる回数(最大25万回、またはユーザー数に基づく)に制限があります。10分間隔で24時間動かすと1日144回消費します。そこまで大きな影響はありませんが、他の大量処理バッチと重なる組織では注意が必要です。
3.代替案の検討
スケジュールによる定期的なバッチ実行で実装する必要があるかの検討が必要です。
例えば「外部システムへ即時にデータ連携したいが、相手先のサーバー負荷やデータの競合を避けるため、同じ処理が同時に複数走るのを確実に防ぎたい」という場合は、細切れのスケジュールバッチよりも、Queueable Apex の登録前に実行中ジョブをチェックする構成を使った方がよいです。スケジュールバッチで細かく起動枠を消費するのではなく、レコードが更新されたタイミングで都度 Queueable を呼び出します。ただし、呼び出す直前にジョブを検索し、「すでに同じジョブが待機中や実行中であれば、新しいジョブはキューに入れない」という条件分岐を挟むことで、処理のリアルタイム性を保ちつつ、安全で確実な排他制御が実現できます。
例えば「外部システムへ即時にデータ連携したいが、相手先のサーバー負荷やデータの競合を避けるため、同じ処理が同時に複数走るのを確実に防ぎたい」という場合は、細切れのスケジュールバッチよりも、Queueable Apex の登録前に実行中ジョブをチェックする構成を使った方がよいです。スケジュールバッチで細かく起動枠を消費するのではなく、レコードが更新されたタイミングで都度 Queueable を呼び出します。ただし、呼び出す直前にジョブを検索し、「すでに同じジョブが待機中や実行中であれば、新しいジョブはキューに入れない」という条件分岐を挟むことで、処理のリアルタイム性を保ちつつ、安全で確実な排他制御が実現できます。
おわりに
今回はApexスケジューラーを用いた短時間・範囲指定起動の実装パターンを紹介しましたが、いかがでしたでしょうか。
1時間に1回のシンプルな要件であれば従来通り標準の設定画面からの登録で十分ですが、今回のようなカスタマイズを行うことで、標準機能の制限を超えた複雑な業務要件にも柔軟に対応できるようになります。多重起動を防止する排他制御を組み込むことで、予期せぬエラーやデータのロック競合といったシステムトラブルも減らせるのではないかと思います。また、要件に合わせてQueueable等の代替案と比較検討することで、よりシステムに優しく安全な処理が実現できます。実務における高度なバッチ処理の設計で非常に役立つテクニックなので、ぜひ活用してみてください。
1時間に1回のシンプルな要件であれば従来通り標準の設定画面からの登録で十分ですが、今回のようなカスタマイズを行うことで、標準機能の制限を超えた複雑な業務要件にも柔軟に対応できるようになります。多重起動を防止する排他制御を組み込むことで、予期せぬエラーやデータのロック競合といったシステムトラブルも減らせるのではないかと思います。また、要件に合わせてQueueable等の代替案と比較検討することで、よりシステムに優しく安全な処理が実現できます。実務における高度なバッチ処理の設計で非常に役立つテクニックなので、ぜひ活用してみてください。