🦎

Laravel v11.23 で同時実行(Concurrency)が使えるようになったので試してみた

2024/09/14に公開

Laravel v11.23 がリリースされ、新たに Concurrency(同時実行性) 機能が導入されました。
これにより、アプリケーション内で複数のタスクを簡単に同時実行できるようになりました。今回は、この Concurrency 機能を実際に触ってみた感想をまとめてみました。

同時タスクの実行

Concurrency::run() メソッドを使用すると、複数のクロージャを同時に実行できます。

Metricsサービスクラス
App/Service/Metrics.php
<?php

namespace App\Service;

use function Illuminate\Log\{log};
use Illuminate\Support\Number;
use Illuminate\Support\Sleep;

class Metrics
{
    /**
     * メトリクスを記録する関数
     */
    public static function report(): void
    {
        $metric = Sleep::for(1)->second()
            ->then(fn() => Number::currency(random_int(1, 100_000)));

        log($metric);
    }

    /**
     * メトリクスを取得する関数
     *
     * @return string
     */
    public static function get(): string
    {
        $metric = Sleep::for(1)->second()
            ->then(fn() => Number::currency(random_int(1, 100_000)));

        return $metric;
    }
}

routes/web.php に以下のルートを追加します。

<?php

use App\Service\Metrics;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Concurrency;

Route::get('/concurrency', function () {
    // 個別に順番に実行した場合(合計約5.23秒)
    // $values[] = Metrics::get();
    // $values[] = Metrics::get();
    // $values[] = Metrics::get();
    // $values[] = Metrics::get();
    // $values[] = Metrics::get();

    // Concurrency::run() を使用して同時に実行(約1.66秒)
    $values[] = Concurrency::run([
        fn() => Metrics::get(),
        fn() => Metrics::get(),
        fn() => Metrics::get(),
        fn() => Metrics::get(),
        fn() => Metrics::get(),
    ]);

    return view('time', [$values]);
});

このコードでは、Metrics::get() メソッドを5回呼び出しています。コメントアウトされた部分は、個別に順番に実行した場合で、合計約5.23秒かかります。一方、Concurrency::run() を使用すると、これらのタスクを同時に実行でき、約1.66秒で完了します。

同時タスクの延期

Concurrency::defer() を使うことで、クロージャの配列をバックグラウンドで実行できます。
これらのタスクは、HTTP レスポンスがユーザーのブラウザに送信された後 に実行されます。

<?php

use App\Service\Metrics;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Concurrency;

Route::get('/concurrency/2', function () {
    Concurrency::defer([
        fn() => Metrics::report(),
        fn() => Metrics::report(),
        fn() => Metrics::report(),
        fn() => Metrics::report(),
        fn() => Metrics::report(),
    ])->always();

    return view('time');
});

リクエストが失敗した場合の挙動

デフォルトでは、Concurrency::defer() に登録されたタスクは、リクエストが成功した場合にのみ 実行されます。つまり、リクエスト中に例外が発生したり、エラーでレスポンスが返された場合、defer されたタスクは実行されません。

リクエストが失敗した場合でも defer したタスクを実行したい場合は、always() メソッドをチェーンすることで、リクエストの成否に関わらず、defer されたタスクが実行されます。

Concurrency のドライバーの種類

タスクを実行するためのドライバーがいくつか用意されています。

  • process(デフォルトドライバー)
  • fork
  • sync

ドライバーの変更は、config/concurrency.php ファイルの default オプションを更新するか、メソッドチェーンでドライバーを指定できます。

$results = Concurrency::driver('fork')->run([...]);

まとめ

Laravel v11.23 で導入されたConcurrency機能は、パフォーマンスの向上にかなり役立ちそうだと思いました。
簡単に同時実行ができるようになり、個人的には導入されて嬉しい機能です。

参考文献:
https://laravel.com/docs/11.x/concurrency
https://www.youtube.com/watch?v=AwWepVU5uWM&t=2041s
https://laraveleco.com/defer-function-in-laravel/

Discussion