Laravel にはバックグラウンドで自動で処理を実行するためのキューという仕組みがあります。キューはデータベースなどどこかしらに実行すべき処理を示すジョブが貯められ、その処理を実行するプロセスであるワーカーがジョブを順次実行していきます。
キュー 6.x Laravel
このワーカーは一度に一つのジョブしか実行しません。複数のジョブを実行するためには複数のワーカーを立ち上げる必要があります。一方でワーカーはジョブが貯まっているかを監視します。ワーカーが多いほど何かしらのI/O(入出力)処理が増えます。特に監視間隔が短い場合は無視できません。必要な時に必要なだけワーカーを走らせる仕組みが欲しくなります。
必要な時に必要なだけワーカーを走らせる仕組みを実現したライブラリはいくつかあります。例えば、laravel-async-queueです。
barryvdh/laravel-async-queue: Laravel Async Queue Driver
このライブラリは上述の必要な時に必要なだけワーカーを走らせる仕組みを提供してくれます。
この記事ではこの必要な時に必要なだけワーカーを走らせる仕組みの根本を紹介します。
作るのはジョブを監視し、ワーカーを別プロセスとして走らせるキューマネージャ―ともいうべきコマンドです。ジョブを監視するプロセスはキューマネージャー唯一つのためI/Oを気にする必要はありません。行うべきジョブをキューマネージャーが発見した場合、都度別プロセスでワーカーが走るため常に複数のジョブ(何らかのすべき処理を行うプロセス)を実行できます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Eloquents\Job;
use Illuminate\Support\Collection;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Process\Process;
class AsyncJobWorkerManager extends BaseCommand
{
protected $name = 'queue:async-worker';
protected $description = '非同期で queue:work を走らせる';
protected Collection $runningWorkers;
protected function getOptions()
{
return [
['max', 'm', InputOption::VALUE_OPTIONAL, '同時に走る worker の最大数']
];
}
public function handle(): void
{
// 今動作しているワーカーを保持しておく Collection
$this->runningWorkers = new Collection([]);
// 同時に走らせるワーカーの最大数
$maxWorkerCount = +$this->option('max') ?: 100;
while (true) {
// 走っているワーカーだけを保持する(止まったワーカーは捨てる)
$this->runningWorkers = $this->runningWorkers
->filter(static fn(Process $worker) => $worker->isRunning());
// 現在実行中のワーカー数が同時に走らせるワーカー数を超えておらず, 未試行の Job を見つけたら実行
if ($this->runningWorkers->count() < $maxWorkerCount && Job::whereAttempts(0)->count() > 0) {
// 一つのジョブを行ったら消えるワーカーコマンドを非同期実行
$worker = new Process(['php', 'artisan', 'queue:work', 'database', '--once']);
$worker->setTimeout(null);// タイムアウトなし
$worker->start(fn($msg) => \Log::info($msg));// ワーカー内での出力はログファイル送り
// 保持するワーカーに新たなワーカーを追加
$this->runningWorkers->push($worker);
}
// 処理を休憩(秒)
sleep(5);
}
}
}
これで監視用プロセスを一つに絞ったまま非同期でワーカーを実行できます。このコードを元に色々と拡張することで要件にあった処理を実現できます。例えば上記コードは 5 秒に 1 つのワーカーしか走らせないコードです。場合によっては 1 ループで複数ワーカーを走らせたり、監視間隔を短くする必要があるかもしれません。サブプロセスとして php コマンドを動かしていますが docker run をかませる必要がるかもしれません。