Laravel が大量の CPU を消費する理由

最小構成の Laravel「hello world」アプリケーションでも、CPU 時間の大半はアプリケーションコード以外で消費される。OpenResty XRay による Laravel CPU プロファイリングでは、サービスプロバイダの起動と登録が CPU 消費の第 1 位であり、ルートハンドラではないことが判明する。具体的には:

  • #1 最もホットな PHP コードパス(CPU の 36.4%)bootProvider — Laravel は登録済みのすべてのサービスプロバイダ(Carbon 日付ライブラリ、Ignition など)をリクエストごとに起動する。
  • #2 最もホットな PHP コードパス(CPU の 14.5%)register — サービスプロバイダの登録と解決処理(resolveProviderDatabaseServiceProvider::register など)もリクエストごとに実行される。
  • #3 最もホットな PHP コードパス(CPU の 12.8%):実際の「hello world」レスポンス — 自身のコードに使われる CPU はごくわずか。

つまり、Laravel 高 CPU 使用率の典型的なシナリオでは、フレームワークのブートストラップオーバーヘッドが大部分を占める。以下のチュートリアルでは、OpenResty XRay の CPU フレームグラフを使ってこれらのホットパスを特定する方法を詳しく解説する。

PHP アプリケーション全般の高 CPU 問題のトラブルシューティングにも、同様のプロファイリング手法が適用できる。


以下のウォークスルーでは、上記の分析結果がどのように得られたかを、対象 PHP プロセスの選択から PHP レベルの CPU フレームグラフの読み取りまで順を追って解説する。同じ手順を自身の Laravel アプリケーションでも再現できる。

検証環境:CPU 100% の Hello World アプリ

PHP の Laravel フレームワークを使用して、シンプルな「hello world」Web アプリケーションを構築しました。

PHP Laravel フレームワークで構築した hello world Web アプリケーション

ここでは、「Hello, world」というレスポンスを返すリクエストハンドラ関数を定義しています。

Hello, world レスポンスを返す Laravel ルートハンドラ関数

curl コマンドを使用して Laravel の HTTP インターフェースにアクセスします。レスポンスボディは確かに「hello world」です。

curl コマンドで Laravel の HTTP インターフェースにアクセスし hello world を返す様子

top コマンドを実行して、対象の PHP プロセスの CPU 使用状況を確認します。

これは先ほど示した PHP「hello world」サービスプロセスです。

top コマンドで表示した Laravel PHP hello world サービスプロセス

明確な CPU プロファイルを得るため、事前にクライアント側の負荷テストツールでエンドポイントに負荷をかけ、プロセスの CPU 使用率を 100% に飽和させています。

負荷をかけて CPU 使用率 100% に飽和した Laravel PHP プロセスの top 表示

ps コマンドにより、このプロセスが Linux ディストリビューション付属の標準 php バイナリで実行されていることが確認できます。

Laravel アプリが標準 php バイナリで実行されていることを示す ps コマンド

Laravel CPU プロファイリング:3 大ホットコードパス

OpenResty XRay を使用して、PHP プロセス内の CPU 時間の消費状況を確認します。OpenResty XRay の Web コンソールで、対象のマシンを確認し、「Guided Analysis」ページを開き、診断する問題タイプとして「High CPU usage」を選択します。

OpenResty XRay のガイド付き分析で High CPU usage 問題タイプを選択

PHP アプリケーションを選択し、CPU をほぼ 100% 消費しているワーカープロセスを選びます。先ほど top で確認したプロセスと同じもの(ここでは PID 2092、CPU 93%)です。

CPU を 93% 消費する PHP ワーカープロセス PID 2092 を分析対象に選択

OpenResty XRay はアプリケーションタイプを自動検出し、複数の言語レベルを同時に分析できるため、PHP と C/C++ の両方を選択したまま、最大分析時間はデフォルトの 300 秒のままにします。開始後、C 層と PHP 層の CPU フレームグラフを交互にサンプリングしながら複数ラウンドの分析が実行されます。本例では 2 ラウンドで十分なため、ここで分析を停止します。

言語レベルを PHP と C/C++ に設定し最大分析時間 300 秒

OpenResty XRay が自動的に分析レポートを生成します。

OpenResty XRay が自動生成した Laravel CPU 分析レポート

レポートには、診断した問題タイプ「CPU」がタグ付けされています。

問題タイプ CPU がタグ付けされた分析レポート

