19
11

はじめに

Qiita Engineer Festa 2024 が開催されているせっかくの機会なので、
最近の開発の際に初めて触った、LaravelのJob Queueについて
まとめてみようと思います!!!

前提条件

MacBook Pro Apple M2
PHP 8.0.27
Laravel 9.52.15

そもそもJob Queueとは

Webアプリケーションの構築中に、アップロードされたCSVファイルの解析や保存など、通常のWebリクエスト中に実行するのでは時間がかかりすぎるタスクが発生する場合があります。幸運なことに、Laravelを使用すると、バックグラウンドで処理したい仕事をキューへ投入するジョブクラスが簡単に作成できます。時間のかかるタスクをキューに移動することで、アプリケーションはWebリクエストに驚異的な速度でレスポンスし、顧客により良いユーザーエクスペリエンスを提供できます。

公式ドキュメントにはこのように書いてあります:point_up:

今回業務で扱う前までは
会員登録後の確認メール送信なんかで使ってるんだっけ??くらいの認識でした。

非同期で処理を実行したいときにはとっておきの機能です!!

そこで今回、私がJob Queueを採用する主な理由としては、
OpenAIに対してAPI通信を行うため、レスポンス待ちによってユーザー体験が下がることを防ぐためです。

+------------------+
| User Request     | クライアントサイドからリクエストを飛ばす
+------------------+
         |
         v
+---------------------+
| Application Server  |
+---------------------+
         | 1. Jobの生成
         | 2. JobをQueueに追加
         v
+-------------------+      +------------------+
| Job Queue         | ---> | Client Receives  | クライアントサイドには
+-------------------+      | Immediate        | 即座にレスポンスを返す
         |                 | Response         |
         v                 +------------------+
+-------------+
| Worker      | QueueからJobを取り出し、処理を実行
+-------------+
         |
         v
+------------------+
| Success or Error | 成功時:メール送信や通知、DBへの保存など・・
+------------------+ 失敗時:failed_jobsテーブルへレコード追加
         |
         v
+----------------------+
| User Notification    |
+----------------------+

実装手順

Queueテーブルの作成

まず、jobを保持するためのQueue用のテーブル
jobの実行が失敗した際にレコードが追加されるfailed_jobsテーブルを作成します。

php artisan queue:table
php artisan queue:failed-table

php artisan migrate
       
jobsテーブル
# column_name 備考
1 idユニークID
2 queueジョブが属するキューの名前
3 payloadジョブの実行に必要なデータを含むJSON形式の文字列
dispatchメソッドの引数の部分が入る
4 attemptジョブの実行試行回数
5 reserved_at処理が予約された日時のタイムスタンプ
6 available_atジョブが次に実行可能となる日時スタンプ
7 created_at作成日時

envファイルの変更

QUEUE_CONNECTIONをsyncからdatabaseに変更することでqueueテーブルにjobが積まれるようになります。

QUEUE_CONNECTION=database

Jobクラスの作成

アプリケーションのすべてのqueueableジョブは、app/Jobsディレクトリに保存します。
このディレクトリがない場合でもコマンドを実行すると自動で作成されます。

php artisan make:job HogeJob

非同期処理の追加(dispatchメソッド)

Jobクラスを作成すると、dispatchメソッドによってJobをディスパッチすることができます。非同期処理を差し込みたい箇所に処理を追加します。

※dispatchメソッドの引数はコンストラクタに渡されます。

▼ 例:HogeController storeメソッド内

HogeController.php
<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Jobs\HogeJob;
use App\Models\Hoge;
use Illuminate\Http\Request;
 
class HogeController extends Controller
{
    /**
     * Store a new hogehoge.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $hoge = Hoge::create(/* ... */);
 
        // ...
 
        HogeJob::dispatch($hoge); //Jobクラス名(HogeJob)
    }
}

    // Queueに積まれてから10分後に実行という設定も可能
    HogeJob::dispatch($hoge)->delay(now()->addMinutes(10));

Jobクラスに処理を追加

Jobクラス内では、Job実行に対して幾つかの条件(プロパティ)を設定することができます。

Jobが失敗した際に再試行する最大回数タイムアウトになるまでの最大秒数などなど
公式ドキュメント にたくさん書いてあります!

HogeJob.php
namespace App\Jobs;

use App\Models\Hoge;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

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

    /**
     * ジョブが失敗した場合に再試行する最大回数を指定
     *
     * @var int
     */
    public $tries = 3;

    /**
     * ジョブがタイムアウトになるまでの最大秒数を指定
     *
     * @var int
     */
    public $timeout = 120;

    /**
     * hogeインスタンス
     *
     * @var \App\Models\Hoge
     */
    protected $hoge;

    /**
     * 新しいジョブインスタンスの生成
     *
     * @return void
     */
    public function __construct()
    {
        $this->hoge = $hoge;
    }

    /**
     * ジョブの実行
     *
     * @return void
     */
    public function handle()
    {
        // 実行内容を記述
    }

    /**
     * ジョブの失敗を処理
     *
     * @param Throwable $exception
     * @return void
     */
    public function failed(Throwable $exception): void
    {
        // ジョブ失敗時にslackで通知
        $this->sendFailedJobSlackNotification($exception); //例
    }
}

Jobの実行

下記コマンドを叩くことで、Queueに積まれたJobを実行することができます。※1

php artisan queue:work

タスクスケジュールで定期実行

今回ステージングおよび本番環境では、起動コマンドをタスクスケジュールに設定して運用します。

毎分起動コマンドを実行し、withoutOverlappingをつけ重複実行しないよう設定します。

※本番環境では、Supervisorを使用して自動で再起動する仕組みを構築することが、ドキュメントでは薦められています。

app/Kernel.php

protected function schedule(Schedule $schedule)
{
    // 毎分実行、重複起動無し おすすめ理由自動生成するためのjobを実行
    $schedule->command('queue:work')
        ->everyMinute()
        ->withoutOverlapping();

    // 5分ごと実行、重複起動無し おすすめ理由自動生成失敗したjobを再実行
    $schedule->command('queue:retry all')
        ->everyFiveMinutes()
        ->withoutOverlapping();
}

その他実行コマンド

// すべてのワーカを再起動
php artisan queue:restart

// 再起動せずコード反映 ※2
php artisan queue:listen

// 失敗したジョブをすべて再試行
php artisan queue:retry all

// failed_jobsのレコードを全て削除
php artisan queue:flush

※1 キューワーカは存続期間の長いプロセスであるため、再起動しない限り、コードの変更が反映されません!
コード修正した場合は、再起動コマンドを実行してください。

※2 listenコマンドはコードの反映を即座に反映しますが、workコマンドより大幅にパフォーマンスが下がります。

最後に

今回のJob Queueの処理は最低限のコードで書いたので、
改めて調べてみるともっと色々なことができそうだし、カスタマイズができそうです。

サービスプロバイダなんかも使うと、複数のQueueを使い分けることができたり、
ジョブの処理前、後、失敗時を切り分けてロジックを組めたりできそう!

最後までお読みいただきありがとうございました!

参考記事

19
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11