Apache2.4のMPM prefork/worker/eventの違いを理解する


※この記事ではマニュアルに沿って動作の違いを解説したものです。パフォーマンスの差については触れられていないのでご了承ください。
また、どのMPMを選べばいいかもアドバイスできてません。期待してクリックしてくれた方すみません。

はじめに

RHEL8.0(CentOS8.0)からは、Apache HTTP Server(以下Apache)のデフォルトマルチプロセッシングモジュール(以下MPM)がpreforkからeventに変更になりました。
eventにすること自体はかなり前から可能だったようですが、このリリースからはyum(というかDNF)でApacheを落としたら最初からMPMがeventになっているってことですね。

MPMとは

Webサーバーたるもの、外部からのリクエストにさらされる運命です。
でも、全てのリクエストをシリアルに処理していったのでは、同時に複数のリクエストが届いた場合に対処しきれません(たぶん)。
ApacheではMPMを使うことで、この問題に対処しています。

Apache 2.4 で選択できるMPM

Apache 2.4系で選択できるMPMには以下の通りです。

  • prefork
  • worker
  • event

どのMPMをロードするかは、設定ファイルに記載します。デフォルトでは/etc/httpd/conf.modules.d/00-mpm.confに設定されています。

prefork

起動時にApacheの子プロセスを複数用意することで、予めアクセスに備えておく方式です。
プロセスにはコントローラープロセスと子プロセスの2種類があり、同時アクセス数が多ければ、コントローラープロセスが子プロセスをさらに増やして対応します。

以下はApache起動直後のプロセスの状態です。
StartServersディレクティブの初期値が5のため、5つのプロセスがデフォルトで生成されています。
USER列でrootユーザーに紐づいてるのがコントローラープロセス。
apacheユーザーに紐づいているのが子プロセスです。

# ps aux | grep -e httpd -e USER | grep -v grep
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     16272  0.4  0.4 211060  4792 ?        Ss   21:39   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16273  0.0  0.2 211060  2852 ?        S    21:39   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16274  0.0  0.2 211060  2852 ?        S    21:39   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16275  0.0  0.2 211060  2852 ?        S    21:39   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16276  0.0  0.2 211060  2852 ?        S    21:39   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16277  0.0  0.2 211060  2852 ?        S    21:39   0:00 /usr/sbin/httpd -DFOREGROUND

worker

複数の子プロセスを予め起動しておくという点ではpreforkと同じです。
が、各子プロセスがスレッドを持っている点がpreforkと大きく異なります。
スレッドにはリスナースレッドとサーバースレッドの2種類があります。
一つの子プロセスは一つのリスナースレッドと複数のサーバースレッドをもちます。
リスナースレッドがリクエストを待ち受け、手の空いているサーバースレッドに渡します。

preforkでは子プロセス1つで1つのリクエストしか処理できませんでしたが、workerでは子プロセス一つに対してサーバースレッドの数だけリクエストを処理できるわけです。

起動直後のプロセスの状態は以下の通りです。

ps aux | grep -e httpd -e USER | grep -v grep
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     16168  0.0  0.4 211256  4988 ?        Ss   21:33   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16169  0.0  0.2 211004  2828 ?        S    21:33   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16170  0.0  0.5 500220  5400 ?        Sl   21:33   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16171  0.0  0.5 500220  5400 ?        Sl   21:33   0:00 /usr/sbin/httpd -DFOREGROUND
apache   16172  0.0  0.5 500220  5404 ?        Sl   21:33   0:00 /usr/sbin/httpd -DFOREGROUND

ここで注目してほしいのがVSZ(仮想メモリサイズ)列です。workerは子プロセスが複数のスレッドを持つため、preforkの時と比べて子プロセス当たりのメモリサイズが大きくなってます。(起動直後の場合ThreadsPerChild初期値の25のサーバースレッドを持ちます。)

※ところでいつも起動時にメモリサイズの大きくない子プロセスがいますがなんでかわかりません。教えて偉い人

VSZはこの状態で合計1922920KBです。ただし、子プロセスはデフォルトで25のサーバースレッドを持つため、上記の状態で子プロセス数*25=100スレッドがリクエストを待ち受けていることになります。
起動直後の状態を比較すると以下のようになります。

MPM 親プロセス数 子プロセス数 同時リクエスト待ち受け数 子プロセスの仮想メモリサイズ 待ち受け口あたりの仮想メモリサイズ
prefork 1 5 5 1055300KB 211060KB
worker 1 4 100 1711664KB 17117KB

メモリ消費量の点ではworkerの方が優れていることがよくわかりますね。
一方、スレッドセーフではないプログラムを載せるときなどはスレッドを使えないので、リクエストごとにプロセスを分けるpreforkを使うことになります。

event

eventはworkerのバリエーションです。
子プロセスが複数のスレッドを持つ点ではworkerと変わりません。プロセスの状態も以下の通り、workerとほぼ同じ。

# ps aux | grep -e USER -e httpd | grep -v grep
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      3771  0.1  0.4 211268  5004 ?        Ss   20:31   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3772  0.0  0.2 211016  2836 ?        S    20:31   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3773  0.0  0.5 500232  5420 ?        Sl   20:31   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3774  0.0  0.5 500232  5420 ?        Sl   20:31   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3775  0.0  0.5 500232  5424 ?        Sl   20:31   0:00 /usr/sbin/httpd -DFOREGROUND

eventとworkerの違いは、HTTPでのKeepAliveの扱いにあります。
workerの場合、サーバースレッドはレスポンス後もKeepAliveが効いている間手空きになることはありませんでした。
一方eventのサーバースレッドは、レスポンス後はコネクションの管理をリスナースレッドに返してしまいます。そうすることで、このサーバースレッドは手空きのスレッドとして次のリクエストを処理できるわけです。

となると、同時に処理できるリクエストの数はworkerとeventで変わらないけど、eventの方がシリアルに処理していくスピードが速いということになるんでしょうね。

どれを選ぶのか

ここまで引っ張っといて申し訳ないのですが正直event触ったことないので、まだわかんないのですすみません。。。
preforkはメモリを食うので、スレッドセーフでないプログラムを載せるのでない限りworker/eventを選ぶことになると思います。
eventはデフォルトMPMになったことだし、いろいろ検証してみたいですね。

参考URL

MPMについて
https://httpd.apache.org/docs/2.4/ja/mpm.html
httpdプロセスのメモリ使用量について
https://www.atmarkit.co.jp/ait/articles/0509/16/news128_3.html
eventの設定・挙動について
https://qiita.com/rryu/items/5e02ea60e36d7fd956b8