【PHP】Laravelでキューとジョブを実装する方法とその利用シーン

対象バージョン: Laravel 13.x / PHP 8.3+
一次ソース: Queues – Laravel 13.x

メール送信や画像処理のような時間のかかる処理をリクエスト中に実行すると、レスポンスが遅くなります。Laravel のキューを使うと、こうした処理を「ジョブ」としてキューに積み、別プロセスのワーカーにバックグラウンドで実行させられます。この記事では、ジョブの作成からディスパッチ、ワーカーの起動、失敗時の扱い、本番運用までを Laravel 13.x の既定構成で順に説明します。

この記事で分かること

  • make:job で生成されるジョブクラスの構成と、ProcessOrder::dispatch($order) によるディスパッチ
  • config/queue.php と .env の役割の違い、既定の database ドライバ
  • queue:work / queue:restart と、本番での Supervisor による常駐
  • $tries / $backoff / failed() / queue:failed / queue:retry による失敗処理
  • delay / onQueue / dispatchSync の使い分け

前提として、Laravel 13.x の新規プロジェクト(php artisan migrate 実行済み)を想定します。

キューの設定:config/queue.php と .env

キューの設定は 2 か所に分かれています。

ファイル 役割
config/queue.php 接続(connection)の定義。各ドライバの接続設定、失敗ジョブの保存先などを記述する
.env QUEUE_CONNECTION で、どの接続を既定として使うかを選ぶ

config/queue.php の既定値は次のように .env を参照しています。

'default' => env('QUEUE_CONNECTION', 'database'),

Laravel 13.x の新規プロジェクトでは、.env も次の値になっています。

QUEUE_CONNECTION=database

ドライバには sync、database、beanstalkd、sqs、redis などがあります。.env だけでキューの設定が完結するわけではなく、接続の中身は config/queue.php 側で定義されている点に注意してください。

jobs テーブルは最初から用意されている

database ドライバは、ジョブをデータベースの jobs テーブルに保存します。Laravel 13.x では、既定のマイグレーション database/migrations/0001_01_01_000002_create_jobs_table.php に jobs・job_batches・failed_jobs の 3 テーブルが含まれています。そのため、php artisan migrate を実行していれば追加の作業は不要です。

既存プロジェクトなどでテーブルが欠けている場合だけ、次のコマンドでマイグレーションを作成します。

php artisan make:queue-table
php artisan migrate

ジョブの作成

ジョブクラスは make:job コマンドで生成します。

php artisan make:job ProcessOrder

app/Jobs/ProcessOrder.php が作成されます。Laravel 13.x で生成される内容は次のとおりです。

<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessOrder implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        //
    }
}

ShouldQueue を実装したクラスは、ディスパッチするとキューに積まれます。トレイトは Queueable の 1 つだけです。

ジョブクラスを完成させる

注文を処理するジョブとして、コンストラクタで受け取った注文を handle() で処理する形にします。App\Models\Order は説明用の Eloquent モデルです。

<?php

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
use Throwable;

class ProcessOrder implements ShouldQueue
{
    use Queueable;

    /**
     * 最大試行回数
     */
    public $tries = 3;

    /**
     * 再試行までの待機秒数
     */
    public $backoff = 10;

    public function __construct(
        public Order $order,
    ) {}

    /**
     * ジョブの本処理
     */
    public function handle(): void
    {
        // 時間のかかる処理(外部 API 連携、メール送信など)をここに書く
        Log::info("Processing order {$this->order->id}");
    }

    /**
     * すべての試行が失敗したときに呼ばれる
     */
    public function failed(?Throwable $exception): void
    {
        Log::error("Order {$this->order->id} failed: {$exception?->getMessage()}");
    }
}

ジョブに渡すデータは処理に必要な最小限にとどめます。パスワードなどの機密値や大きなデータをそのまま詰め込むのは避けてください。

ジョブのディスパッチ

コントローラーなどからジョブをキューに積むには、ジョブクラスの静的メソッド dispatch を使います。引数はコンストラクタに渡されます。

use App\Jobs\ProcessOrder;

ProcessOrder::dispatch($order);

dispatch ヘルパーでも同じようにディスパッチできます。

dispatch(new ProcessOrder($order));

遅延とキュー名の指定

実行を遅らせたい場合は delay を使います。

ProcessOrder::dispatch($order)->delay(now()->addMinutes(10));

指定した時刻までは、ワーカーが動いていてもジョブは実行されません。

onQueue を使うと、ジョブを積むキューの名前を指定できます。

