PHP アプリケーションの並列性の最適化
最終更新日 2024年03月28日(木)
Table of Contents
Heroku 上の PHP アプリケーションは PHP-FPM FastCGI Process Manager の下で動作し、FastCGI プロトコルを使用して Apache または Nginx Web サーバーと通信します。
PHP-FPM は、実際の PHP アプリケーションコードを実行する子プロセスを生成して管理します。これらの各プロセスは、Web サーバーからのリクエストを一度に 1 つずつ処理します。プロセスが増えると並列性が向上し、トラフィックが多い状況でもアプリケーションのパフォーマンスが向上します。
Heroku では、PHP-FPM は static
プロセス管理モードを使用し、固定された数の子プロセスを生成します。この設定は、dyno インスタンスが完全に分離され、RAM 割り当てが固定されている、Heroku のような環境に最適です。
設定された PHP のメモリ制限は、アプリケーションを実行する各子プロセスに適用され、終了する前にその制限までメモリを消費できます。メモリ制限の設定は、Heroku で PHP アプリケーションの並列性を調整する主な方法です。
Premier または Signature Success Plan の Heroku Enterprise の顧客は、Customer Solutions Architecture (CSA) チームに、このトピックに関する詳細なガイダンスを要求できます。ここでエキスパートコーチングセッションについて学習するか、または Salesforce の担当者にお問い合わせください。
デフォルト設定と動作
アプリケーションで複数の buildpack が使用されている場合、アプリケーションの第一言語の buildpack である PHP buildpack が他の言語の buildpack の後に実行されるようにしてください。そうしないと、他の buildpack の WEB_CONCURRENCY
のデフォルト値が、PHP buildpack によって以前に設定された値を上書きする可能性があります。
生成される子プロセスの数は、PHP-FPM の pm.max_children
設定によって制御されます。この設定は、Heroku の PHP buildpack が自動的に適切な値に設定する WEB_CONCURRENCY
環境変数を使用して決定されます。
アプリケーションを起動すると、dyno タイプが自動的に検出され、WEB_CONCURRENCY
環境変数が適切なデフォルト値に設定されます。
$ heroku ps:scale web=1:standard-2x
$ heroku logs
2024-02-06T14:52:40… heroku[web.1]: State changed from down to starting
2024-02-06T14:52:42… heroku[web.1]: Starting process with command `heroku-php-apache2`
2024-02-06T14:52:43… app[web.1]: Available RAM is 1G Bytes
2024-02-06T14:52:43… app[web.1]: PHP memory_limit is 128M Bytes
2024-02-06T14:52:43… app[web.1]: Starting php-fpm with 8 workers...
2024-02-06T14:52:43… app[web.1]: Starting httpd...
2024-02-06T14:52:44… heroku[web.1]: State changed from starting to up
dyno タイプ別の WEB_CONCURRENCY のデフォルト
現在サポートされているすべてのバージョンの PHP で、memory_limit
のデフォルト値は 128M
ですが、異なる値を設定することもできます。この表は、Heroku が 3 つの異なる PHP memory_limit
値でさまざまな dyno タイプに設定する WEB_CONCURRENCY
のデフォルトの概要を示しています。
dyno タイプ | Dyno RAM | CPU コア | WEB_CONCURRENCY for memory_limit of… |
||
---|---|---|---|---|---|
64M |
128M 1 |
256M |
|||
Eco Basic Standard-1X |
512 MB | 共有 | 8 | 4 | 2 |
Standard-2X | 1 GB | 共有 | 16 | 8 | 4 |
Private-S Shield-S |
1 GB | 2 | 16 | 8 | 4 |
Performance-M Private-M Shield-M |
2.5 GB | 2 | 40 | 20 | 10 |
Performance-L Private-L Shield-L |
14 GB | 8 | 224 962 |
112 482 |
56 242 |
Performance-L-RAM Private-L-RAM Shield-L-RAM |
30 GB | 4 | 128 | 64 | 32 |
Performance-XL Private-XL Shield-XL |
62 GB | 8 | 288 | 144 | 72 |
Performance-2XL Private-2XL Shield-2XL |
126 GB | 16 | 640 | 320 | 160 |
1: 現在サポートされているすべてのバージョンの PHP で、memory_limit のデフォルト値は 128M です。2: Performance-L dyno では、下位互換性のため、 WEB_CONCURRENCY のデフォルト値は PHP バージョン 7.4 より前の低い値に設定されます。
|
下位互換性を確保するため、performance-l
dyno タイプのデフォルト値では、7.4 より前の PHP バージョンで使用可能な全メモリ量は使用されません。Heroku が自動的に割り当てるよりも多くのプロセスを使用する場合は、「手動での並列性のチューニング」を参照してください。
これらのデフォルト値は、PHP-FPM 親プロセスまたは Web サーバープロセスに「余裕」を残さないように意図的に選択されています。アプリケーションが各リクエストでメモリ制限全体を消費し、完全な飽和状態になる可能性は低いため、dyno はデフォルトでわずかにオーバーサブスクライブされます。
WEB_CONCURRENCY のデフォルト計算
使用可能な RAM を超えず、使用可能な CPU コアあたりの PHP-FPM ワーカープロセスの生成が多すぎない WEB_CONCURRENCY
値を計算するために、Heroku は次の値を求めます。
WEB_CONCURRENCY
の RAM ベースの制限を PHPmemory_limit
と使用可能な RAM を使用して求めます (RAM ベースの制限)WEB_CONCURRENCY
の CPU ベースの制限 を PHPmemory_limit
、使用可能な RAM、CPU コア数を含む対数スケーリング係数を使用して求めます
Heroku は、この 2 つの制限値のうち低い方を WEB_CONCURRENCY
の値として使用します。いずれの場合も、設定された PHP memory_limit
は自動的に決定されます。
Heroku のデフォルトの memory_limit
は、それぞれの PHP バージョンのデフォルトです。現在、PHP のすべてのバージョンの制限は 128 MB です。
CPU ベースの制限が RAM ベースの制限を上書きする場合、起動時に次のメッセージが表示されます。
$ heroku ps:scale web=1:performance-xl
$ heroku logs
2024-02-06T14:52:40… heroku[web.1]: State changed from down to starting
2024-02-06T14:52:42… heroku[web.1]: Starting process with command `heroku-php-apache2`
2024-02-06T14:52:43… app[web.1]: Available RAM is 62G Bytes
2024-02-06T14:52:43… app[web.1]: PHP memory_limit is 128M Bytes
2024-02-06T14:52:43… app[web.1]: Maximum number of workers that fit available RAM at memory_limit is 496
2024-02-06T14:52:43… app[web.1]: Limiting number of workers to 144
2024-02-06T14:52:43… app[web.1]: Starting php-fpm with 144 workers...
2024-02-06T14:52:43… app[web.1]: Starting httpd...
2024-02-06T14:52:44… heroku[web.1]: State changed from starting to up
WEB_CONCURRENCY
に使用する計算に関係なく、PHP memory_limit
と PHP-FPM 子プロセス数には線形の相関関係があります。たとえば、上記の表に示すように、memory_limit
値を半分にすると WEB_CONCURRENCY
の結果は 2 倍になり、その逆も同様です。
memory_limit
を使用した並列性のチューニング
メモリ制限の設定は、Heroku で PHP アプリケーションの並列性を調整する主な方法です。この表を参照して、各 dyno タイプとメモリ制限の設定ごとに子プロセスの数がどのように変化するかを確認してください。詳細は、「適切なメモリ制限の決定」を参照してください。
PHP-FPM の memory_limit
の設定
.user.ini
を使用したメモリ制限の設定
メモリ制限の設定を含む .user.ini
設定ファイルを、アプリケーションのドキュメントルート (通常はアプリケーションのトップレベルディレクトリ) に追加できます。たとえば、アプリケーションのメモリ制限を 64 MB に設定するには、.user.ini
で次のように設定します。
memory_limit = 64M
M
サフィックスを使用してメガバイトを表すには、PHP に必要な正しい省略表記を使用する必要があります。
Procfile
コマンド引数を使用して設定している場合、アプリケーションのドキュメントルートはアプリケーションのトップレベルディレクトリとは異なる可能性があります。
このファイルを使用してアプリをデプロイすると、指定されたメモリ制限に合わせてワーカーの数が自動的に調整されることがわかります。たとえば、Standard-1X dyno の場合は次のようになります。
$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2`
2019-01-15T07:51:33.188816+00:00 app[web.1]: Optimizing defaults for 1X dyno...
2019-01-15T07:51:33.370674+00:00 app[web.1]: 8 processes at 64MB memory limit.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up
ドキュメントルートのサブディレクトリにある追加の .user.ini
ファイルは、dyno の起動時に Heroku がメモリ制限を決定する際に評価されません。これらのファイルは、ディレクトリにある PHP ファイルへのリクエストを処理するときに、記載されているように実行時に有効になります。まれに、アプリケーションのサブディレクトリごとに memory_limit
設定が異なる場合は、この点に注意してください。サブディレクトリにも同じ設定を使用することをお勧めします。
PHP-FPM 設定を使用したメモリ制限の設定
.user.ini
ファイルの代わりに、PHP-FPM 設定インクルードを使用して php_value
または php_admin_value
ディレクティブを追加し、memory_limit
設定を変更することもできます。たとえば、アプリケーションのメモリ制限を 64 MB に設定するには、fpm_custom.conf
という名前のファイルを作成します。
php_value[memory_limit] = 64M
これらの設定を有効にするには、Procfile
コマンドで -F
オプションを使用して設定をロードする必要があります。
web: heroku-php-apache2 -F fpm_custom.conf
新しい fpm_custom.conf
と変更された Procfile
を使用してアプリをデプロイすると、指定されたメモリ制限に合わせてワーカーの数が自動的に調整されることがわかります。たとえば、Standard-1X dyno の場合は次のようになります。
$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2 -F fpm_custom.conf`
2019-01-15T07:51:33.109122+00:00 app[web.1]: Using PHP-FPM configuration include 'fpm_custom.conf'
2019-01-15T07:51:33.188816+00:00 app[web.1]: Optimizing defaults for 1X dyno...
2019-01-15T07:51:33.370674+00:00 app[web.1]: 8 processes at 64MB memory limit.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up
memory_limit
の実行時の変更
WEB_CONCURRENCY
の計算に使用されるメモリ制限は起動時に決定されるため、実行時に ini_set("memory_limit", ...)
を使用してメモリ制限を変更しても並列性には影響しません。
ini_set()
を使用して実行時にメモリ制限を初期設定値よりも増やすプロセスが多数あり、それらのプロセスが実際に追加のメモリを消費している場合は、R14 エラーが発生する可能性があります。このエラーは、アプリケーションがディスクへのページングを開始したことを示しており、パフォーマンスが低下する可能性があります。この場合は、静的な memory_limit
を増やすか、または WEB_CONCURRENCY
を手動でより小さい値に設定します。
多くの場合、より高い並列性を実現するためにメモリ制限を小さくすることが望まれます。このような場合は ini_set()
を使用して、一時的に大きなメモリ制限を必要とするアプリケーションのいくつかのコードパスに対してのみ、実行時により大きな制限を動的に設定します。
適切なメモリ制限の決定
アプリケーションが必要とするメモリの量は、リクエスト中に処理するデータの量や、そのデータのうちのどれだけを同時にメモリ内に保持するかによって異なります。画像処理や大規模なデータベース結果セットの処理などのタスクは、通常メモリを大量に使用します。
PHP での 128 MB のデフォルトのメモリ制限は、ほぼすべての種類のアプリケーションに十分な “余裕” を与えることを目的にした控えめなデフォルト値です。コードはリクエスト中にそれほど多くのメモリを消費しない可能性があるため、この制限を小さくすることはアプリケーションのパフォーマンスを最適化するための優れた方法です。
アプリケーションの最大メモリ使用量を決定したら、将来の成長や予期しない状況に備えて制限を設定する際に安全マージンを考慮します。たとえば、時間の経過とともにデータセットが増加する場合などです。
ローカルでのメモリ使用量の測定と最適化
開発用マシンでは、コードで (通常はスクリプトの最後に) memory_get_peak_usage()
関数を使用して、使用されるピークメモリを特定できます。たとえば、コードの最後にある file_put_contents("path/to/logfile", memory_get_peak_usage()."\n", FILE_APPEND);
で負荷/機能テストを実行し、sort path/to/logfile | tail -n 1
を使用して、そのログファイル内の最も大きな値を特定することもできます。
別のアプローチとして、「メモリ制限を超えました」というエラーメッセージが表示されるまで、以降の手順で PHP で設定されているメモリ制限を、たとえば 16 MB ずつ小さくする方法があります。多くの場合、64 MB の制限は安全であり、デフォルト設定と比較してワーカープロセスの数が 2 倍になります。
ab
、siege
、httperf
などを使用してローカルで負荷テストを実行する場合は、ps
または top
を使用して、php-fpm
プロセスで消費されるメモリの量を観察することもできます。
開発中にアプリケーションを適切にプロファイリングするには、XHProf をお勧めします。補完的な xhprof.io GUI により、プロファイリングの結果のナビゲーションが簡単かつ便利になります。
ローカルでテストを実行する場合は、現実的な条件を使用することを忘れないでください (データベースの適切な大規模結果セットの使用など)。また、入力データサイズの増加によって不適切にスケーリングする可能性がある関数、ループ、またはアルゴリズムがないかコードを監査し、それに応じて最適化することを強くお勧めします。
Heroku 上のメモリ使用量の測定
プラットフォーム固有の細かな差異は別にして、アプリケーションのメモリ消費は、入力が同じであればローカル開発環境と Heroku の間でほぼ同じであるため、まず開発環境で最適なメモリ制限を見つけるようにすることをお勧めします。
log-runtime-metrics Heroku Labs 機能は、メモリ消費を heroku logs
ストリームに定期的に報告します。負荷テストを実行しているとき、メモリ使用量が、対応する dyno タイプで使用可能な量より大幅に低い場合は、メモリ制限が大きすぎる値に設定されていることを示している可能性があります。並列性を向上させ、それにより実際のメモリ使用量を増やすために、制限を小さくしてみてください。
Heroku Dashboard のアプリの概要セクションで、基本的なメモリ使用量のレポートやグラフを検査することもできます。ここには、すべての実行中の dyno の平均値が表示されることに注意してください。
New Relic などのアプリケーションパフォーマンス監視ツールはメモリ使用量を記録し、それをユーザーによる分析のために報告します。
Heroku Pipelines は、デプロイをある環境から別の環境に (たとえば、ステージング環境から本番環境に) プロモートするためのワークフローの自動化に役立ちます。これにより、非本番アプリでのメモリ使用量の変更を測定して最適化した後、それらの変更の準備ができたら本番環境にプロモートできるようにすることが簡単になります。
手動での並列性のチューニング
アプリケーションを実行している子プロセスの数を手動で設定する場合は、環境設定を設定することによって WEB_CONCURRENCY
環境変数を調整できます。
たとえば、子プロセスの数を 8 に静的に設定するには、heroku config:set
を使用します。
$ heroku config:set WEB_CONCURRENCY=8
WEB_CONCURRENCY
を手動で設定する場合は、その値に memory_limit
を掛けた値が、その dyno タイプ上で使用可能な RAM の量を超えないようにしてください。
環境設定を設定するとアプリケーションが再起動し、起動中に dyno は静的な設定を報告します。
$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2 -F fpm_custom.conf`
2019-01-15T07:51:33.109122+00:00 app[web.1]: Using PHP-FPM configuration include 'fpm_custom.conf'
2019-01-15T07:51:33.370674+00:00 app[web.1]: Using WEB_CONCURRENCY=8 processes.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up
WEB_CONCURRENCY
を固定値に設定する場合は、別の dyno タイプにスケーリングするときに調整して、新しい dyno タイプで使用可能な RAM の量を最適化することを忘れないでください。