ウェブから時間がかかる作業、例えばアップロードした画像の整形などを行うときは、ジョブを作成してキュー使って非同期で実行するのが通常。しかし、Laravel 12.xからジョブをキューを使わずに即バックグラウンドで処理することが可能となりました。今回はその紹介です。

準備

まず使用するジョブを、Backgroundと命名して作成します。

namespace App\Jobs;

use Illuminate\Support\Facades\Log;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;

class Background implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        Log::info("バックグラウンドのジョブを作成");
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('バックグラウンドのジョブの処理の開始');

        sleep(10);

        Log::info('バックグラウンドのジョブの処理の終了');
    }
}

このジョブの仕事は見ての通り、10秒間スリープすることですが、実際には代わりに画像の整形などの作業がプログラムされます。

それから、以下config/queue.phpにおいて以下の接続が入っていることを確認してください。

...
    'connections' => [
...
        'deferred' => [
            'driver' => 'deferred',
        ],

        'background' => [
            'driver' => 'background',
        ],

        'failover' => [
            'driver' => 'failover',
            'connections' => [
                'database',
                'deferred',
            ],
        ],

    ],
...

ウェブと同じプロセスで同期実行(deferred)

同期実行と書くと、実行が完了するまで待たされること(上のジョブでは10秒のスリープ)を想像しますが、そうではなくウェブのレスポンスをユーザーに即座に返してからジョブが実行されます。つまり、画面で10秒待たされることはありません。

通常のジョブの発信は.envで、QUEUE_CONNECTION=sqsとかで指定したキューに以下のように発信します。

Background::dispatch();

しかし、deferredはdefaultのキューの接続を使わないので、onConnection()でdeferredを指定発信します。

Background::dispatch()->onConnection('deferred');

コントローラではこんな感じでコールします。

...
use App\Jobs\Background;
...

final class BackgroundController extends Controller
{
    public function create(): View
    {
        ...
        return view('create');
    }

    public function store(Request $request): RedirectResponse
    {
         Background::dispatch()->onConnection('deferred'); // ジョブを発信

         return redirect(route('background.create'));
    }
}

ウェブと違うプロセスで同期実行(background)

backgroundを使用したジョブの発信は、引数がbackgroundとなるだけです。

Background::dispatch()->onConnection('background');

deferredとの違いは、ジョブの処理はジョブを発信したウェブとはまったく違うプロセスで実行されます。ジョブを発信後に、Unixで馴染みなpsコマンドでプロセスをモニターしていると、以下のようにartisanのコマンド実行プロセスが新たに出てきます。

www-data 14961   340  4 19:49 ?        00:00:00 /usr/bin/php artisan invoke-serialized-closure

あたかも、ウェブからコマンドラインでコマンドを実行しているようです。

deferred vs background

さて、キューを使わないでバックグラウンドでジョブを処理できる方法を2つ紹介しましたが、どう使い分けたらいいのでしょうか。

2つの決定的な違いは、deferredではプログラムの実行時間がphpのmax_execution_timeの設定値に限られることです。
プログラムの実行の40秒かかるとしてmax_execution_time=30としたら、実行開始してから30秒後に「Maximum execution time of 30 seconds exceeded」とようなエラーとなり処理が途中停止となってしまいます。

一方backgroundはあたかもコマンドライン(CLI)でコマンドを実行するように、そのような時間制限はありません。

必然的に、deferredは短めの作業の実行となり、backgroundは長めの作業に適するということになります。

この違いを実際にテストするには、先のBackgroundジョブを以下のように書き換えてdeferedbackgroundの両方で発信してみてください。
sleep()がここで使われない理由は、スリープをいくら長くしてもはmax_execution_timeで指定する時間制限にカウントされないからです。

...
class Background implements ShouldQueue
{
    use Queueable;
...
    public function handle(): void
    {
        Log::info('バックグラウンドのジョブ処理の開始');
        $startTime = time();
        $lastLogTime = $startTime;

        while (true) {
            if (time() - $lastLogTime >= 10) {
                $secondsPassed = time() - $startTime;
                Log::info("実行中... ({$secondsPassed} 秒経過)");
                $lastLogTime = time();
            }

            if (time() - $startTime > 40) {
                break;
            }
        }

        Log::info('バックグラウンドのジョブ処理の終了');
    }
}
メルマガ購読の申し込みはこちらから。

By khino