Laravel キューとジョブ:非同期アーキテクチャと面接対策 2026

Laravelキューシステムの内部動作、ジョブのバッチ処理・チェイニング・リトライ戦略を解説。2026年の技術面接で問われるキューアーキテクチャの重要ポイントを網羅します。

Laravel Queues and Jobs Architecture

Laravelキューは、メール送信・ファイルアップロード処理・レポート生成など時間のかかるタスクをバックグラウンドワーカーに委譲するための統一APIを提供します。ユーザーを待たせることなく、アプリケーションはジョブをキューにプッシュして処理を続行します。このメカニズムは、スケーラブルなLaravelアプリケーションの中核に位置します。

キューアーキテクチャの概要

Laravelは複数のキューバックエンド(Redis、Amazon SQS、データベース、Beanstalkd)をドライバー非依存の単一APIでサポートしています。ジョブはShouldQueueインターフェースを実装したシリアライズ可能なPHPクラスです。ワーカーがキューからジョブを取得し、デシリアライズしてhandle()メソッドを実行します。失敗したジョブは専用のfailed_jobsテーブルに格納され、リトライや調査が可能です。

Laravelジョブディスパッチの内部動作

ジョブクラスでdispatch()が呼び出されると、Laravelはジョブインスタンス(パブリックプロパティを含む)をシリアライズし、設定されたキュー接続にペイロードをプッシュします。シリアライズされたペイロードには、完全修飾クラス名、シリアライズされたプロパティ、ターゲットキュー名、許可された試行回数やタイムアウトなどのメタデータが含まれます。

キューワーカープロセス(php artisan queue:work)は、キューバックエンドから新しいジョブをポーリングする長寿命デーモンとして動作します。ペイロードを受信すると、ワーカーはジョブをデシリアライズし、サービスコンテナを通じて依存関係を解決し、handle()を呼び出します。

App/Jobs/ProcessInvoice.phpphp
namespace App\Jobs;

use App\Models\Order;
use App\Services\PdfGenerator;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

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

    public int $tries = 3;
    public int $backoff = 60;
    public int $timeout = 120;

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

    public function handle(PdfGenerator $pdf): void
    {
        // Generate PDF invoice for the order
        $invoice = $pdf->generate($this->order);

        // Store the generated file
        $this->order->update([
            'invoice_path' => $invoice->path(),
            'invoiced_at' => now(),
        ]);
    }

    public function failed(\Throwable $e): void
    {
        // Notify the ops team when invoice generation fails
        logger()->error('Invoice generation failed', [
            'order_id' => $this->order->id,
            'error' => $e->getMessage(),
        ]);
    }
}

SerializesModelsトレイトは、Eloquentモデル全体ではなく、モデルの主キーとクラス名のみを保存します。ワーカーがジョブを処理する際に、データベースから最新のモデルを取得します。これにより古いデータの使用を回避し、ペイロードサイズを小さく保ちます。

並列ワークロードのためのジョブバッチング

ジョブバッチングは、複数のジョブを単一のバッチにグループ化し、全体的な進行状況を追跡し、すべてのジョブが完了した時点でコールバックを発火させます。このパターンは、データインポート、一括通知、レポート生成など、最終ステップの前に複数の独立した作業単位を完了させる必要がある場面に適しています。

App/Http/Controllers/ImportController.phpphp
use App\Jobs\ImportRow;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

public function import(Request $request)
{
    $rows = $this->parseCSV($request->file('data'));

    // Create a batch of import jobs, one per CSV row
    $batch = Bus::batch(
        collect($rows)->map(fn ($row) => new ImportRow($row))
    )
    ->then(function (Batch $batch) {
        // All jobs completed successfully
        Notification::send(
            auth()->user(),
            new ImportComplete($batch->totalJobs)
        );
    })
    ->catch(function (Batch $batch, \Throwable $e) {
        // First failure in the batch
        logger()->warning('Batch import partial failure', [
            'batch_id' => $batch->id,
            'failed' => $batch->failedJobs,
        ]);
    })
    ->finally(function (Batch $batch) {
        // Runs after all jobs finish (success or failure)
        Cache::forget("import_lock_{$batch->id}");
    })
    ->allowFailures()
    ->dispatch();

    return response()->json(['batch_id' => $batch->id]);
}

Laravel 12ではバッチペイロードにキュー待機時間やワーカー識別情報を含むメタデータが追加されています。allowFailures()メソッドは、単一のジョブ失敗がバッチ全体をキャンセルするのを防ぎます。これは部分的な成功が許容される大規模インポートにおいて重要です。

順次ワークフローのためのジョブチェイニング

バッチングが並列ワークロードを扱うのに対し、チェイニングは順次実行を保証します。チェイン内の各ジョブは、前のジョブが成功した後にのみ実行されます。いずれかのジョブが失敗すると、残りのチェインは中断され、catchコールバックが発火します。

App/Services/OrderWorkflow.phpphp
use App\Jobs\ValidatePayment;
use App\Jobs\ReserveInventory;
use App\Jobs\SendConfirmation;
use App\Jobs\GenerateShippingLabel;
use Illuminate\Support\Facades\Bus;

