FrankenPHPとLaravel Octaneを組み合わせると、起動したLaravelアプリケーションを保持し複数のリクエストを処理できます。一方で、リクエスト間の状態やコード変更の反映には注意が必要です。
この記事では、実際に動かして確認した2つの注意点を紹介します。
検証環境は以下です
- PHP 8.5.12
- FrankenPHP 1.13.4
- Laravel 13.39.0
- Laravel Octane 2.22.0
なお今回の注意点はLaravel固有のものではなく、FrankenPHPのworkerモードを利用するPHPアプリケーション全般に共通します。
workerモードとOctaneとは
FrankenPHPのworkerモードでは、起動済みのアプリケーションをworker内に保持し次のリクエストでも再利用します。
Laravel Octaneは、LaravelをFrankenPHPなどのアプリケーションサーバーで動かすための公式パッケージです。Laravel側の状態をリクエスト間でリセットする役割も担いますが、アプリケーション独自の状態がすべて自動でリセットされるわけではありません。
注意点1:リクエスト間で値が残ることがある
worker内に値が残る例を確認します。結果を再現しやすいようにworkerを1つにして起動します。
php artisan octane:frankenphp \
--workers=1 \
--max-requests=500
リクエストで受け取った値をstatic変数の配列に追加する、意図的に問題を起こすサンプルコードを用意しました。
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/demo/before', function (Request $request) {
static $userIds = [];
$userIds[] = $request->query('user_id');
return response()->json(['user_ids' => $userIds]);
});
異なるuser_idで2回リクエストします。
curl 'http://localhost:8000/demo/before?user_id=101'
# {"user_ids":["101"]}
curl 'http://localhost:8000/demo/before?user_id=202'
# {"user_ids":["101","202"]}
2回目は202だけを送ったにもかかわらず、前回の101も返ってきました。static変数の配列が同じworker内に残り、次のリクエストで202が追加されたためです。
staticを外し、リクエストごとに新しい配列を作るよう修正します。
Route::get('/demo/after', function (Request $request) {
$userIds = [];
$userIds[] = $request->query('user_id');
return response()->json(['user_ids' => $userIds]);
});
curl 'http://localhost:8000/demo/after?user_id=101'
# {"user_ids":["101"]}
curl 'http://localhost:8000/demo/after?user_id=202'
# {"user_ids":["202"]}
こちらは、各リクエストで送った値だけが返ってきました。
static変数は、関数の呼び出しが終わっても値を保持します。workerモードでは、同じworkerがルートの処理を再利用するため、次のリクエストでもその値が残ります。
注意点2:コード変更がすぐに反映されないことがある
workerは起動済みのLaravelを保持するため、ファイルを変更しても動作中のworkerには古いコードが残ることがあります。
実際に確認する
Route::get('/demo/version', function () {
return response()->json(['version' => 'before']);
});
php artisan octane:frankenphp \
--workers=1 \
--max-requests=500
--max-requestsとは、各workerが指定回数のリクエストを処理した後に再起動する設定です。Laravel公式ドキュメントではデフォルトは500回で、メモリリーク対策として説明されています。
リクエストすると、beforeが返ります。
curl 'http://localhost:8000/demo/version'
# {"version":"before"}
Octaneを起動したまま、レスポンスをbeforeからafterに変更します。
Route::get('/demo/version', function () {
return response()->json(['version' => 'after']);
});
もう一度リクエストします。
変更後も古い値が返りました。
curl 'http://localhost:8000/demo/version'
# {"version":"before"}
workerをreloadします。
php artisan octane:reload
reload後にリクエストすると、新しいコードが反映されました。
curl 'http://localhost:8000/demo/version'
# {"version":"after"}
Laravel公式も、デプロイ後に新しいコードを読み込ませる方法としてworkerのreloadを案内しています。
開発中の設定
開発中はworkerを1リクエストごとに再起動する設定もいいと思います。
Laravel公式ドキュメントのDockerを使った開発環境の例でも、以下のように設定されています。
entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
公式ドキュメント