Apacheのメモリ消費を抑える(prefork)

2022年12月13日

※以下の記事は古い。Apache2.4以上を含めたpreforkの設定については、Apache2.4 preforkパラメータの設定方法を参照のこと。

apache(httpd)のメモリ消費量を抑える方法を調べてみた。

というのも、最近アクセスが増加しており、それに伴いhttpdがメモリをふんだんに消費してしまうらしく、そのために他のプログラム、特にtomcatが強制終了されてしまうからである。

apacheのマルチプロセシングモジュール

apacheが複数のリクエストを処理するのにMPMというものを使うという。MPMには複数の種類がある。

つまり、リクエストを受けて返答を返すというサイクルを同時に複数行うわけだが、その方式として異なるものがあるというのだ。つまり、

  • マルチプロセス(prefork)
  • マルチスレッド(worker)
  • イベント駆動(event、Apache2.4以上)

今回はCentOS6上のapache2.2が対象であり、このデフォルトはpreforkになっている。そして、eventは2.4以上でしか使えないようだ。おそらくOSやapacheのバージョン等によってデフォルトは異なるものと思われるので注意。

preforkの設定

ということで、preforkを設定してみる。参考としては以下。

prefork方式の場合、apacheは複数プロセスを起動して複数の同時リクエストを処理しており、当然のことがら、

  • プロセス数が少なすぎると同時に処理できなくなる。
  • プロセスが多すぎるとメモリを圧迫する。
  • phpを使う場合には、プロセス内でメモリリークすることがある。

ということだ。

※もちろん、どうチューニングしても、メモリ量に比較してあまりにたくさんのリクエストがある場合には、メモリを増やすしか無い。

したがって、方針としては以下になる。

  • 同時に存在するプロセス数は多すぎず、少なすぎずとする。
  • ある程度の数のリクエストをこなしたら、そのプロセスには死んでもらい、別のプロセスを作る。

ということだ。

で、/etc/httpd/conf/httpd.confを見てみると、以下のような記述があるのだが、

<IfModule prefork.c>
StartServers            8
MinSpareServers         5
MaxSpareServers        20
ServerLimit           256
MaxClients            256
MaxRequestsPerChild  4000
</IfModule>

これが上記を制御するためのパラメータとなる。

これらの意味なのだが、これらの項目名としてはpreforkのみに使われるものではなく、MPM全体に使われるものであるがために理解しがたい点がある。特にServerLimitだ。

マニュアルは以下にある。

以下ではマニュアルの引用も示す。

StartServers

apacheが起動した時に、あらかじめいくつのプロセスを起動しておくのか。これにあまり大した意味は無いかと思う。

StartServers ディレクティブは、 起動時に生成される子サーバプロセスの数を設定します。 プロセス数は負荷に応じて動的に制御されますので、 通常はこの値を調整する理由はあまりないでしょう。

MinSpareServers、MaxSpareServers

空いているプロセスの最小個数と最大個数。上記の例だと、今現在いくつかのプロセスがサービスを行っており、いくつかのプロセスがサービスを行っていないものとすると、そのサービスを行っていない空きのプロセスが最小限何個でなければならないかをMinSpareServersで決める。この数未満の場合には、新たにプロセスが作成される。

逆にMaxSpareServersでは、空きのプロセスの最大数を決める。つまり、20個以上空いてる場合には、余分なプロセスを殺すというわけだ。

MaxSpareServers ディレクティブは、 アイドルな子サーバプロセスの希望最大個数を設定します。 アイドルプロセスとは、リクエストを扱っていないプロセスです。 MaxSpareServers よりも多い数がアイドルであれば、 親プロセスは超過プロセスを kill します。

非常に混んでいるサイトでのみ、このパラメータをチューニングするべきです。 このパラメータを大きくするということは、大抵の場合は悪い発想です。 MinSpareServers 以下に設定した場合、MinSpareServers +1 に自動調整されます。

MaxClients

同時に接続できるクライアントの数。ということは、同時に生きてサービスをするプロセスの最大数となる、はず。

MaxClients ディレクティブは、 応答することのできる同時リクエスト数を設定します。 MaxClients 制限数を越えるコネクションは通常、 ListenBacklog ディレクティブで設定した数までキューに入ります。 他のリクエストの最後まで達して子プロセスが空くと、 次のコネクションに応答します。

スレッドを用いないサーバ (すなわち prefork) では、MaxClients は、リクエストに応答するために起動される 子プロセスの最大数となります。 デフォルト値は 256 で、これを増加させたい場合は、 ServerLimit の値も増加させる必要があります。

ServerLimit

多くが言うにはMaxClientsと同じ値にしろとのこと。

prefork MPM の場合は、このディレクティブは Apache プロセス稼働中における MaxClients に設定可能な上限値を設定することになります (訳注: prefork の場合は同時クライアント数 = サーバプロセス数なので) 。

つまり、この項目はprefork MPMの場合には特に意味を持っておらず、常にMaxClientsと同じにするのが無難ということ。

MaxRequestsPerChild

一つのプロセスが処理する最大のリクエスト数。つまり、この数を越えた場合には、そのプロセスが殺されるということ。これは、メモリリーク対策であるようだ。

特に「phpはリークしてしまう」と、どこかに書いてあった。そのために定期的にプロセスを殺してしまうわけだ。

上記の例では、プロセスが生まれてから4,000回のリクエストをサービスしたら、そのプロセスはお役御免になるということ。

MaxRequestsPerChild ディレクティブは、 個々の子サーバプロセスが扱うことのできるリクエストの制限数を 設定します。MaxRequestsPerChild 個のリクエストの後に、子プロセスは終了します。 MaxRequestsPerChild が 0 に設定されている場合は、プロセスは期限切れにより終了することはありません。

結局どうすれば良いのか?

まず重要なのは、MaxClientsの数だろう。これで同時にアクセスできるクライアントの数が決まる。しかし、同時とは言っても、リクエストを受けてレスポンスを返すまでが一単位であり、かつそこには、ページの中にある画像などは含まれない。それらは別のリクエストになるからだ。

そして、もしこのMaxClientsの数が不足していても、ある程度の待ち時間の後にプロセスの空きができれば、それがサービスを行う。ということは、うちのサーバの場合には256も必要無いと思われる。

そしてServerLimitは同じ値にする。

次に、MaxRequestsPerChildだが、この値によってどの程度プロセスが強制killされるかによるだろうが、明らかに4,000では多すぎると思われる。

とりあえず以下のようにしてみた。

<IfModule prefork.c>
StartServers       8
MinSpareServers    5
MaxSpareServers   10
ServerLimit      25
MaxClients       25
MaxRequestsPerChild  400
</IfModule>