PHP アプリケーションの並列性の最適化
最終更新日 2022年11月28日(月)
Table of Contents
Heroku 上の PHP アプリケーションは PHP-FPM FastCGI Process Manager の下で動作し、FastCGI プロトコルを使用して Apache または Nginx Web サーバーと通信します。
FPM は、実際の PHP アプリケーションコードを実行する子プロセスを生成して管理します。これらの各プロセスは、Web サーバーからのリクエストを一度に 1 つずつ処理します。つまり、プロセスの数を増やすと並列性が向上し、それによってトラフィックが多い状況でのアプリケーションパフォーマンスも向上します。
設定されている PHP のメモリ制限は、アプリケーションを実行する各子プロセスに適用されます。これらの子プロセスは、終了される前にその制限までメモリを消費できます。
Premier または Signature Success Plan の Heroku Enterprise の顧客は、Customer Solutions Architecture (CSA) チームに、このトピックに関する詳細なガイダンスを要求できます。ここでエキスパートコーチングセッションについて学習するか、または Salesforce の担当者にお問い合わせください。
デフォルト設定と動作
PHP-FPM
PHP-FPM は、static
プロセス管理モードを使用して動作するように設定されます。つまり、固定された数の子プロセスが生成されます。これは、dyno インスタンスが完全に分離され、固定された RAM が割り当てられている、Heroku のような環境には最適な設定です。
生成する子プロセスの数 (PHP-FPM の pm.max_children
設定によって制御される) は、Heroku によって適切な値に自動的に設定される WEB_CONCURRENCY
環境変数を使用して決定されます。
WEB_CONCURRENCY
のデフォルト値
アプリケーションで複数の buildpack が使用されている場合、PHP buildpack (アプリケーションの第一言語の buildpack) が他の言語の buildpack の後で実行されるようにする必要があります。そうしない場合、PHP buildpack の後で実行された buildpack の WEB_CONCURRENCY
のデフォルトがアプリケーションに適用されることがあります。
たとえば、heroku/nodejs
buildpack を heroku/php
と同時に使用するとき、heroku/nodejs
buildpack が heroku/php
buildpack の後に実行された場合、WEB_CONCURRENCY
のデフォルトは正しくない値になります。
アプリケーションを起動すると、dyno タイプが自動的に検出され、WEB_CONCURRENCY
環境変数は、その dyno 上の使用可能な RAM の量を各 PHP プロセスに対して設定されているメモリ制限で割った値に設定されます。
$ heroku ps:scale web=1:standard-2x
$ heroku logs
2020-02-06T14:52:40… heroku[web.1]: State changed from down to starting
2020-02-06T14:52:42… heroku[web.1]: Starting process with command `heroku-php-apache2`
2020-02-06T14:52:43… app[web.1]: Detected 1073741824 Bytes of RAM
2020-02-06T14:52:43… app[web.1]: PHP memory_limit is 128M Bytes
2020-02-06T14:52:43… app[web.1]: Starting php-fpm with 8 workers...
2020-02-06T14:52:43… app[web.1]: Starting httpd...
2020-02-06T14:52:44… heroku[web.1]: State changed from starting to up
Heroku 上のデフォルトの memory_limit
は、対応する PHP バージョンのデフォルト値です。現在は、PHP のすべてのバージョンで 128 MB です。
つまり、次の設定が標準で適用されます。
dyno タイプ | 使用可能な RAM | PHP memory_limit | WEB_CONCURRENCY |
---|---|---|---|
Eco、Basic、Standard-1x | 512 MB | 128 MB | 4 |
Standard-2X | 1024 MB | 128 MB | 8 |
Performance-M | 2.5 GB | 128 MB | 20 |
Performance-L | 14 GB | 128 MB | 1121 |
1: WEB_CONCURRENCY のデフォルト値は、7.4 より前の PHP バージョンでは 48 です。 |
下位互換性のために、performance-l
dyno タイプのデフォルト値では、7.4 より前の PHP バージョンで使用可能なメモリ量のすべては利用されません。Heroku で自動的に割り当てられる数を超える数のプロセスを使用したい場合は、このドキュメントの「手動での並列性のチューニング」のセクションで概説されている手法を使用してください。
アプリケーションが各リクエストで、完全な飽和状態でそのメモリ制限全体を消費することはめったにない、つまり dyno はデフォルトでは少し過剰に購読されているため、これらのデフォルト値は、PHP-FPM マスタープロセスまたは Web サーバープロセスの “余裕” をまったく残さないように意図的に選択されています。
子プロセスの数 (さらには、並列に処理できるリクエストの数) を増やすには、単にアプリケーションの memory_limit
設定を小さくします。メモリ制限は、Heroku 上の PHP アプリケーションの並列性を調整するための主な方法です。
memory_limit
を使用した並列性のチューニング
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
dyno の起動時に Heroku によってメモリ制限が決定されるときにドキュメントルートのサブディレクトリ内の追加の .user.ini
ファイルは評価されませんが、このようなディレクトリ内の 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
の実行時の変更
計算に使用されるメモリ制限は起動時に決定されるため、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 の量を考慮に入れるためにその値の調整が必要になることがあります。