2026年7月15日

2026年7月15日

LaravelのJob・Queueでバックグラウンド処理を実装する方法

メール送信や画像処理、外部 API の呼び出しのような時間のかかる処理は、Laravel の Job(ジョブ)にしてキュー(Queue)へ積み、ワーカーに裏で実行させると、ユーザーを待たせずに済みます。この記事では、キューの接続先の選び方、ジョブの作成と投入、queue:work によるワーカーの起動、失敗時の再試行、本番で Supervisor を使って常駐させる方法、そして処理が進まないときの確認手順までを説明します。

キューの仕組みを押さえる

キューを使った処理は、3つの役割に分かれます。

  • 投入する側: コントローラーなどで「この処理をあとでやって」とジョブをキューに入れる
  • キュー: ジョブを順番に保管しておく場所(データベース、Redis、Amazon SQS など)
  • ワーカー: キューからジョブを取り出して実行し続ける常駐プロセス

ワーカーは Web リクエストとは別のプロセスで動きます。ジョブを投入するだけでワーカーを起動していなければ、ジョブはキューにたまるだけで実行されません。これがキューを導入したときに最も多いつまずきです。

接続先(ドライバー)を決める

キューの接続先は .env の QUEUE_CONNECTION で決まります。

  • sync: キューを使わず、その場で実行します。動作確認には便利ですが、バックグラウンド処理にはなりません。
  • database: データベースの jobs テーブルを使います。Laravel 11 以降の雛形では既定値で、テーブル作成用のマイグレーションも最初から含まれています。小〜中規模なら追加のサーバーなしで始められます。
  • redis: 処理件数が多い場合に向きます。Redis のダッシュボード兼ワーカー管理ツールである Laravel Horizon も使えるようになります。
  • sqs: AWS 上で運用する場合の選択肢です。

既定の接続や設定項目は版によって変わるため、手元の config/queue.php を確認してください(2026年10月現在)。まずは database で始め、件数が増えてから Redis に移る進め方で十分です。

QUEUE_CONNECTION=database

古いプロジェクトで jobs テーブルが無い場合は、Laravel 11 以降なら php artisan make:queue-table、それより前なら php artisan queue:table でマイグレーションを作り、php artisan migrate を実行します。

ジョブを作成する

php artisan make:job SendWelcomeMail

app/Jobs/SendWelcomeMail.php が作られます。ユーザー登録後にウェルカムメールを送るジョブにしてみます。

<?php

namespace App\Jobs;

use App\Mail\WelcomeMail;
use App\Models\User;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Throwable;

class SendWelcomeMail implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;          // 最大3回まで試す
    public int $timeout = 120;      // 1回の実行は120秒まで
    public array $backoff = [10, 60]; // 再試行までの待ち秒数(2回目は10秒後、3回目は60秒後)

    public function __construct(public User $user)
    {
    }

    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }

    public function failed(?Throwable $exception): void
    {
        // 個人情報やトークンをログに残さず、追跡に必要な ID だけ記録する
        Log::warning('Welcome mail failed', [
            'user_id' => $this->user->id,
            'error' => $exception?->getMessage(),
        ]);
    }
}

ShouldQueue を実装したクラスは、キューを通して実行されます。コンストラクターに Eloquent モデルを渡すと、キューにはモデルの ID だけが保存され、実行時にデータベースから取り直されます。そのため、投入から実行までの間にデータが更新されていれば、実行時点の最新の内容が使われます。大きな配列やファイルの中身を直接渡すとキューのデータが肥大化するので、ID やパスを渡してジョブの中で読み込むのが基本です。

handle() の中で例外が投げられると、そのジョブは失敗扱いになり、$tries の回数まで再試行されます。上限に達すると failed() が呼ばれ、failed_jobs テーブルに記録されます。

ジョブを投入する

use App\Jobs\SendWelcomeMail;

SendWelcomeMail::dispatch($user);