ProcessOrder::dispatch($order)->onQueue('emails');

キュー名を分けると、重要度の高いジョブを先に処理するといった運用がしやすくなります。

すぐに実行する:dispatchSync と sync ドライバ

dispatchSync は、キューを経由せずにその場でジョブを実行します。

ProcessOrder::dispatchSync($order);

また、QUEUE_CONNECTION=sync にするとすべてのジョブが即時に同期実行されます。ローカル確認やテストには便利ですが、非同期にはならないため、レスポンスを速くする目的には使えません。

キューワーカーの起動

キューに積まれたジョブは、ワーカーが動いていないと実行されません。ワーカーは queue:work で起動します。

php artisan queue:work

よく使うオプションは次のとおりです。

# 処理するキューを優先順に指定(emails を先に処理)
php artisan queue:work --queue=emails,default

# ジョブを 1 件だけ処理して終了
php artisan queue:work --once

# キューが空になったら終了(開発時の確認に便利)
php artisan queue:work --stop-when-empty

queue:work は起動時にアプリケーションを読み込み、そのまま常駐します。そのため、コードを変更してもワーカーには反映されません。デプロイ後やコード変更後は、次のコマンドでワーカーを再起動します。

php artisan queue:restart

ワーカーは処理中のジョブを終えてから終了します。

本番環境では Supervisor で常駐させる

本番では、ターミナルで手動起動した queue:work に頼らず、Supervisor などのプロセスマネージャーでワーカーを常駐させます。ワーカーが停止しても自動で再起動されるようになります。設定例は次のとおりです(パスやユーザー名は環境に合わせて変更します)。

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work database --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=2
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600

設定後は、次のコマンドで設定を読み込み、ワーカーを起動します。

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"

詳しくは公式ドキュメントの Supervisor の項を参照してください。

失敗したジョブの扱い

handle() で例外が発生すると、ジョブは再試行されます。前述の ProcessOrder では、$tries = 3 で最大 3 回まで試行し、$backoff = 10 で再試行まで 10 秒待ちます。

すべての試行が失敗すると、failed() メソッドが呼ばれ、ジョブは failed_jobs テーブルに記録されます。失敗したジョブは次のコマンドで確認できます。

php artisan queue:failed

原因を修正したら、ID を指定して再実行するか、すべてをまとめて再実行します。

# ID を指定して再実行
php artisan queue:retry 80420fb6-2863-4074-b288-02c954c24ced

# すべての失敗ジョブを再実行
php artisan queue:retry all

重複ディスパッチを防ぐ:ShouldBeUnique

同じジョブが重複してキューに積まれるのを防ぎたい場合は、ShouldBeUnique インターフェイスを実装します。一意性のロックにはキャッシュが使われます。

既定では、一意性はジョブクラス単位で判定されます。そのままだと、ある注文のジョブが処理待ちの間は、別の注文のジョブをディスパッチしても破棄されてしまいます。注文ごとに一意にするには、uniqueId() で一意キーを返します。

use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessOrder implements ShouldQueue, ShouldBeUnique
{
    // ...

    public function uniqueId(): string
    {
        return (string) $this->order->id;
    }
}

ロックは、ジョブの処理が完了するか、すべての試行に失敗するまで保持されます。また、キャッシュドライバはアトミックロックに対応している必要があります(Laravel 13.x 既定の database ドライバは対応しています)。

キューとジョブの利用シーン

キューが向いているのは、ユーザーが結果をその場で待つ必要のない、時間のかかる処理です。

  • メール送信: 登録完了メールや大量の通知メールを送る(Mailable を使ったメール送信)
  • 画像・ファイル処理: アップロード画像のリサイズや変換
  • 外部 API 連携: 応答の遅いサービスへの送信や同期
  • 集計・分析: 複雑なデータの集計やレポート作成

決まった時刻に処理を実行したい場合は、タスクスケジューリングと組み合わせます。

まとめ

  • ジョブは make:job で作成し、ShouldQueue と Queueable を持つクラスの handle() に処理を書く
  • ProcessOrder::dispatch($order) でキューに積み、delay や onQueue で実行時刻やキューを指定する
  • 接続の定義は config/queue.php、既定接続の選択は .env の QUEUE_CONNECTION。Laravel 13.x の既定は database で、必要なテーブルは既定のマイグレーションに含まれる
  • ワーカーは queue:work で起動し、コード変更後は queue:restart、本番では Supervisor で常駐させる
  • 失敗は $tries / $backoff / failed() で制御し、queue:failed と queue:retry で確認・再実行する

参考リンク

コメントする