Laravel OctaneをFrankenPHPで導入したら、APIのレスポンス速度が別次元になった話

Laravelの標準PHP-FPM構成にLaravel Octane(FrankenPHP)を導入したところ、毎リクエストの再起動コストがなくなり、APIの応答速度がかなり改善しました。

🙌 結論から

社内向けのAPIサーバーに、Laravel Octane を FrankenPHP ドライバで導入してみました。

毎リクエストごとにフレームワークを起動し直すコストがなくなり、レスポンスタイムがかなり短くなりました!

導入前は「Swooleとかメモリリークが怖そう…」と敬遠していたのですが、FrankenPHPはWebサーバーごと1バイナリにまとまっていて、思ったより導入がラクだったのが意外でした(・∀・)

本番投入前にステージング環境で1週間ほど負荷試験をしてから切り替えたので、そのあたりの手順も含めて書いておきます。

💡 PHP-FPM構成の何が遅いのか

通常のPHP-FPM構成では、リクエストが来るたびにLaravelのカーネルをブートストラップし、サービスプロバイダを毎回読み込み直します。

処理自体は軽くても、この「毎回作り直す」コストが積み重なると、特にAPIのように高頻度でリクエストが来るエンドポイントではかなりのオーバーヘッドになります。

Octaneは、アプリケーションを一度だけ起動してメモリに常駐させ、そこにリクエストを流し込む方式に変えることで、この起動コストをまるごと省いてくれます。

👀 FrankenPHPで導入してみた

インストールはComposer経由です。

composer require laravel/octane
php artisan octane:install --server=frankenphp

FrankenPHPを選んだ理由は、Webサーバー(Caddy相当)とTLS終端が同じバイナリに含まれていて、コンテナのプロセス数を1つに減らせるからです。

php artisan octane:start --server=frankenphp --workers=8

--workers はCPUコア数を見ながら調整しました。今回は4コアの環境だったので、まずは8で様子見しています。

デプロイ時はDockerfileにFrankenPHPのバイナリを組み込むだけで、既存のNginx + PHP-FPM構成より設定ファイルがシンプルになりました。

✨ 使ってみた感想

一番わかりやすかったのは、P95レスポンスタイムの改善です。

ステージング環境でのベンチマークでは、同じエンドポイントでもPHP-FPM構成と比べて数倍のスループットが出ていて、ちょっと感動しました🔥

APIのように起動コストの比率が大きい処理ほど恩恵が大きく、逆に元々重いクエリを叩いているエンドポイントは、そこまで劇的には変わらなかったです。

「Octaneを入れれば何でも速くなる」わけではなく、ボトルネックが起動コスト側にあるかどうかを見極めるのが大事だなと感じました。

🐛 ハマったメモリリークっぽい挙動

一番注意したのが、アプリケーションがメモリに常駐する構成ゆえの「状態の使い回し」問題です。

静的プロパティやシングルトンにリクエストごとの値を溜め込むコードが一部に残っていて、それが原因でリクエストをまたいでデータが混ざるバグが1つ見つかりました(´;ω;`)

Octane用に用意されている octane:reload コマンドで定期リロードする運用でも回避できますが、根本的にはリクエストスコープの状態管理を見直すのが本筋だと思います。

php artisan octane:status で稼働状況を確認しながら、負荷試験中はログを注視して潰していきました。

🔁 さらに踏み込んで調整したところ

状態管理を見直すのが本筋とはいえ、正直すべてのメモリリークの芽を1週間の負荷試験だけで潰しきれる自信はありませんでした。

そこで、Octaneには--max-requestsというオプションがあることを知り、これも併用することにしました。

php artisan octane:start --server=frankenphp --workers=8 --max-requests=500

ワーカーが500リクエストを処理したタイミングで自動的に再起動してくれるので、じわじわ溜まったメモリや、拾いきれなかった状態汚染をリクエスト単位ではなくワーカー単位で強制的にリセットできます。

最初は--max-requestsを使うのは「原因を潰しきれていないことの言い訳」みたいで気が引けたんですが、実際は本家のドキュメントでも推奨されている運用で、保険として入れておくのが素直だと今は思っています。

数値は最初1000で試して、それでもメモリ使用量のグラフが右肩上がりのままだったので、500まで下げて様子を見ています。

ワーカー数×リクエスト上限のバランスは、アプリの重さによって結構変わる印象です。

自分のチームだったら、と考えると、社内向けAPIのような影響範囲が読める用途からOctaneを試すのがやはり正解だと思っています。

決済など一発のミスが大きい経路は、状態管理の見直しが完全に終わってから最後に乗せる、くらいの慎重さで進めたいです。

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

👍 まとめ

Laravel Octane を FrankenPHP で導入したところ、APIのレスポンス速度がPHP-FPM構成よりかなり改善しました。

一方で、常駐プロセスならではの状態管理の見直しは必須で、ここをサボると原因不明のバグに繋がります。

高頻度アクセスのAPIサーバーを持っていて、起動コストがボトルネックになっていそうな方は、一度ステージング環境で試してみる価値があると思います!

※ Octaneのドライバ選定(Swoole・RoadRunner・FrankenPHP)はワークロードによって向き不向きがあるので、公式ドキュメントで最新の推奨構成を確認してください👀