Natsuyoshi-Jr Blog

技術や趣味や日常生活で調べたことを投稿していこうと思います。

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

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

(サーバサイドの話) あるWebページで同じオペレーションをしても処理が早い時もあればタイムアウトになるくらい遅い時もあることがわかった。 PHPの処理が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サーバの搭載メモリ

まずはどれくらいメモリを使うことができるか確認。

$ free -h
              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の最適値を探していく。

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

プロセスが足りないと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設定値を上げ、再度各値を変更した方が良いと思う。