【Laravel】QueueでBatchを使う方法と通常Jobとの違い
上記のLaravelのQueueに関するトピック。
LaravelのQueueで通常のJobを実行させる方法については理解しているものとして省略する。
今回は公式ドキュメントのBatchの項目が読んだだけでは、個人的には分かりづらい部分があったので実際に試してみた内容をまとめる。
Jobとの共通点・相違点
共通点の1つ目は、処理を定義するファイルは同じものを使えば良いこと。
Jobを定義する際はphp artisan make:job
を叩くことで生成されるShouldQueueファイルにコンストラクタ、処理内容、振る舞い等を定義する。そして、Batchを定義する際もShouldQueueインターフェースを実装したクラスに記載すれば良い。
共通点の2つ目は、実行する際の要領。
Jobを実行させるには、まずphp artisan queue:work
でワーカープロセスを常駐させる。そしてこのワーカープロセスが動いている状態でdispachされたJobが順番に実行されていく。queueの名前についてはdipatchメソッドにonQueueメソッドをチェインすることで任意の名前を指定できる。
Batchについても同様にworkerプロセスを立ち上げた状態で、--queue
オプションで指定した名前のBatchを順番に実行していく。dispatchする方法もJobのときと同じで、Batchを定義したクラスを呼び出してdipatchメソッドを実行すれば良い。
相違点は、Batchステータスなどのメタ情報を管理するjob_batchesというテーブルを作成するのが前提となること
php artisan queue:table
php artisan migrate
Jobについては複数のQueueドライバが存在し、データベースに専用テーブルを作ってJobを保持する方法や、Jobを保持せずその場で処理を行なう同期(sync)というパターンもある。
databaseをQueueドライバにする際は上記コマンドでjobs
という名前のテーブルが作成されるが、こちらは実行や再試行待ちのJobのみが記録される。失敗したファイルはLaravelプロジェクト作成時にデフォルトでマイグレーションファイルが存在する、failed_jobs
テーブルに蓄積されていくが、成功したものは自身で処理を実装しないとjobsテーブルからは消えてしまって残らない。
Queueについてはjob_batchesテーブルを作成して運用することが前提となっている。
php artisan queue:batches-table
php artisan migrate
特に何も設定していなくてもBusファサード
のbatchメソッド内にそのBatchで実行したいJobクラスをまとめておくだけで、job_batchesテーブルのレコードは勝手に更新される。
job_batchesテーブルのマイグレーションファイルでスキーマを確認すれば、メタ情報にどんな項目が含まれるか何となく分かるかと。
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
最低限の動作を確認する
適当に作成したファイルで最低限の使い方だけ試したので、そのやり方を残しておく。
テーブルの作成
php artisan queue:table
php artisan queue:batches-table
php artisan migrate
Jobファイルの作成
php artisan make:job SampleJob
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable; //追加
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class SampleJob implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; //Batchableトレイトを追加
public function __construct()
{
//
}
public function handle(): void
{
Log::info('ジョブを実行しました。');
}
}
Batchableトレイトを追加しておかないと、呼び出し元Batchに紐づくJobだと認識されずにエラーが発生する。
Batchファイルの作成
php artisan make:job SampleBatch
一旦公式例の通りにBusファサードを使った振る舞いを定義してみる。
public function handle(): string
{
$batch = Bus::batch([
new SampleJob(),
])->then(function (Batch $batch) {
})->catch(function (Batch $batch, Throwable $e) {
})->finally(function (Batch $batch) {
})->onQueue('SampleJob')->dispatch();
return $batch->id;
}
onQueueメソッドは各Jobクラスではなくbatchメソッドにチェインさせる。
配下に紐づく各Jobについて個別にQueueを指定できない模様。
BatchをQueueに投入する
Batchのディスパッチを実行するartisanコンソールを作成する。
php artisan make:command ExecSampleBatchCommand
protected $signature = 'app:exec-sample-batch-command';
public function handle()
{
SampleBatch::dispatch()->onQueue('SampleBatch');
}
.envでQueueドライバをデータベースに設定。
QUEUE_CONNECTION=database
キャッシュクリアを実行すると作成したコンソールを実行し、QueueにBatchを投入する。
php artisan optimize:clear
php artisan app:exec-sample-batch-command
通常のJobと同じようにjobsテーブルにデータが作成される。
ワーカープロセスを立ち上げて処理を開始する
ワーカーの立ち上げるとQueueにスタックされたBatchとJobが実行される。
--queue
オプションでBatchとJobの両方のQueue名を指定する必要がある。
php artisan queue:work --queue=SampleBatch,SampleJob
2023-06-16 11:14:19 App\Jobs\SampleBatch .... RUNNING
2023-06-16 11:14:19 App\Jobs\SampleBatch ....209.97ms DONE
2023-06-16 11:14:19 App\Jobs\SampleJob .... RUNNING
2023-06-16 11:14:19 App\Jobs\SampleJob .... 54.06ms DONE
実行結果を確認する
Busファサードが実行された時点でjob_batchesテーブルにレコードが生成される。
先ほど実行したBatchについては、配下のjobが1件で失敗したものがないためfailed_jobsのカウントは0件、failed_job_idsは空の配列となっている。
finished_atについては、配下に未実行のJobが存在すれば記録されずにNULLとなる。
ただし、全てのJobを実行してその中に失敗したものが含まれている場合、Batchは実行したものとみなされる。
Jobファイルで例外を投げて失敗させてみる
先ほどのSampleJobクラスで例外を投げて失敗させて見る。
public function handle(): void
{
throw new \Exception('ジョブに失敗しました。');
}
php artisan queue:work --queue=SampleBatch,SampleJob
2023-06-16 11:37:04 App\Jobs\SampleBatch ..... RUNNING
2023-06-16 11:37:05 App\Jobs\SampleBatch ..... 205.03ms DONE
2023-06-16 11:37:05 App\Jobs\SampleJob ..... RUNNING
2023-06-16 11:37:05 App\Jobs\SampleJob ..... 43.41ms FAIL
failed_jobsのカウントが1となり、失敗したjobのIDがfailed_job_idsの配列に増えている。
また、配下のJobが失敗するとBatchは自動的に「キャンセル済み」という扱いになり、cancelled_atにタイムスタンプが格納されている。
配下のJobの失敗によって自動的にキャンセル済みにマークしない場合は、以下のようにBusファサードにallowFailuresメソッドをチェインする。
public function handle(): string
{
$batch = Bus::batch([
new SampleJob(),
])->then(function (Batch $batch) {
})->catch(function (Batch $batch, Throwable $e) {
})->finally(function (Batch $batch) {
})->onQueue('SampleJob')->allowFailures()->dispatch();
return $batch->id;
}
以上
Discussion