以前紹介した下記の文書ではQueueの基本設定を確認しました。今回は前回の文書で動作確認を行なっていないQueueの設定についてもう少し詳細に説明を行なっています。

Laravel環境は下記の文章で構築したものを利用しています。ユーザ登録後に送信するWelcomeメールのジョブを利用してキューの動作確認を行なっていきますで下記の文書を先に読むことをお勧めします。

ジョブの実行を遅らせる

キューに入れたジョブの実行を遅らせたい時、delayメソッドを利用することができます。

下記ではジョブがキューに入ってから3分後に実行できるように設定を行なっています。


SendWelcomeMail::dispatch($user)->delay(now()->addMinutes(3));

ユーザがユーザを登録を行なってから3分後にキューに入ったジョブが処理されました。Processingのメッセージが表示されるまでに3分間かかります。ProcessingからProcessedまでの間が3分というわけではありません。


$ php artisan queue:work
[2019-08-27 04:59:04][5] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 04:59:08][5] Processed:  App\Jobs\SendWelcomeMail

ジョブを即時実行させる

ジョブを遅らせるdelayメソッドが準備されていましたが、即座に実行するdispatchNowメソッドも準備されています。dispatchNowメソッドを使うとすぐにジョブが実行されます。この場合はキューを経由せず同期処理として実行されるため、登録したユーザはメールが送信するまで次の画面への移動しないため待ちが生じます。

ジョブが失敗した場合の動作

ジョブが失敗した場合にはどのような処理が行われるのか気になるところです。意図的にジョブを失敗させて動作確認を行なっていきます。

メール送信のジョブSendWelcomeMail.phpファイルを開いて、handleメソッドにExceptionを追加します。


public function handle()
{

    throw \Exception('意図的なエラー');

    Mail::to($this->user->email)->send(new WelcomeMail());
    
}
ジョブのコードの更新を行なったら、Workder(ワーカー)を更新する必要があります。
fukidashi

ユーザ登録を実行するとジョブは失敗しても繰り返し実行されるのがわかります。


$ php artisan queue:work
[2019-08-27 05:07:54][7] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][8] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][9] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][10] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][11] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][12] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:07:54][13] Processing: App\Jobs\SendWelcomeMail
  ・
  ・

storage/logsの下に保存されているログファイルにもExceptionのエラーがジョブの実行数と同じ数書き込まれて膨大な量になります。

登録を行なったユーザ側のブラウザには何もエラーは表示されません。

再実行の間隔を変更する

デフォルトでは失敗するとすぐに再実行されることがわかりました。–delayオプションを指定すると次に再実行までの時間を指定することができます。

–delayオプションでは秒単位で指定できるので、下記ではジョブが失敗すると60秒後に再実行されます。


$ php artisan queue:work --delay 60

再実行回数を指定する

デフォルトではジョブでエラーが発生すると再実行を続けることがわかりました。延々と続く再実行の回数を設定することができます。–triesオプションを使い、再実行の回数を指定します。3としてので、3回目でFailed(失敗)しているのでそれ以上は再実行されません。


$ php artisan queue:work --tries 3
[2019-08-27 05:32:58][1039] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:32:58][1040] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:32:58][1041] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 05:32:58][1041] Failed:     App\Jobs\SendWelcomeMail

キューに入っていたジョブもFailedするとキューから削除されます。ログファイルを確認すればエラーは確認できますが、どのジョブが失敗したのかを確認することはできません。Laravelでは失敗したジョブを登録するテーブルが準備されています。

失敗したジョブを登録するテーブルの作成

失敗したジョブを登録するテーブルを作成するマイグレーションファイルを作成するphp artisan queue:failed-tableが準備されているので実行します。Laravelのバージョンによってはusersテーブルと一緒にfailed-tableも作成されるためこの処理は必要ではありません。


$ php artisan queue:failed-table
Migration created successfully!

実行するとdatabase¥migrationsディレクトリの中にマイグレーションファイルが作成されるので、php artisan migrateコマンドを実行します。データベースにfailed_jobsテーブルが作成されます。


$ php artisan migrate
Migrating: 2019_08_27_054022_create_failed_jobs_table
Migrated:  2019_08_27_054022_create_failed_jobs_table (0 seconds)

作成後に失敗したジョブを確認するコマンドを実行してみます。failed_jobsテーブルには失敗したジョブが登録されていないので、No Failed jobs!と表示されます。


 $ php artisan queue:failed
No failed jobs!

失敗したジョブの登録確認

失敗したジョブを登録するfailed_jobsテーブルが作成されたので、再度意図的にエラーが発生するジョブを実行してみましょう。ワーカーのtriesオプションは3で起動します。

ジョブが失敗したことを確認して、php artisan queue:failedコマンドを実行すると失敗したジョブの情報が登録されていることが確認できます。

failed_jobテーブルを確認
failed_jobテーブルを確認

failed_jobsテーブルにも失敗したジョブに関する情報は登録されます。

失敗したジョブの再実行

失敗したジョブを確認後、コードの修正を行う必要があります。コードの処理が完了後、失敗したジョブを再実行することができます。

php artisan queue:retryコマンドを使用します。引数には失敗したジョブのIDを指定する必要があります。指定がなければ再実行できません。


 $ php artisan queue:retry 1
The failed job [1] has been pushed back onto the queue!

実行するとジョブが実行されるのではなく、ジョブがキューに戻されるので、ワーカーが起動していなければ処理は行いません。

修正を行なっても再度失敗した場合は、以前の失敗したジョブの情報はなくなり、新しいIDで新規で失敗したジョブがfailed_jobsテーブルに登録されます。


 $ php artisan queue:work --tries=2
[2019-08-27 06:03:41][1045] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:03:41][1046] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:03:41][1046] Failed:     App\Jobs\SendWelcomeMail
Retryでも再度失敗
Retryでも再度失敗