これは CPU リソースを最も消費している C コードパス(#1)で、CPU 時間の 96.2% を占めています。

CPU 時間の 96.2% を占める最もホットな C コードパス

最初の zend_execute 関数は PHP オペコードの解釈と実行に使用されます。

PHP オペコードを解釈・実行する zend_execute 関数のフレームグラフ

サーバーが新しい HTTP リクエストを受信すると、php_cli_server_dispatch_router 関数が呼び出されてリクエストデータの読み取りと解析を行います。

HTTP リクエストを読み取り解析する php_cli_server_dispatch_router 関数

main 関数フレームから、本デモが PHP CLI 起動の組み込み Web サーバーで実行されていることがわかる。本番環境の php-fpm ではこの C レベルのサーバーディスパッチフレームが異なるが、以下の Laravel フレームワーク層の PHP ホットスポットこそが注目すべき対象である。

PHP CLI 組み込み Web サーバーを示す main 関数フレーム

エントリを展開すると、_start プロセスエントリポイントからサーバーのイベントループを経て PHP オペコードインタプリタに至る完全な C 層コールパスが表示される。

_start から PHP オペコードインタプリタに至る完全な C 層コールパス

このホットコードパスは、C 言語レベルの CPU フレームグラフから自動的に導き出されたものです。

OpenResty XRay が生成した C 層 CPU フレームグラフ

以下は現在の問題に関するより詳細な説明と提案です。先ほど見た zend_execute 関数について言及しています。

zend_execute 関数に言及するレポートの解説

次に PHP レベルの結果を見ます。#1 最もホットな PHP コードパスは、それだけで CPU 時間の 36.4% を消費しています。

CPU 時間の 36.4% を占める最もホットな PHP コードパス

bootProvider 関数は Laravel フレームワークの一部で、アプリケーションに登録されたサービスプロバイダを起動する役割を担っています。

登録済みサービスプロバイダを起動する Laravel bootProvider 関数

完全なパスでは、リクエストが public/index.php および HTTP カーネルを通って入り、array_walk を介してすべての登録済みプロバイダのサービスプロバイダ起動に展開されます。

public/index.php から HTTP カーネルを経てサービスプロバイダ起動に至るコールパス

レポートにはこのコードパスの自動生成された解説も含まれています。bootProvider はアプリケーションに登録された各サービスプロバイダを起動し、サービスプロバイダは Laravel アプリケーションの設定が行われる中心的な場所です。

bootProvider によるサービスプロバイダ起動パスの自動生成解説

PHP レベルの CPU フレームグラフを拡大すると、bootProvider フレームとその下で起動されている個々のサービスプロバイダが確認できます。

bootProvider フレームと配下のサービスプロバイダを拡大した PHP 層 CPU フレームグラフ

Laravel の ServiceProvider プログラムにおいて、ServiceProvider::boot メソッドは Carbon 日付ライブラリのマクロ登録と設定に使用されます。対応する boot メソッドはタイムゾーンなどの設定の初期化に使用されます。

Carbon 日付ライブラリのタイムゾーン設定を初期化する ServiceProvider boot メソッド

IgnitionServiceProvider::boot メソッドはすべてのサービスプロバイダの起動を担当しています。

すべてのサービスプロバイダを起動する IgnitionServiceProvider boot メソッド

次に #2 最もホットな PHP コードパスを見ます。これは CPU 時間の 14.5% を消費しています。

CPU 時間の 14.5% を占める 2 番目にホットな PHP コードパス

この register 関数はアプリケーションのブートストラップ中に実行され、サービスプロバイダの解決と登録を行います。Laravel はリクエストごとに新しいアプリケーションインスタンスを作成するため(Laravel Octane のような常駐型セットアップを使用しない限り)、このコストは繰り返し発生します。

ブートストラップ中にサービスプロバイダを解決・登録する Laravel register 関数

完全なパスは上記の boot パスと同様で、リクエストが public/index.php および HTTP カーネルを通って入り、registerConfiguredProviders へと下降します。

public/index.php から registerConfiguredProviders へ下降するコールパス

自動生成された解説では、index.php のアプリケーションエントリポイントから始まる同じ一連の呼び出しが分解されています。

index.php エントリポイントから始まる register パスの自動生成解説

拡大した PHP レベルの CPU フレームグラフでは、周囲のブートストラップフレームの中で Application::register フレームが強調表示されています。

PHP 層 CPU フレームグラフで強調表示された Application::register フレーム

resolveProvider は Laravel フレームワーク内のメソッドで、サービスプロバイダの解決と登録に使用されます。

サービスプロバイダを解決・登録する Laravel resolveProvider メソッド

DatabaseServiceProvider::register メソッドは Laravel においてデータベースサービスとその関連コンポーネントをサービスコンテナに登録する役割を担っています。

Laravel データベースサービスを登録する DatabaseServiceProvider register メソッド

3 番目にホットなコードパスは CPU 時間の 12.8% を消費しています。

CPU 時間の 12.8% を占める 3 番目にホットな PHP コードパス

そのコールチェーンは Laravel のルーティングパイプライン(Router::dispatch、ミドルウェアスタック、ControllerDispatcher)を経てコントローラに到達します。

Router::dispatch、ミドルウェアスタック、ControllerDispatcher を経るコールチェーン

このパスが実際の「hello world」レスポンスを実装しています。自身のハンドラコードに加え、その周囲のルーティングおよびレスポンス機構も含まれています。

Laravel hello world レスポンスを実装するコードパス

#1 と #2 のパスはどちらも同じリクエスト単位のブートストラップに属する。一方がサービスプロバイダを起動し、もう一方が登録を行い、合計で CPU 時間の半分以上を占めている——アプリケーションロジックが実行される前に。

サービスプロバイダの起動と登録パスが合計で CPU 時間の半分以上を占める様子

参考として、この Laravel「hello world」アプリと同等の OpenResty ハンドラのスループット比較を示します。371 リクエスト/秒 対 28,000 リクエスト/秒で、およそ 75 倍の差があります。Laravel はフルスタックフレームワークであるのに対し、OpenResty はリクエストあたりの抽象化がはるかに少ないため、単純な比較ではありませんが、上記で計測したフレームワークオーバーヘッドが実際にどれほどのコストになるかを示しています。Laravel アプリケーションでメモリ消費量の問題もある場合は、OpenResty XRay で同様にプロファイリングできます。

スループット比較:Laravel 371 リクエスト/秒 対 OpenResty 28,000 リクエスト/秒

自動 CPU 使用率分析とレポート

OpenResty XRay は手動操作なしでオンラインプロセスを自動的に監視することもできます。「Insights」ページでは、各アプリケーションの日次・週次分析レポート(上記と同じ CPU 分析結果とホットコードパスの内訳)が収集されるため、日常運用では「Guided Analysis」を実行する必要はありません。ガイド付き分析は、開発時やオンデマンドの詳細分析(本チュートリアルのような場面)で引き続き有用です。

Insights ページに表示される Laravel CPU の自動日次・週次レポート

Laravel 高 CPU 使用率に関するよくある質問

Laravel のサービスプロバイダはリクエストごとに実行されますか?

はい。標準的な Laravel 構成では、リクエストごとに新しいアプリケーションインスタンスが生成されるため、サービスプロバイダは毎回登録・起動されます。上記のプロファイルでは、register(CPU の 14.5%)と bootProvider(CPU の 36.4%)がリクエストごとに実行され、合計で CPU の半分以上を占めています。しかもこれはルートのロジックが実行される前の話です。このリクエスト単位のブートストラップこそ、Laravel 高 CPU 使用率シナリオにおける支配的なコストです。

Laravel Octane はこの CPU オーバーヘッドを削減できますか?

ここで計測された高コストは、サービスプロバイダの登録と起動をリクエストごとに繰り返すことに由来します。Laravel Octane のような常駐型の構成では、アプリケーションをリクエストごとに再構築せずメモリ上に常駐させるため、このブートストラップ処理が繰り返し実行されることはありません。Laravel 高 CPU 使用率が registerbootProvider のパスに支配されている場合、まさにこの種の構成が削減対象とするオーバーヘッドです。

Laravel アプリで最もホットなコードパスを特定するには?

OpenResty XRay のガイド付き分析で実行中の PHP プロセスをプロファイリングします。C 層(zend_execute と PHP 仮想マシン)と PHP 層(Laravel フレームワーク関数)の両方で CPU フレームグラフが生成され、最もホットなパスが CPU 時間の割合順に並びます——本例では bootProvider(36.4%)、register(14.5%)、ルートレスポンス(12.8%)です。これにより、どのフレームワーク関数が CPU を支配しているかを推測せずに特定できます。

hello world レスポンスにおいて OpenResty は Laravel よりどれだけ速いですか?

上記のスループット比較では、この Laravel「hello world」アプリは 371 リクエスト/秒だったのに対し、同等の OpenResty ハンドラは約 28,000 リクエスト/秒で、およそ 75 倍の差がありました。Laravel はフルスタックフレームワークであるのに対し、OpenResty はリクエストあたりの抽象化がはるかに少ないため単純な比較ではありませんが、フレームワークのブートストラップオーバーヘッドが実際にどれほどのコストになるかを示しています。

OpenResty XRay について

OpenResty XRay動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、動作の問題、セキュリティの脆弱性を診断し、実行可能な提案を提供します。基盤となる実装において、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、様々な環境下で複数の異なるランタイムをサポートしています。

本チュートリアルがお役に立ちましたら、本ブログおよび YouTube チャンネルのご登録をお願いいたします。

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵入型の障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。心より感謝申し上げます!