🐘

php-fpmのプロセスをコントロールする

2024/02/27に公開

スターフェスティバル株式会社 エンジニアのDPontaroです。

昨年のことですが、php-fpm周りのパラメータチューニングを行いパフォーマンス調整を行ったので記事にしてみます。

概要

  • 既存WebアプリケーションにNew Relic入れることになった
  • 本番導入したらパフォーマンス悪くなり障害発生、切り戻し
  • 改めて検証
  • php-fpmとNew Relicのパラメータ調整して無事導入

前提

New Relic

https://newrelic.com/jp
New Relicはアプリケーションパフォーマンス管理(APM)ツールです。
Webアプリケーションやサーバーのパフォーマンスを可視化して、開発者はシステムの問題を迅速に特定し、改善することができるようになります。

php-fpm

https://www.php.net/manual/ja/install.fpm.php
FPM (FastCGI Process Manager)

CGIはリクエスト毎にプロセスの生成、破棄が行われるのでリクエスト数の増大によりそのオーバーヘッドが無視できなくなります。
php-fpmでは起動時にプロセスをメモリ上に保持(プール)し、リクエスト毎にプロセスを再利用するのでオーバーヘッドなく処理できます

アプリケーションの構成

  • AWS ECSクラスターで稼働
  • nginx + php-fpm

New Relic導入

社内的に各アプリケーションへの導入が進んでおり私が担当していたアプリケーションにも導入することとなりました。

本番で障害発生

開発環境などで動作検証した際は問題なかったのですが、
本番環境にも導入したところパフォーマンスに影響出てしまい、切り戻すことになりました。
New Relicというツールの特性上、実行されてるコードのトレースを行ったりするわけですが、そのあたりが影響した可能性がありそうでした。

改めての検証

New Relicを入れたECSクラスターを別に用意して、n%だけそちらに割り振られるようにALBのリスナールールを設定して検証を進めることとなりました。

ターゲットグループ名隠してるのでわかりにくいですが、こんな感じで初回は10%だけ割り振るような設定。徐々に割り振りを増やしてパフォーマンスを監視していきました。

ALBリスナールール 転送アクションあたりを参照。

モニタリング

ピークタイム時などのリソースをモニタリングしていると、CPU使用率が跳ね上がる瞬間がありました。

ALBのリクエスト数を確認してみると、同じタイミングで跳ね上がっていました。

見つけたwarning

同時刻のログを確認していると、下記のWARNINGを見つけました。

WARNING: [pool www] seems busy (you may need to increase pm.start_servers,
or pm.min/max_spare_servers), spawning 32 children,
there are 0 idle, and 9 total children

ざっくりいうとリクエスト量が生成済のプロセス数を上回って処理しきれていなさそうでした。
(生成済のプロセスは9個、うちアイドル状態のものが0、別途32個のプロセスを生成しようとしている)

php-fpmのプロセス生成まわりを調整

php-fpmの設定
php-fpmでプロセス数や生成に関する設定は、下記pm系のパラメータで行います。

pm = dynamic
pm.max_children = 40
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

max_childrenは生成する最大数です。40とそれなりに多めに設定されていました。
pmがdynamicになっていました。これはプロセス数を動的に増減させる設定です。

dynamic - 子プロセスの数は、 pm.max_childrenpm.start_servers、 pm.min_spare_serverspm.max_spare_servers の内容に基づいて動的に設定されます。

起動時の子プロセス(pm.start_servers)は2となっており、WARNING発生時点でプロセスは9までは増えている状況でした。
staticにすると、起動時からmax_children分のプロセスを生成するのでそちらに切り替えて再度検証するようにしました。

デメリットとして、リクエストが少ないときはアイドル状態のプロセスが多くあり、余分にメモリを使用することにはなりますが、メモリは余裕があったのでdynamicにしておく意義は薄いと判断しました。

一旦解消したけれど

上記設定により一旦解消したかに見えたのですが、またCPU使用率があがるタイミングがありました。
今度はメモリ使用率のとこ見ると、同じタイミングでガクンとメモリが空いていました。(キャプチャ漏れなので画像なし)
これはphp-fpmのプロセスの再生成が一気に起きて一時的にメモリが空いたのではないかと推測しました。

; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
pm.max_requests = 500

pm.max_requestsという設定があり、これは設定したリクエスト数をさばくとプロセスを再起動するというものです。

各子プロセスが、再起動するまでに実行するリクエスト数。 サードパーティのライブラリにおけるメモリリークの回避策として便利です。 再起動せずにずっとリクエストを処理させる場合は '0' を指定します。

500と設定されており、この数値が小さすぎたかもしれません。
あまりバラけずに各プロセスにリクエストが割り振られ、近しいタイミングでプロセスの再起動が発生。
一時的にプロセスが枯渇するタイミングになりCPU使用率の増加につながったのではないかと仮説を立てました。

5000に引き上げると以後特に問題は発生せず、うまくバラけて再起動されるようになったかと思います。

こちらもデメリットとしてプロセスが長生きする分、メモリ使用量への影響が出てくるのでそこと相談して設定することになるかと思います。
(当該アプリケーションでは1日以内には、どのプロセスも再生成されるくらいにはリクエストがくるのでそこまでゴミも残らないと判断)

New Relicのサンプル数

リクエストが捌ききれない、といった状況はphp-fpmの設定により解消できたのですが、
リクエスト全体のレスポンスタイムが導入以前より50〜100msほど増えているようでした。
https://docs.newrelic.com/jp/docs/apm/agents/php-agent/configuration/php-agent-configuration/#inivar-span-events-max-samples-stored
New Relic側の設定で、newrelic.span_events.max_samples_stored というものがあります。
1分あたりに送信されるイベント量の設定のようで、調整することで負荷を軽減ができそうでした。
デフォルト設定だったので、2000になっていたようで半分の1000にするなどして調整しました。
減らしすぎるとサンプルが少なくなり分析に支障が出るかもしれないので、そのあたりとの兼ね合いで設定すると良さそうです。

まとめ

  • 本番検証したいときは、一部のリクエストだけ流すといったALBのリスナールールを利用してユーザー影響を抑えつつ検証が可能。
  • アプリケーションのリクエストの傾向などと照らし合わせて、php-fpmのプロセス数や増減の方法などを調整すると良し。
  • New Relicのサンプル数も適度な数に調整すること。

何かお役に立てたら幸いです。
ご拝読ありがとうございました。

スタフェステックブログ

Discussion