ステージング環境をAWSで運用していましたが、頻繁にサーバーが落ちるという問題がありました。
そこでエラーログを見てみたところ、
PHP Fatal error: Out of memory (allocated 54525952) (tried to allocate 86016 bytes) in Unknown on line 0
というエラーが。
メモリ使用量がいっぱいになってしまいPHPのプログラムが動かなくなっていたので、ApacheとPHP-FPMの設定を見直しました。
同じようなエラーが出たという方の参考になるかもしれませんので備忘録として残します。
まずはサーバーのスペック確認
メモリ数とコア数によって設定値を調整する必要があるので確認します。
メモリ 3757MB・コア数 1となっていました。
$ free -m
total used free shared buffers cached
Mem: 3757 1247 2510 39 141 276
$ grep "cpu cores" /proc/cpuinfo
cpu cores : 1
次に、現在のApache MPMの設定を確認してみます。
$ httpd -V | grep MPM
Server MPM: event
event MPMを使っているようです。
eventはスレッドを使用している分、preforkと比べてメモリ使用量は抑えられると思いますが、
とはいえ設定ファイルがデフォルトの数値のままだったので、メモリ3757MBでも稼働するように調整が必要です。
もともとの設定は下記になっていましたので、こちらを改善していきます。
<IfModule mpm_event_module>
StartServers 10
MinSpareThreads 100
MaxSpareThreads 125
ThreadLimit 110
ThreadsPerChild 100
MaxRequestWorkers 600
MaxConnectionsPerChild 0
</IfModule>
設定の見直しとチューニング
メモリ使用量を抑えるためには、
- サーバー起動時のプロセス数を設定する
StartServers
- プロセス数の上限である
ServerLimit
- 全プロセスにおけるスレッド合計を設定する
MaxRequestWorkers
が特に大切かと思います。
また、MaxConnectionsPerChild
を0にするとリクエスト数が増えた時にプロセスが肥大化してしまうので、一定の数値に設定する必要があります。
Apacheの設定
こちらの記事を参考に設定していきます。
メモリ使用量の確認
設定値を計算するためには「プロセスあたりのメモリ使用量」を把握する必要があります。
下記のツールを用いることで物理メモリを実行ユーザーごとにまとめて表示してくれます。
$ cd ~
$ wget https://raw.githubusercontent.com/pixelb/ps_mem/master/ps_mem.py
$ chmod a+x ps_mem.py
$ sudo python ps_mem.py
...
11.1 MiB + 8.8 MiB = 20.3 MiB php-fpm-7.0 (5)
461.4 MiB + 777.5 KiB = 462.1 MiB mysqld
2.4 GiB + 42.5 MiB = 2.5 GiB httpd (4)
---------------------------------
3.0 GiB
=================================
今回の場合、Apacheは2.5GBメモリを使用していて、1プロセスあたり625MB使用していることが分かりました。
また、PHP-FPMは1プロセスあたり4MBでした。
実際の設定
StartServers
、ServerLimit
、MaxRequestWorkers
にどのくらいの値を設定するかですが、
mediumのサイトを参考に、
ServerLimit = (Total RAM - Memory used for Linux, DB, etc.) / process size
MaxRequestWorkers = (Total RAM - Memory used for Linux, DB, etc.) / process size
StartServers (Number of Cores)
となっていて、
「Total RAM」は$ free -m
で確認したメモリ容量、
「Memory used for Linux, DB, etc」はApache以外のメモリ使用量を考慮するためのもので、今回は全体のメモリの15%を取って500MBとします。
「process size」は先ほど確認したApacheの1プロセスあたりメモリ使用量、
「Number of Cores」はサーバーのコア数です。
なので今回の場合は、
ServerLimit = (3757MB - 500MB) / 625MB ≒ 5
MaxRequestWorkers = (3757MB - 500MB) / 625MB ≒ 5
StartServers = 1
で設定します。
スレッドの設定について
スレッドの設定 MinSpareThreads
MaxSpareThreads
ThreadLimit
ThreadsPerChild
の値に関しては、
今回は詳しい説明は省きますが、
スレッド自体は親プロセスのメモリを利用するので、スレッドがメモリ使用量に与える影響は小さいです。
なのでメモリ使用量を抑えるという点では、これらの設定値はそこまで重要ではないです。
ただし最低限、
MinSpareThreads
はMaxSpareThreads
より小さな値を設定する必要があり、
ドキュメントには「ThreadLimit
はThreadsPerChild
の上限を超える値を設定してはいけない」と書かれていますので、同じ値にしておくのが安全でしょう。
最終的には、下記のような設定になりました。
<IfModule mpm_event_module>
ServerLimit 5
StartServers 1
MinSpareThreads 5
MaxSpareThreads 75
ThreadLimit 5
ThreadsPerChild 5
MaxRequestWorkers 5
MaxConnectionsPerChild 300
</IfModule>
PHP-FPMのmax-children
を計算
PHP-FPMもプロセスを生成していますので、こちらの設定も修正する必要があります。
こちらの設定も先ほどのmediumのサイトを参考に設定します。
pm.max_children
が一番重要で、プロセスの上限を設定するものです。
pm.max_children = (total RAM - (DB etc) / process size)
今回は、
pm.max_children = (3757MB - 500MB) / 4MB ≒ 814
で設定をします。
また、pm.min_spare_servers
はコア数×2、
pm.start_servers
、pm.max_spare_servers
はコア数×4、
pm.max_requests
はApacheのMaxConnectionsPerChild
と同様で、同じ値になるように設定します。
最終的に、PHP-FPMは下記の設定となりました。
pm.max_children = 814
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 300
設定を反映
再起動して設定を反映します。
$ service httpd restart
Stopping httpd: [ OK ]
Starting httpd: [ OK ]
$ service php-fpm-7.0 restart
Stopping php-fpm-7.0: [ OK ]
Starting php-fpm-7.0: [ OK ]
改善後のメモリ使用量を確認
上記の設定でしばらく稼働させ、htop
コマンドでメモリ使用量を確認してみます。
改善前は2~3時間でメモリがいっぱいになって落ちてしまっていましたが、
5日経ってもメモリ使用量は30%以下をキープするようになりました!
今回はメモリに限ったチューニングでしたが、アプリケーション側のパフォーマンスが良くないのでそちらの改善を今後やっていきたいです。
サーバーのメモリが限られていてOut Of Memoryになってしまう方がいましたら、
簡単な計算で算出できますので、是非やってみてください。