php-fpm の設定を理解してサイトのパフォーマンスを向上させる

PHPとnginxとApacheのロゴ

nginxでWordpressサイトを運用する場合、php-fpmを利用することが多いかと思います。(Apacheでも利用することができますが)

php-fpmの設定によって、Webサイトのパフォーマンスは左右されます。それだけでなく、不適切な設定はメモリリーク等につながるので、Webサイトにとって重要な設定であるといえます。

この記事では、そんな php-fpm の設定の最適化方法について解説していきたいと思います。

php-fpmの役割と特徴

WordPress などの動的サイトは、Webサーバーがクライアントからリクエストを受けると、サーバー上でPHPを実行して動的にページを生成し、生成したページをレスポンスとしてクライアントに返します。

この、サーバー上でPHPを実行する仕組みがphp-fpmです。php-fpmでは、リクエストのたびにプロセスを生成していたのでは非効率なので、原則あらかじめ1つないし複数のワーカープロセス(以下、「プロセス」と書きます)を起動しておき、サーバーに届いたリクエストの処理に割り当てます。このプロセスの集まりのことを、プールと言います。

起動しているプロセスの数によって、サーバーの同時接続可能数やリソース使用量(CPU、メモリ、ディスク等)が変動します。多くのプロセスが起動していれば、それだけ多くの接続を一度に捌くことができますが、リソース消費は増大します。

プロセス数の制御

起動プロセス数の制御方法は2つあります。

