apacheのprefork MPMについて

こんにちは、いなむーです。

今回はapacheのMPM(マルチプロセッシングモジュール)について、再度色々勉強したものをまとめておこうと思います。
(誤りがありましたらご指摘ください!)
また、今回はMPMと言いつつも、preforkモデルの取り扱いが多いので、preforkに焦点をあてて調べていきます。

参考にしたサイトはこちら。apache公式ドキュメント

MPMとは

公式ドキュメント引用
スレッドを使わず、先行して fork を行なう ウェブサーバを実装しています。 スレッドセーフでないライブラリとの互換性をとるために、 スレッドを避ける必要のあるサイトでは、このモジュールの使用が適切でしょう。 あるリクエストで発生した問題が他のリクエストに影響しないように、 個々のリクエストを単離するのにも、最適な MPM です。

親となるプロセスをpre”事前”にfork”分岐”(複製)するのがprefork式です。workerでは、プロセスをforkせずにスレッド同士でメモリなどを共有するので、効率的に処理が行える反面、問題のあるリクエストがあった場合に、共有しているスレッド同士も影響が発生します。それを避けるのに適した方式がpreforkだと書かれています。

各ディレクティブについては様々なサイトで既に書かれていますが、自分用に残しておきます。

StartServers

  • 起動時に生成される子サーバプロセスの数
  • 動的に制御される

    MaxSpareServers

  • アイドル状態のサーバープロセス数の最大個数を設定

    アイドル状態なので、リクエスト処理していないプロセスです。
    リクエストが増えると、MaxClientsまでプロセスをforkしていきます。
    アイドル状態のプロセスが、MaxSpareServersよりも多い状態になった場合は、その値までプロセスをkillしていきます。

    MinSpareServers

  • アイドル状態のサーバープロセス数の最低個数を設定

    これもアイドル状態なので、リクエスト処理していないプロセスです。
    アイドル状態のプロセスがこの値よりも少なくなったら、プロセスは最高で1秒につき1個の割合で新しい子プロセスを生成するようです。

    ServerLimit

  • MaxClientsに設定可能な上限値
  • ServerLimitはデフォルトが256。
  • 256よりも大きい値を設定する場合は、Maxclientsディレクティブよりも上に記述する必要がある。
  • workerモデルの場合に、ThreadsLimitと組み合わせてMaxClientsに設定可能な値を設定する。

    preforkの場合はServerLimitとMaxClientsの値をべつべつに設定する意味がないので、同じ値を設定しておくと良いように思います。

    MaxRequestPerChild

  • 個々の子プロセスが稼働中に扱うリクエスト数の上限
  • 子プロセスがMaxRequestPerChildの値だけリクエストを受け付けると、プロセスからは終了する。

    各プロセスの遷移はこちらのメルカリのエンジニアさんが6年前に書かれた内容が大変分かりやすいです。

    こちらに書かれている通り、forkはリソースを大変消費するため、StartServers,MaxSpareServers,MinSpareServersは値を同じにしておくと良さそうであるということには、納得と理解ができました。

    しかしながら、MaxClientsについては、気になる記述があります。
    正確にはMaxClientsではなく、ServerLimitで、

    公式ドキュメント引用
    このディレクティブを使用する際は特に注意してください。 ServerLimit が必要以上に大きな値に 設定された場合は、余計な未使用共有メモリが割り当てられます。

    という記載がありました。
    しかしながら、上記記事を確認すると、LinuxはCoWがあるのでそこまでメモリを使用しないという記事がありました。


    CoWでは、親プロセスのメモリ空間をコピーし、メモリーのデータはコピーしないため、forkした直後ではメモリーは消費していない状態であるためです。
    もちろん子プロセスそれぞれが稼働していくと、個別にメモリーを確保していくためメモリー使用率はあがっていきます。

    ちなみに、MinSpareServersやMaxSpareServersの値を同じにするのなら、なんでこのディレクティブがあるのか?と思い、かなり調べたのですがそれらしい記述は見当たりませんでした。
    これは恐らくですが、これらのディレクティブが出た当初は、サーバーには複数のミドルウェアと複数の機能を同居させ、リソースも潤沢に無かったであろう時代だったのかと思います。
    Apacheが稼働していない時は、無駄なプロセスは開放していき、他のプロセスにリソースを回すことを優先されたのではないかと勝手に推測しました。
    ※私がサーバー管理をし始めた頃でも、まだ貧弱なサーバーでウェブサイトを運営してたりして、OOM killerとかで死んだとかはよくありました。今では余程メモリをバカ食いするor致命的なメモリリークとか無い限り、OOM killerは見なくなりました。

    色々パラメータについての理解は深まりましたが、実際にプロセスの数はどのような動きをするのかを試してみました。

    MaxSpareServers

    MaxRequestsPerChildに関わらず子プロセスはリクエスト処理が完了すると、すぐにkillされる。


    MaxSpareServers 20
    ServerLimit 30
    MaxClients 30
    MaxRequestsPerChild 40000


    $ ab -n 10000 -c 200 http://localhost/

    プロセス数が31になるまで何度か繰り返す


    grep -fl "/sbin/httpd" | wc -l
    31
    :
    21

    プロセスが31になるが、abが終わるとすぐに21(MaxSpareServersの値)までプロセス数が下がることが分かる。

    MaxRequestsPerChild

    値が低いと子プロセスのkillとforkを頻繁に繰り返す。

    下記では「MaxRequestsPerChild」を「10」にしてテストしてみました。


    StartServers 8
    MinSpareServers 5
    MaxSpareServers 20
    ServerLimit 30
    MaxClients 30
    MaxRequestsPerChild 10

    $ ab -n 3000 -c 30 http://localhost/

    Time taken for tests: 22.767 seconds

    Connection Times (ms)
    min mean[+/-sd] median max
    Total: 1 227 409.3 20 2019

    そうすると、このリクエストでは約23秒かかってます。
    次に、他のパラメータは同じで、「MaxRequestsPerChild」を「100」に設定して、テストしてみます。

    テストパターン2


    MaxRequestsPerChild 10 -> 100

    Time taken for tests: 4.729 seconds
    Connection Times (ms)
    min mean[+/-sd] median max
    Total: 2 47 189.2 4 999

    abも同じでテストしましたが、結果、約5秒まで短縮されました。
    「MaxRequestsPerChild」が少ないと、子プロセスが頻繁にforkされ、killされるので、その間はクライアントは待たされてしまうという結果が出ました。
    この値は公式ドキュメントにもあるようにメモリーリーク対策になるため、起動プロセス数のピーク時に合わせて、値を検討していくと良さそうに思われます。

    その他検証してわかったこと

    MaxClients以上にStartServersを設定しても、プロセスは生成されませんでした。
    当たり前と言えばそうなのですが、「へぇ〜」という気持ちになりました。

    apacheは手軽にインストールでき、安定して稼働する素晴らしいソフトウェアです。
    デフォルト値でもサービス稼働できてしまうミドルウエアの1つでもあります。
    その反面、Webサーバーという多種多様なアプリケーションに対応するため、アクセスが多いサイトでは、デフォルト値では捌ききれない場合があります。
    また、CPUやメモリー、アプリケーションのキャッシュ、ディスクIO、スループット、バックエンドとの接続など、ありとあらゆる要素によって、処理能力に差が出てきます。

    今回の検証で、ベンチマークテストが重要であることを再認識出来た結果と言えます。
    今後もテストケースを重ねて、対象ホストで最適なパラメータチューニングが出来るように精進しようと思いました。

    以上。