// 10分後に実行
SendWelcomeMail::dispatch($user)->delay(now()->addMinutes(10));

// 優先度の高いキューに入れる
SendWelcomeMail::dispatch($user)->onQueue('high');

データベースのトランザクションの中で投入する場合は注意が必要です。コミット前にワーカーがジョブを拾うと、まだ保存されていないデータを探しに行って失敗することがあります。->afterCommit() を付けると、トランザクションのコミット後に投入されます。

ワーカーを起動する

開発中は次のコマンドでワーカーを動かします。

php artisan queue:work

複数のキューを優先順位つきで処理するなら --queue=high,default のように指定します。queue:work はアプリケーションのコードをメモリに読み込んだまま動き続けるため、コードを変更しても自動では反映されません。開発中はワーカーを止めて起動し直すか、変更を検知して読み直す php artisan queue:listen を使います。

本番では Supervisor で常駐させる

本番サーバーでは、ワーカーが落ちても自動で再起動するように、Supervisor などのプロセス管理ツールに任せます。以下は設定例です。パスやユーザー名は自分の環境に合わせて書き換えてください。

; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/my-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/my-app/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"

--max-time=3600 は1時間ごとにワーカーを入れ替えるための指定で、メモリの肥大化を防ぎます。stopwaitsecs は、停止時に実行中のジョブが終わるまで待つ秒数で、最も長いジョブの実行時間より長くしておきます。設定はステージング環境で試してから本番に反映してください。

デプロイ後は queue:restart

新しいコードをデプロイしたら、次のコマンドでワーカーに再起動を指示します。

php artisan queue:restart

ワーカーは実行中のジョブを終えてから終了し、Supervisor が新しいコードで起動し直します。この再起動の合図はキャッシュを通じて伝わるため、キャッシュの設定が正しく動いていることが前提です。

失敗したジョブを扱う

php artisan queue:failed          # 失敗したジョブの一覧
php artisan queue:retry all       # すべて再実行
php artisan queue:retry <ID>      # 特定のジョブだけ再実行
php artisan queue:forget <ID>     # 記録を削除

再実行する前に、failed_jobs の例外メッセージや storage/logs/laravel.log を読み、原因が解消されているかを確かめます。外部 API の一時的な障害なら再実行で通りますが、コードの不具合が原因なら、修正をデプロイしてからでないと同じ失敗を繰り返します。メール送信のように何度も実行されると困る処理は、二重実行されても問題が起きない作りにするか、ShouldBeUnique で重複投入を防ぎます。

処理が進まないときの確認手順

起きやすい順に確認します。

  1. ワーカーが動いていない: sudo supervisorctl status で状態を見ます。開発中なら queue:work を起動しているかを確認します。
  2. **QUEUE_CONNECTION が sync のまま**: その場で実行されるため、画面の応答が遅いままになります。.env を確認し、php artisan config:clear を実行します。
  3. ワーカーが別のキューを見ている: onQueue('high') で投入したのに、ワーカーが default しか処理していないケースです。--queue の指定をそろえます。
  4. 古いコードで動いている: デプロイ後に queue:restart を実行したかを確認します。
  5. タイムアウトと再投入の設定が合っていない: config/queue.php の retry_after がジョブの $timeout より短いと、実行中のジョブが別のワーカーに再び拾われ、二重実行の原因になります。retry_after を数秒以上長くします。

まとめ

  • 時間のかかる処理は ShouldQueue を実装したジョブにして dispatch() で投入する
  • 接続先は database から始め、件数が増えたら Redis と Horizon を検討する
  • ワーカーは本番では Supervisor などで常駐させ、デプロイ後に queue:restart を実行する
  • $tries・$backoff・failed() で失敗時の動きを決め、queue:failed と queue:retry で対処する
  • retry_after はジョブの $timeout より長くし、二重実行を防ぐ

お気軽にご相談ください

お見積りへ お問い合わせへ
▲