public function processOrder(Order $order): void
{
    // Each job runs only after the previous one succeeds
    Bus::chain([
        new ValidatePayment($order),
        new ReserveInventory($order),
        new GenerateShippingLabel($order),
        new SendConfirmation($order),
    ])
    ->onQueue('orders')
    ->catch(function (\Throwable $e) use ($order) {
        // Roll back the order if any step fails
        $order->update(['status' => 'failed']);
        logger()->error('Order chain failed', [
            'order_id' => $order->id,
            'step' => $e->getMessage(),
        ]);
    })
    ->dispatch();
}

チェイニングは、ステップの順序が重要なドメインワークフローに適しています。支払い検証は在庫予約の前に完了する必要があり、配送ラベルは確認された在庫に依存します。

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

横断的関心事のためのキューミドルウェア

キューミドルウェアは、ジョブ実行をレート制限、重複排除、サーキットブレーカーなどの再利用可能なロジックでラップします。これらの関心事をすべてのジョブに埋め込むのではなく、ミドルウェアがジョブをビジネスロジックに集中させます。

App/Jobs/Middleware/RateLimitedJob.phpphp
namespace App\Jobs\Middleware;

use Closure;
use Illuminate\Support\Facades\RateLimiter;

class RateLimitedJob
{
    public function __construct(
        private string $key,
        private int $maxAttempts = 10,
        private int $decaySeconds = 60
    ) {}

    public function handle(object $job, Closure $next): void
    {
        // Release job back to queue if rate limit exceeded
        if (RateLimiter::tooManyAttempts($this->key, $this->maxAttempts)) {
            $job->release($this->decaySeconds);
            return;
        }

        RateLimiter::hit($this->key, $this->decaySeconds);

        $next($job);
    }
}

ジョブクラスにmiddleware()メソッドを定義してミドルウェアを適用します:

App/Jobs/CallExternalApi.phpphp
public function middleware(): array
{
    return [
        new RateLimitedJob(
            key: 'external-api',
            maxAttempts: 30,
            decaySeconds: 60
        ),
        // Prevent duplicate jobs from running concurrently
        (new WithoutOverlapping($this->apiResource->id))
            ->releaseAfter(300)
            ->expireAfter(600),
    ];
}

WithoutOverlappingミドルウェアはアトミックロックを使用して、あるキーで識別されるジョブのインスタンスが一度に1つだけ実行されることを保証します。レート制限と組み合わせることで、重複処理とAPIスロットリングの両方を防止します。

失敗したジョブの処理とリトライ戦略

本番環境のキューシステムには堅牢な障害処理が必要です。Laravelは失敗したジョブをfailed_jobsテーブルに完全なペイロード、例外トレース、障害を生成したキュー/コネクション情報とともに保存します。各ジョブクラスのfailed()メソッドは、すべてのリトライ試行が完了した後に実行されます。

ジョブごとにリトライ動作を設定することで、きめ細かな制御が可能です:

App/Jobs/SyncExternalData.phpphp
class SyncExternalData implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;

    // Exponential backoff: 10s, 30s, 60s, 120s, 300s
    public function backoff(): array
    {
        return [10, 30, 60, 120, 300];
    }

    // Job-specific timeout
    public int $timeout = 180;

    // Maximum exceptions before marking as failed
    public int $maxExceptions = 3;

    public function retryUntil(): \DateTime
    {
        // Keep retrying for up to 24 hours
        return now()->addHours(24);
    }

    public function handle(): void
    {
        $response = Http::timeout(30)
            ->retry(2, 1000)
            ->get('https://api.vendor.com/data');

        if ($response->failed()) {
            // Release back to queue with delay for transient failures
            $this->release(60);
            return;
        }

        DataSync::process($response->json());
    }

    public function failed(\Throwable $e): void
    {
        Notification::route('slack', config('services.slack.ops_channel'))
            ->notify(new SyncFailed($e));
    }
}

面接では$tries$maxExceptionsretryUntil()の違いが重要です。$triesは手動リリースを含むすべての試行をカウントします。$maxExceptionsは未処理の例外のみをカウントします。retryUntil()は試行回数に関係なく時間枠を設定します。

キューワーカーの管理とデプロイ

本番環境のキューワーカーには、プロセス監視、デプロイ時のグレースフルリスタート、リソース管理が必要です。Supervisorがワーカーを稼働させ続けるための標準ツールです。

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

デプロイ時の重要な考慮事項:

  • グレースフルリスタートphp artisan queue:restartはワーカーに現在のジョブを完了してからリスタートするよう信号を送ります。デプロイ中のジョブ破損を防止します。
  • 最大時間/ジョブ数--max-time=3600--max-jobs=1000はワーカープロセスを定期的にリサイクルすることでメモリリークを防止します。
  • スリープ間隔--sleep=3は空のキューを再ポーリングするまでのワーカーの待機時間を制御します。値を小さくすると応答性は向上しますが、データベース/Redisの負荷も増加します。
  • 複数キュー--queue=critical,default,lowは優先順位に従ってキューを処理します。ワーカーはcriticalキューを完全に消化してからdefaultに移ります。

