Edited at

[PHP] PHP-FPMのチューニングをするときに考えたことと行ったこと


パフォーマンスの問題が発生

(サーバサイドの話)

あるWebページで同じオペレーションをしても処理が早い時もあれば、タイムアウトになるくらい遅い時もあった。

原因を調査すると、Swapに入ってしまうことが大幅な遅延を引き起こす原因だった。

PHP-FPMのプロセスの設定が自分たちのサービスにとって適切なものではなさそうだったのでPHP-FPMの設定を見直してこの問題を解決した。

この記事はその時の記録。


Step1 PHP-FPMのプロセスに関する項目を理解する

pm (必須)

プロセスマネージャが子プロセスの数を制御する方法。
使用可能な値: static / ondemand / dynamic
static
- 子プロセスの数は固定される。
pm.max_childrenの値 = 子プロセスの数 となる。
ondemand
- プロセスを必要に応じて立ち上げる。
リクエストされるとpm.start_serversで指定しただけサービスを開始する。
dynamic <- 我々のサービスはこれを採用している。
- 関連する設定値の内容によって、立ち上がる子プロセスの数が動的に決まる。
pm.max_children
pm.start_servers
pm.min_spare_servers
pm.max_spare_servers

pm.max_children
pmの設定がstaticの場合
作成される子プロセスの数
pmの設定がdynamicの場合
作成される子プロセスの最大数

pm.start_servers
PHP-FPMのマスタープロセスを起動した時に作成される子プロセスの数。
pmの設定がdynamicの場合のみ有効になる。
デフォルト値: min_spare_servers + (max_spare_servers - min_spare_servers) / 2

pm.min_spare_servers
アイドル時(サーバが暇な時)に立ち上げておく子プロセス数の最小値を設定する。
pmの設定がdynamicの場合のみ有効になる。

pm.max_spare_servers
アイドル時(サーバが暇な時)に立ち上げておく子プロセス数の最大値を設定する。
もしもこの設定値よりも多くの子プロセスが立ち上がっていた場合、余剰分のプロセスはアイドル時にkillされる。
pmの設定がdynamicの場合のみ有効になる。

pm.max_requests
各子プロセスが、再起動するまでに実行するリクエスト数。
子プロセスのメモリが肥大化するときの回避策として使える。
再起動せずにリクエストを処理をさせ続ける場合は0を指定する。
デフォルト値: 0
PHP_FCGI_MAX_REQUESTSと同じ。

※参照したページ

1. PHPマニュアル FastCGI Process Manager (FPM) > 設定

http://php.net/manual/ja/install.fpm.configuration.php


Step2 まず各値の暫定値を決める

適当な値でチューニングすることは難しいと思った。

なので、各値を決めるための基準を自分の中で設け、

それによって導きだした値を暫定値としてチューニングを始めることにした。


pm.max_children

子プロセスの最大数を決めていく。

子プロセスの最大数は


  • Webサーバの搭載メモリ

  • 自サービスが1リクエストあたりどれくらいのメモリを使うのか

この2つの要素で決まってくるのではないかと考えた。


Webサーバの搭載メモリ

まずはどれくらいメモリを使うことができるかWebサーバの搭載メモリを確認した。

$ free

total used free shared buff/cache available
Mem: 3873252 1501948 1917404 63528 453900 2041612
Swap: 4194300 335644 3858656

約3.7GBのメモリを使うことができそう。


自サービスが1リクエストあたりどれくらいのメモリを使うのか

これはmemory_limitの設定値が参考になると考えた。うちのサービスはメモリ使用量が多めでmemory_limitが256MB。

3800 ÷ 256 MB = 14.8xx

四捨五入した値を採用し、pm.max_childrenの暫定値を15としておくことにした。


pm.start_servers

PHP-FPMのマスタープロセスを起動した時に作成される子プロセスの数。

プロセスを立ち上げるのにもコストがかかると思うので、少なすぎず多すぎずの値が良いと思う。

ひとまずメモリの30%-40%を使うくらいの設定しておく。

3800 MB × 0.35 ÷ 256 MB = 5.1xx


pm.min_spare_servers

PHP-FPMの立ち上げ時 = アイドル状態

と考えてpm.start_serversと同じく5を暫定値としておく。


pm.max_spare_servers

アイドル時(サーバが暇な時)に立ち上げておく子プロセス数の最大値。

ここもプロセスを立ち上げるコストを考えて、アイドル時でも少し多めにプロセスを残すことにした。

メモリの70%を閾値として

3800 MB × 0.7 ÷ 256 MB = 10.3xx

10を暫定値としておく。

プロセスがmaxの15プロセス立ち上がっていると、5つkill (256 MB × 5 = 1,280 MBのメモリが開放)する設定。


Step3 暫定値を反映させてテスト

設定に問題はないかテストしてみる。

テスト中は、サーバの状態をvmstatで、プロセスの状態をpsで、PHP-FPMのlogをtailで確認する。

$ vmstat 5

$ watch -n1 "ps auxf | grep 'php-fpm'"


Step4 プロセスのメモリが肥大化する問題に直面したので再チューニング

最も重いページを数十回連続で操作すると、プロセスのメモリが肥大化してメモリを食いつぶしSwapに入ることがテスト中に判明したので、

pm.max_requestsを設定することで子プロセスを再起動してプロセスのメモリが肥大化するのを防ぐようにする。


Step5 Step3とStep4の繰り返し

Step3とStep4を繰り返し、pm.max_requestsの最適値を探していく。


Step4の問題が生じなければ

ab(Apache Bench)で負荷を見ながらチューニングしてもいいと思う。

# 100ユーザーが同時に1リクエスト(合計100リクエスト)

$ ab -n 100 -c 100 [テストするページのURL]


場合によっては搭載メモリを増強する必要もある

プロセスが足りないとPHP-FPMのlogにWARNINGが出力される。

[12-Dec-2018 14:24:49] WARNING: [pool xxxxx] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 3 idle, and 10 total children

[13-Dec-2018 17:37:19] WARNING: [pool xxxxx] server reached pm.max_children setting (15), consider raising it

あまりにもこのlogが頻繁に出力されているようだと数を絞り過ぎている可能性が大きいので、Webサーバの搭載メモリを増強、その後pm.max_children設定値も上げるなどして、再度各値を変更した方が良いと思う。


終わりに

知見のある方、フィードバックをお待ちしています。