設定した数を常に起動しておく方法(static

プロセスの起動に伴うオーバーヘッドがないという利点があります。ただし、最大同時接続可能数を上げようとすればするほど常に多くメモリを必要とするようになるという短所もあります。メモリに対してCPUの処理能力が低いサーバに向いています。

起動数を動的に変更する方法(dynamic)

通常時はある程度の数プロセスを起動しておきます。同時接続数が増え、プロセス数が足りなくなった時だけ、設定した範囲でプロセスが追加で起動されます。この方法は、通常時のメモリ消費を抑えつつ、ピーク性能を向上させることができるという点で優れています。

static と dynamic どちらを選択すれば良いのか

dynamic のほうが柔軟に処理能力を調整することができるので、より良いようにも思えます。しかし、プロセスはリクエストの処理中CPU1コアを占有するので、同時処理可能なリクエスト数はCPUの論理コア数に依存します。(コア数以上にプロセスを起動しても、使い切れないことが多いです)それに対して、1プロセスあたりのメモリ消費量は100MBを超えることはほぼありません。したがって、一般的なサーバーの場合メモリやディスクの限界に達する前にCPUがボトルネックとなるので、あまり多くの数プロセスを起動しても意味がありません。

それならば、プロセスの動的な起動に伴うオーバーヘッドがないstaticを選択するほうが良い場合も多いです。

実際の動作(dynamic)

dynamicのプロセス管理方法は少しイメージしづらいので、実際の動作を見ながら解説します。

起動直後のプロセスごとのメモリ使用量を確認してみます。(このサイトとよく似た検証環境を用意してテストしています。メモリ使用量などは、サイトによって異なるかと思われます。)

$ ps aux
#関係する行のみ抜粋
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       4625  0.0  0.9 402624 16780 ?        Ss   22:42   0:00 php-fpm: master process (/etc/php-fpm.conf)
www        4626  0.0  0.4 402624  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4627  0.0  0.4 402624  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4628  0.0  0.4 402624  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4629  0.0  0.4 402624  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4630  0.0  0.4 402624  8204 ?        S    22:42   0:00 php-fpm: pool www

php-fpm: pool www と書かれた行が、実際にリクエストを受け付けるプロセスです。RSS 列がキロバイト単位の物理メモリ使用量です。

起動直後は、プロセスあたり約8MBしか消費していません。

 

リクエストを1つ捌いた後の状態が以下です。

USER        PID %CPU %MEM   VSZ    RSS TTY      STAT START   TIME COMMAND
root       4625  0.0  0.9 402624 16780 ?        Ss   22:42   0:00 php-fpm: master process (/etc/php-fpm.conf)
www        4626  0.0  1.8 505432 33456 ?        S    22:42   0:00 php-fpm: pool www
www        4627  0.0  1.6 421732 28612 ?        S    22:42   0:00 php-fpm: pool www
www        4628  0.0  0.4 402624  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4629  0.0  1.6 421732  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4630  0.0  0.4 402624  8204 ?        S    22:42   0:00 php-fpm: pool www
www        4645  0.0  0.4 402624  8204 ?        S    22:50   0:00 php-fpm: pool www #新たに追加されたプロセス

2つのプロセスが使用され、新たに1つプロセスが起動されました。リクエストの処理に使用されたプロセスのメモリ使用量は、30MB程度まで上昇しています。

 

さらにもう1リクエスト捌いた後の状態が以下です。

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       4625  0.0  0.9 402624 16780 ?        Ss   22:42   0:00 php-fpm: master process (/etc/php-fpm.conf)
www        4626  0.0  1.8 505432 33456 ?        S    22:42   0:00 php-fpm: pool www
www        4627  0.0  1.6 421732 28612 ?        S    22:42   0:00 php-fpm: pool www
www        4628  0.0  1.6 421732 29320 ?        S    22:42   0:00 php-fpm: pool www #今回使用されたプロセス
www        4629  0.0  1.6 421732  8200 ?        S    22:42   0:00 php-fpm: pool www
www        4630  0.0  0.4 402624  8204 ?        S    22:42   0:00 php-fpm: pool www
www        4645  0.0  0.4 402624  8204 ?        S    22:50   0:00 php-fpm: pool www

未使用だったPID:4628のプロセスが処理に使用されました。php-fpmは、起動しているプロセスをまんべんなく使用しようとします

 

この状態から、同時10接続を捌いた後のプロセスの状態を確認してみます。

$ ab -n 10 -c 10 https://example.com

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       4625  0.0  0.9 402624 16780 ?        Ss   22:42   0:00 php-fpm: master process (/etc/php-fpm.conf)
www        4626  0.0  2.5 517720 45808 ?        S    22:42   0:01 php-fpm: pool www
www        4627  0.0  2.3 434020 41776 ?        S    22:42   0:01 php-fpm: pool www
www        4628  0.0  2.3 434020 41748 ?        S    22:42   0:01 php-fpm: pool www
www        4629  0.0  2.3 434020 41728 ?        S    22:42   0:01 php-fpm: pool www
www        4630  0.0  2.1 429924 37616 ?        S    22:42   0:00 php-fpm: pool www
www        4645  0.0  2.1 429924 37600 ?        S    22:50   0:00 php-fpm: pool www
www        4678  0.0  1.6 421732 29356 ?        S    23:17   0:00 php-fpm: pool www
www        4688  0.4  1.6 421732 29360 ?        S    23:17   0:00 php-fpm: pool www
www        4694  0.4  1.6 421732 29376 ?        S    23:17   0:00 php-fpm: pool www
www        4695  0.0  0.4 402624  8204 ?        S    23:17   0:00 php-fpm: pool www

同時10接続を処理するために、新たにプロセスが起動され、合計で10個のプロセスが起動しています。

プロセスの肥大化

php-fpmは、再起動しない限りプロセスを使いまわそうとするので、リクエストの処理数と共にプロセスが肥大化していくことがあります。

別のサーバーで、再起動せずに2万リクエストほど処理させた後のプロセスの状態を以下に示します。

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
www      17609  0.0  7.8 718236 161236 ?       S     5月14   6:19 php-fpm: pool www
root     25017  0.0  1.4 534764 28896 ?        Ss    5月14   0:26 php-fpm: master process (/etc/php-fpm.conf)
www      25018  0.0  8.0 720764 164492 ?       S     5月14   6:30 php-fpm: pool www
www      25019  0.0  7.7 715952 157684 ?       S     5月14   6:29 php-fpm: pool www
www      25020  0.0  8.1 721608 166440 ?       S     5月14   6:21 php-fpm: pool www
www      25021  0.0  8.1 720904 165812 ?       S     5月14   6:26 php-fpm: pool www
www      25022  0.0  8.1 720956 166852 ?       S     5月14   6:25 php-fpm: pool www
www      25071  0.0  7.8 720020 159816 ?       S     5月14   6:26 php-fpm: pool www
www      28624  0.0  7.3 707008 150584 ?       S     5月14   5:22 php-fpm: pool www

各プロセスごとに160MBほど消費しており、メモリ消費は合計すると1GBを優に超えています。メモリ搭載量の少ないサーバーでは、これだけでメモリを使い切ってしまう可能性があります。

php-fpm の設定(まとめ)

php-fpm の設定を最適化するで詳しい最適化の考え方を紹介しますが、とりあえず設定例を挙げておきます。

想定環境

今回設定する想定のサーバーの概要を以下に示します。

  • CPU:仮想2コア
  • 物理メモリ: 2GB
  • SSD搭載
  • 月間PV:10万以下

一般的な月額1000円台のクラウドサーバーという想定です。月間PV10万程度であれば、このスペックのサーバーでも問題ありません。(極端にアクセスに偏りがなければ)

設定例

[www] #プール名(任意の名前でOK)
listen = /var/run/php-fpm/php-fpm-www.sock
user = www
group = www
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
pm = static
pm.max_children = 2
pm.max_requests = 1500

listen

リクエストを待ち受けるポート or UNIX ドメインソケットを指定します。

Webサーバーとphp-fpmを実行するのが同じマシンであれば、UNIXドメインソケットを使ったほうが処理が速いです。

user / group

プロセスの実行ユーザー/グループを指定する。

listen.owner / listen.group

UNIX ドメインソケットを利用する場合の、ソケットのオーナー/グループを指定します。Webサーバーからリクエストを受け付けるには、Webサーバーがソケットに対して読み書き可能でなければいけません

listen.mode

UNIX ドメインソケットを利用する場合の、ソケットのパーミッションを指定します。chmodコマンドと同じ方法で指定します。

 

これ以外の設定については、php-fpm の設定を最適化するで解説します。

php-fpm の設定を最適化する

php-fpmの設定を調整することで、サーバーのピーク性能を最大化しつつ、通常時のメモリ消費を抑えることができます。

設定ファイルは、/etc/php-fpm.d 配下にあります。ファイル名はサーバーによって異なるので、適宜読み替えてください。

今回設定を最適化する対象のサーバーの概要は、想定環境を参照。

プロセスの最大起動数(pm.max_children)

同時に起動するプロセスの最大数です。この数値が、サーバーの同時接続可能数を決定します。プロセス数制御がstaticの場合、常にここで指定された数のプロセスを起動します。この数値を超える同時接続があった場合、超えた分のリクエストは、他のリクエストの処理後に処理されるので、レスポンスタイムが悪化します。

ただし、この数値は無闇に増やすのではなく、サーバーのリソースで賄い切れる範囲内で最大の値を設定するべきです。(CPUのコア数を超えるプロセス数を起動しても同時に実行することはできません。また、メモリ消費量が多すぎるとswap領域を使うようになり、かえって逆効果になります。)

最適な値の求め方

プロセスの最大起動数は、最もボトルネックとなっているリソースに合わせて決定します。

CPUは1プロセスあたり1コア、メモリは1プロセスあたり数十MBとして計算します。(正確な1プロセスあたりのメモリ使用量は、サーバーごとに異なるのでpsコマンドを使って調べてください。)

今回の例では、CPUがボトルネックなのは明らかなので、コア数と同じ2を設定します。※1ちなみに、仮にメモリ使用量がボトルネックであったとすると、メモリ容量 ÷ 1プロセスあたりの使用量 – 他のプロセスで使う分 で、プロセスの同時起動可能数が算出できます。このサーバーの場合、1742 ÷ 45 = 38.7 から、WebサーバーやDBで使う分を引いて、35となります。

また、今回は同時起動数が少ないので、プロセス制御はstaticを使用します。

$ vi /etc/php-fpm.d/www.conf

#該当部分のみ抜粋
pm = static
pm.max_children = 2

 

設定値を概算したら、Webサーバーの負荷テストツールであるabと、サーバーのリソースをリアルタイムで確認できるvmstatコマンドを組み合わせて、設定値を検証します。

abがインストールされていない場合、以下のコマンドでインストールします。(CentOS の場合)

$ yum install httpd-tools

 

次に、算出した設定値が適切かどうか確認します。

まず、vmstatコマンドを実行します。

#Webサーバーとphp-fpmの再起動
$ systemctl restart <Webサーバー>
$ systemctl restart php-fpm

# vmstat <データの取得間隔(単位:秒)>
$ vmstat 1

 

その後すぐに別のターミナルウィンドウでabを実行します。同時リクエスト数は、pm.max_children よりも少し多い数に設定し、総リクエスト数はある程度まとまった数を設定します。

abの実行結果は、Failed requests がゼロであることを確認したうえで、Requests per second (1秒当たりのリクエスト処理数)の値に注目します

#ab -n <総リクエスト数> -c: <同時リクエスト数> <サーバーのURL>
ab -n 100 -c 5 https://example.com

#以下、重要な項目のみ抜粋

Benchmarking example.com (be patient).....done


Complete requests:      100
Failed requests:        0

Requests per second:    4.88 [/sec] (mean)

Percentage of the requests served within a certain time (ms)
  50%    922
  66%   1072
  75%   1078
  80%   1082
  90%   1095
  95%   1100
  98%   1107
  99%   1110
 100%   1110 (longest request)

abが終了したら、vmstatのウィンドウに戻って、ctrl + C キーで停止します。(実行結果は、一部読みやすいようにコメントを入れています)

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1278300   2232 209344    0    0   155    11   11   91  0  0 99  0  0
 0  0      0 1278308   2232 209344    0    0     0     0    8   90  0  0 100  0  0
 1  0      0 1278308   2232 209344    0    0     0     0   15  120  0  0 100  0  0
######## ab実行開始 ########
 1  0      0 1198268   2232 231424    0    0 18564     8  687 2087 17  7 69  7  0
 7  0      0 1181860   2232 232100    0    0   500   656  293 2737 81  6 13  1  0
 3  0      0 1181556   2232 232112    0    0     0     0    6 1704 95  5  0  0  0
 3  0      0 1177492   2232 232116    0    0     0   336    6 1749 96  4  1  0  0
 3  0      0 1177436   2232 232116    0    0     0    16    4 1434 95  5  0  0  0
 2  0      0 1177460   2232 232116    0    0     0     0    6 1749 96  4  0  0  0
 3  0      0 1177452   2232 232132    0    0     0     0    4 1439 95  5  0  0  0
 3  0      0 1177412   2232 232132    0    0     0    16    3 1774 96  4  0  0  0
 4  0      0 1177452   2232 232136    0    0     0     0    0 1501 96  4  0  0  0
 2  1      0 1177460   2232 232140    0    0     0     0    1 1624 96  5  0  0  0
 2  0      0 1177452   2232 232140    0    0     0     0    2 1547 95  5  0  0  0
 2  0      0 1177452   2232 232140    0    0     0     0    0 1612 96  4  0  0  0
 3  0      0 1177452   2232 232148    0    0     0     0    0 1573 97  3  0  0  0
 3  0      0 1177412   2232 232148    0    0     0     0    2 1563 96  5  0  0  0
 3  0      0 1177452   2232 232148    0    0     0     0    1 1720 95  5  0  0  0
 2  0      0 1177328   2232 232156    0    0     0     0    4 1446 96  4  0  0  0
 2  0      0 1177328   2232 232160    0    0     0     0    1 1724 95  6  0  0  0
 3  0      0 1177308   2232 232160    0    0     0     0    1 1421 95  5  0  0  0
 3  0      0 1177320   2232 232160    0    0     0     0    0 1717 95  5  0  0  0
 2  0      0 1177288   2232 232184    0    0     0     4    1 1432 95  5  0  0  0
 0  0      0 1178120   2232 232184    0    0     0    52  271 1763 67  3 30  0  0
######## ab終了 ########
 0  0      0 1178180   2232 232184    0    0     0     0   16  159  0  0 100  0  0
 0  0      0 1178180   2232 232184    0    0     0     0    9  147  0  0 100  0  0

vmstatの表示結果の us + sy がCPU使用率になります。実行結果から、しっかり100%使い切っていることが分かります。CPU使用率が100%に近くない場合、pm.max_children の値が足りないか、別のリソースがボトルネックとなっています。

また、vmstatの表示結果のso、si 列の数値が0であればスワップ領域を使用していません。もしスワップ領域を大きく使ってしまっている場合は、pm.max_children の値を下げて再度同じ手順を実行します。

 

ちなみに、pm.max_children = 4 に変更してabを実行してみると以下のような結果になりました。

$ ab -n 100 -c 5 https://example.com/

Benchmarking example.com (be patient).....done


Complete requests:      100
Failed requests:        0

Requests per second:    4.93 [/sec] (mean)

Percentage of the requests served within a certain time (ms)
  50%    935
  66%    995
  75%   1076
  80%   1239 #pm.max_children = 2 の時よりも悪化している
  90%   1313
  95%   1405
  98%   1418
  99%   1420
 100%   1420 (longest request)

やはり、CPUコア数以上にプロセスを増やしても、Requests per second (1秒当たりのリクエスト処理数)はほとんど変化しないことが分かります。それどころか、リクエストへのレスポンスのうち、20%がプロセス数2のときよりも遅くなっています。

プロセスを再起動する処理リクエスト数(pm.max_requests)

プロセスの肥大化で触れた通り、多くのリクエストを処理すると、プロセスで使用するメモリがどんどん増大していくことがあります。

これを防ぐために、一定数リクエストを処理したプロセスを自動で再起動させることができます。この設定では、そのリクエスト数を指定します。

今回は、各プロセスを1日に1回再起動させることにします。各プロセスが1日に処理するリクエスト数は、1日のリクエスト(PV)数 ÷ pm.max_children(dynamic の場合、1日のリクエスト(PV)数 ÷ pm.max_spare_servers) 以下になるはずなので、この値を設定すればおおよそ1日に1回プロセスが再起動します。

$ vi /etc/php-fpm.d/www.conf

#該当部分のみ抜粋
pm.max_requests = 1500

 

ここからは、pm = dynamic の場合のみ設定することができる項目を紹介します。

 

アイドル状態のプロセス最大起動数(pm.max_spare_servers)

リクエストの処理を待機しているプロセスを、最大でいくつ起動しておくかという設定です。アイドル状態のプロセスの数がこの数値を超えると、超えた分だけプロセスがキルされます。

この設定は、通常時に(アクセスのピーク時間帯以外に)常時起動するプロセスの最大数とも捉えることができます。したがって、通常時サーバーで動いている他のプロセスのリソースを奪わない程度の値を設定します。

アイドル状態のプロセス最小起動数(pm.min_spare_servers)

リクエストの処理を待機しているプロセスを、最低限いくつ起動しておくかという設定です。アイドル状態のプロセスの数がこの数値を下回ると、その分新しいプロセスが起動されます。

この設定項目は、「新たなプロセスを起動せずにリクエストを処理する余裕」を最低限どの程度持っておくかということを決定します。例えば、5を設定した場合、新たなプロセスの起動を待たずに、サーバーが追加で最低限5リクエストを処理できることを保証します。

「新たなプロセスを起動せずにリクエストを処理する余裕」は、サーバーへ新たにほぼ全く同じタイミングで多くのリクエストがあった場合に備えるものです。月間10万PV程度のサイトであれば、よほど偏りがない限りこれはせいぜい3〜4接続程度なので、さほど大きな値を設定する必要はないと思われます。

親プロセス起動時の子プロセス起動数(pm.start_servers)

親プロセスの開始時に起動する子プロセスの数です。

この数値を基準にして、pm.max_spare_serverspm.min_spare_serversをもとにプロセス数が決定されます。予想される平均的な同時接続数をベースに、サーバーリソースを節約するのか、性能に余裕を持たせるのかによって数値を調整します。

脚注   [ + ]

1. ちなみに、仮にメモリ使用量がボトルネックであったとすると、メモリ容量 ÷ 1プロセスあたりの使用量 – 他のプロセスで使う分 で、プロセスの同時起動可能数が算出できます。このサーバーの場合、1742 ÷ 45 = 38.7 から、WebサーバーやDBで使う分を引いて、35となります。

あわせて読みたい

コメントを残す

質問・感想などお気軽にどうぞ。
*が付いている項目は入力必須です。メールアドレス以外の項目が公開されます。
スパム防止のため、コメント反映まで少々時間がかかります。