Laravel 12.37ではバックグラウンドキュー接続が導入され、Concurrently::defer()を使用してジョブを遅延実行できます。このドライバーはジョブをシリアライズして別のPHPプロセスで実行します。完全なキューインフラストラクチャを必要としない軽量ジョブに適しています。

ユニークジョブと暗号化ペイロード

本番環境と面接の議論で頻繁に登場する2つのパターンがあります。特定のキーに対してジョブが一度だけ実行されることを保証するパターンと、ジョブペイロード内の機密データを保護するパターンです。

App/Jobs/RebuildSearchIndex.phpphp
use Illuminate\Contracts\Queue\ShouldBeUnique;

class RebuildSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // Lock duration in seconds
    public int $uniqueFor = 3600;

    public function __construct(
        public readonly string $indexName
    ) {}

    // Unique key scopes the lock to this specific index
    public function uniqueId(): string
    {
        return $this->indexName;
    }

    public function handle(): void
    {
        SearchIndex::rebuild($this->indexName);
    }
}

機密データ(ユーザー認証情報、決済トークン)を含むジョブには、ShouldBeEncryptedインターフェースがシリアライズされたペイロード全体を保存時に暗号化します:

App/Jobs/ProcessPayment.phpphp
use Illuminate\Contracts\Queue\ShouldBeEncrypted;

class ProcessPayment implements ShouldQueue, ShouldBeEncrypted
{
    public function __construct(
        private string $paymentToken,
        private float $amount
    ) {}

    public function handle(PaymentGateway $gateway): void
    {
        $gateway->charge($this->paymentToken, $this->amount);
    }
}

ペイロードはRedisやデータベースに保存される前にアプリケーションキーで暗号化されます。ワーカーはデシリアライズ前に自動的に復号します。

Laravelキューに関する面接頻出質問

技術面接では、表面的なAPI知識を超えたキューアーキテクチャの理解が問われます。

キューに入れたジョブが削除済みのEloquentモデルを参照している場合、何が起こるか? SerializesModelsを使用している場合、ワーカーはジョブ処理時にIDでモデルを取得しようとします。モデルが存在しない場合、LaravelはModelNotFoundExceptionをスローします。これをグレースフルに処理するには、$deleteWhenMissingModelsプロパティをtrueに設定します。ジョブは失敗する代わりにキューから静かに削除されます。

ShouldBeUniqueWithoutOverlappingミドルウェアの違いは? ShouldBeUniqueは、同じユニークキーを持つジョブがすでにキューに存在する場合、ジョブのディスパッチを防止します。WithoutOverlappingはディスパッチを許可しますが、同時実行を防止します。同じキーを持つジョブが既に実行中の場合、新しいインスタンスはキューに戻されます。異なる問題を解決するものであり、組み合わせて使用できます。

retryUntil()$triesより好まれる場面は? 回復時間が予測できない外部サービスと連携するジョブにはretryUntil()を使用します。固定のリトライ回数($tries = 3)は短時間の障害中に試行を使い果たす可能性があります。retryUntil()は時間枠(例:24時間)を設定し、サービスが回復するか時間枠が期限切れになるまでバックオフ付きでリトライを続けます。

複数キューでのキュー優先順位の仕組みは? queue:work --queue=critical,default,lowで優先順位システムを構築します。ワーカーはcriticalキューを完全に消化してからdefaultをチェックし、defaultの後にlowをチェックします。ピーク負荷時には低優先度ジョブが飢餓状態になる可能性があります。厳格なSLAが必要な場合は、キューごとに専用ワーカーを配置する方が良い保証を提供します。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

まとめ

  • LaravelキューはRedis、SQS、データベース、およびLaravel 12.37の新しいバックグラウンド接続をサポートするドライバー非依存APIの背後にキューバックエンドを抽象化します
  • ジョブバッチングは集合的な進行状況追跡で並列ワークロードを処理し、チェイニングはドメインワークフローの順次実行を強制します
  • キューミドルウェア(レート制限、WithoutOverlapping)は横断的関心事をジョブのビジネスロジックから分離します
  • 失敗したジョブの処理は$tries$maxExceptionsretryUntil()、指数バックオフを組み合わせた回復力のあるリトライ戦略を実現します
  • ShouldBeUniqueは重複ディスパッチを防止し、ShouldBeEncryptedは保存時の機密ペイロードを保護します
  • 本番ワーカーにはSupervisor、デプロイ時のグレースフルリスタート、--max-time--max-jobsによるメモリ管理が必要です
  • 面接準備では、ディスパッチ時のユニーク性と実行時のロックの区別、モデルシリアライズの動作、キュー優先順位の飢餓問題をカバーする必要があります

Laravel面接質問の実践練習として、SharpSkillの問題集ではキュー、ミドルウェア、Eloquentパターンを詳細な解説付きで提供しています。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#laravel
#queues
#jobs
#async
#interview

共有

関連記事