ジョブのコードを修正したら必ずワーカーを再起動する必要があります。

Retryをしてジョブが正常に完了するとfailed_jobsテーブルから前回失敗したジョブの情報は削除されます。

失敗したジョブをテーブルから削除したい

失敗したジョブはコードを修正し、ジョブをRetryして正常に処理が完了するとfailed_jobsテーブルから情報は削除されます。

失敗したジョブを再実行する必要がない場合は削除することができます。失敗したジョブのIDを指定してphp artisan queue:forgetコマンドを実行します。


 $ php artisan queue:forget 4
Failed job deleted successfully!

failed_jobsテーブルに登録されているすべてのジョブを一括で削除したい場合は、php artisan queue:flushコマンドを実行します。


$ php artisan queue:flush
All failed jobs deleted successfully!

ジョブファイルで再実行回数を設定する

ジョブファイルの変数triesを設定することでジョブごとに再実行回数を指定することができます。ここではSendWelcomeMailジョブを使っているので下記のように$tries変数を追加します。


class SendWelcomeMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $user;

    public $tries = 5;

php artisan queue:workには–triesオプションを指定していませんが、ジョブファイルで指定した$tires=5でジョブがFailedしていることがわかります。ジョブごとに個別に再実行回数を指定したい場合は$tires変数を利用します。


$ php artisan queue:work 
[2019-08-27 06:33:45][1053] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:33:45][1054] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:33:45][1055] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:33:45][1056] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:33:45][1057] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 06:33:45][1057] Failed:     App\Jobs\SendWelcomeMail

キューを処理するための2つのコマンド

キューのジョブを処理するための方法には、php artisan queue:workとphp artisan queue:listenの2つがあります。

queue:workではコードの変更があれば一度停止して、再度起動する必要がありますが、queue:listenだと一度起動をすればコードの修正を行なっても再起動する必要がありません。–tries等のオプションはどちらも使用することができます。

一見queue:listenのほうがいいように思えるかもしれませんが、コードの変更がジョブを実行するために反映されるということはジョブの実行する度にフレームワークを読み込んでいることになり処理に負荷がかかることになります。queue:workは起動した時にフレームワークを読み込んで以後再起動するまでは読み込むことはないため、コードが起動中に反映されることはありませんが処理の負荷がqueue:listenよりも低いことになります。

開発時にはqueue:listenを使って、本番環境ではqueue:workを使うといった使い分けができます。

Laravelのマニュアルには、this command(queue:listen) is not as efficient as queue:work:(queue:listenコマンドはqueue:workほど効率的ではありません。
fukidashi

別のQueue_Connectionを利用する

.envファイルのQUEUE_CONNECTIONでdatabaseを指定していても別のConnectionを利用することができます。

下記のようにdispatchメソッドの後にonConnectionメソッドを追加し、使いたいキューの値を入力するとそのキューにジョブを入れることができます。


SendWelcomeMail::dispatch($user)->onConnection('redis');

実行するとredisのデータベースにジョブの情報が登録されます。そのジョブを実行したい場合は、ワーカーを起動する際にredisを指定します。redisのキューにジョブが入っていれば、下記のように実行されます。


$ php artisan queue:work redis
[2019-08-27 07:18:34][RwmIWRZYXCenZjF9ygZTiEoPwl2Tj6xr] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 07:18:37][RwmIWRZYXCenZjF9ygZTiEoPwl2Tj6xr] Processed:  App\Jobs\SendWelcomeMail

複数のキューを利用する

これまでのキューの動作確認では気にしてきませんでしたがキューにも名前が付いています。queue.phpを見るとその値を確認することができます。

queueの値がdefaultとなっていますが、これがキューの名前です。defaultとなっていますが、これは変更することができます。


'database' => [
    'driver' => 'database',
    'table' => 'jobs',
    'queue' => 'default', //ここ
    'retry_after' => 90,
],

またキューは1つではなく複数のキューを利用することができます。下記のようにdispatchメソッドの後にonQueueメソッドを追加し、名前を入力するとその名前のキューにジョブが入ります。


SendWelcomeMail::dispatch($user)->onQueue('Email');

ユーザ登録を実行後、jobsテーブルの中身を確認するとqueue列にEmailと表示されていることが確認することができます。

列queueにEmail
列queueにEmail

defaultとは異なる名前をつけているので通常通りワーカーを起動しても処理は行ないません。


$ php artisan queue:work

上記では実行されません。

ワーカーを起動する時にも設定したキューの名前を指定する必要があります。–queueオプションでキューの名前を指定すると実行されます。


$ php artisan queue:work --queue=Email
[2019-08-27 07:37:59][1065] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 07:38:02][1065] Processed:  App\Jobs\SendWelcomeMail

例えばhighとlowという2つのキューを作成し、ジョブをいれた場合2つのキューを1つのワーカーで処理することができますが、先に記述したhighを先に処理し、次にlowのキューに入ったジョブの処理を行います。このようにキューに優先度をつけることができます。


$ php artisan queue:work --queue=high,low

別のConnectionを使って別のキューに入れる

デフォルトでdatabaseのキューを利用していてもredisのキューを利用できることがわかりました。また、キューもデフォルトのdafaultではなく任意の名前のキューを作成することもわかりました。下記のようにonConnectionとonQueueを使うことでredisのキューを利用したEmailという名前のキューを作成することができます。


SendWelcomeMail::dispatch($user)
                ->onConnection('redis')
                ->onQueue('Email');

queueの中のjobsを削除したい

jobsテーブルに入っているjobをすべて削除したい場合はclearコマンドを実行することでjobsテーブルたまったjobを一括で削除することができます。


 $ php artisan queue:clear