Apacheのevent MPMのパフォーマンスチューニング方法

Apache2.4からMPMモジュールのデフォルトがevent MPMになって久しいですが、それのパフォーマンスチューニングをしようと思うと設定項目がそれなりにあり、なにげに難しいです。ググってもまとまった情報がいまいち無かったのでまとめてみました。

# event MPMの構造
event MPMは基本的にはworker MPMと同じマルチプロセスでマルチスレッドなサーバです。ひとつのプロセス内にひとつのリスナースレッドと複数個のワーカースレッドがあり、さらにそのプロセスが複数個存在するという構造になっています。各プロセスではリスナースレッドが接続を受け付け、それをワーカースレッドに渡して処理を完了させるということを行っています。プロセス内のワーカースレッドの数は固定で、ワーカースレッドの数はワーカープロセス単位で増減します。

![](https://qiita-image-store.s3.amazonaws.com/0/42710/65019e1c-c510-6d92-a393-77fce909c32f.png)

worker MPMではkeep aliveなどの待ちが発生する状況でもワーカースレッドがそのまま待ち続けるため、アイドル状態のワーカースレッドが枯渇して接続が受け付けられなくなる場合がありました。event MPMでは以下の状況の時にワーカースレッドがコネクションをリスナースレッドに返し、アイドル状態に戻れるようになっています。

- keep aliveな接続でレスポンスを返し終わって次のリクエストを待っている時
- 書き込みバッファがいっぱいになって書き込み待ちになった時
- 既にレスポンスを送信しているが接続を閉じるためにクライアントがリクエストを送信終わるのを待っている時

リスナースレッドは待つのが主な仕事ですから、待つ仕事はリスナースレッドに返してしまおうというわけです。返された接続は書き込み可などのイベント発生を待って再びアイドル状態のワーカースレッドに渡されて処理されます。このことによりワーカースレッドの数以上の接続をさばくことができるようになります。worker_mpmではワーカースレッドの数と接続の数は同じでしたが、event MPMではリスナースレッドに返したぶんだけ接続の方が多くなる場合があります。このことは後の設定で重要になってきます。

# 設定項目
ということでチューニング内容は、プロセス内にどれくらいのワーカースレッドを用意して、どれくらいのプロセスを用意するかを決めるということになります。event MPMでは以下のように若干回りくどい感じで指定するようになっています。

![](https://qiita-image-store.s3.amazonaws.com/0/42710/df83dea6-86af-80f4-45c2-808e21efc752.png)

* ``ThreadsPerChild``
    * プロセス内に用意するワーカースレッドの数です。これが設定の基本単位になります。
* ``MaxRequestWorkers``
    * 全プロセス合計のワーカースレッドの総数です。ワーカースレッドの総数がこの数を超えない範囲でプロセスが増減します。したがって総プロセス数×``ThreadsPerChild``を指定する形になります。余りが出る数も指定可能ですが、余りは無視されます。プロセスを増やそうとした時にスレッドの総数がこれを超えてしまったら追加をやめるみたいな感じになっているのでしょう。
* ``MinSpareThreads``
    * アイドルスレッドの下限の個数です。サーバが忙しくなってアイドルスレッドが減少した時にこの数まで増やされます。これも``ThreadsPerChild``の倍数を指定する形になります。
* ``MaxSpareThreads``
    * アイドルスレッドの上限の個数です。サーバが暇になってアイドルスレッドが増えた時にこの数まで減らされます。
* ``StartServers``
    * サーバ起動時に作られるプロセスの数です。プロセスの下限の個数にもなっているような気がします。
* ``ThreadLimit``
    * プロセスごとのワーカースレッドの上限です。管理用領域の大きさの指定らしく、再起動しないと反映されません。``ThreadsPerChild``よりも大きな値を指定する必要があります。
* ``MaxConnectionsPerChild``
    * プロセスが指定した回数の接続を受け付けたらプロセスを終了します。0を指定すると終了しなくなります。

これだけだと何をどれくらいに設定したらよいのかさっぱり分からないので、まずはUbuntu 16.04のApache 2.4でのevent MPMのデフォルトの設定を見てみましょう。

```apache:mpm_event.conf
<IfModule mpm_event_module>
        StartServers             2
        MinSpareThreads          25
        MaxSpareThreads          75
        ThreadLimit              64
        ThreadsPerChild          25
        MaxRequestWorkers        150
        MaxConnectionsPerChild   0
</IfModule>
```

プロセス内には25のワーカースレッドがあり、最大で6個のプロセスが作られ、最初に2個のプロセスが起動されます。アイドルスレッドは最低1プロセス分、最大3プロセス分残されます。

というような設定になっているようですが、実際のところはどうなっているのでしょうか。プロセスとワーカースレッドの状態はスコアボードから参照することができます。

起動直後の完全に暇な状態のサーバのスコアボードは以下のようになっています。

![スコアボードの画像](https://qiita-image-store.s3.amazonaws.com/0/42710/68ded67d-74cf-b916-c8c4-a306f57bea84.png)

まずプロセスとワーカースレッドの数の表があります。起動しているプロセスの数が2個、それぞれのプロセスに25個のワーカースレッドがあるのが分かります。1スレッドビジーなのがあるのはスコアボードへのリクエストを処理しているためです。その下に各ワーカースレッドの状態を表す文字が並んでいます。ワーカースレッドの文字は「_」がアイドル、「W」が書き込み中、「.」が割り当てられていない開きスロットです。全部で150あるのが分かります。

ということで読み取ったとおりに動作しているようです。

# MaxConnectionsPerChildとMaxSpareThreads

サーバのパフォーマンスを考慮すると、余計な確保・破棄が発生しないように最大の構成で固定されていた方が良いと思われます。ただ、そのようにするとメモリーリークがあった場合にたった数バイトとしても積もり積もって大変な状態になることが考えられます。特にApacheには``MaxConnectionsPerChild``という定期的にプロセスを終了させる設定項目があるのでその辺の自信の無さが伺えます。

ただ``MaxConnectionsPerChild``だとサーバが忙しい時にプロセスが終了してしまってひどいことになる可能性があります。Ubuntuのデフォルト設定では、かわりに``MaxSpareThreads``を使用していると考えられます。サーバが暇な時に``MaxSpareThreads``までプロセスが減るので、ある程度のプロセスの入れ替わりが発生するという訳です。

# AsyncRequestWorkerFactor

ここまでは実はworker MPMと同じ設定項目です。event MPM独自の設定項目として``AsyncRequestWorkerFactor``があります。event MPMでは待ち状態の接続をリスナースレッドに返すということを行っていますが、このリスナースレッドに返された接続を「非同期接続」と呼びます。非同期接続を生かすためには接続の数はワーカースレッドよりも多い必要があります。この接続の数を指定するのが``AsyncRequestWorkerFactor``です。接続の総数は以下の式で求められます。

``ThreadsPerChild`` * (``AsyncRequestWorkerFactor`` + 1)

デフォルト値は2ですから非同期接続用にワーカースレッドの2倍の数が用意されているということになります。思ったよりも多く用意されているようです。

とはいえ非同期接続もアイドルワーカーがなければ処理できないので、無制限に接続を受け付けていくと非同期接続が溜まって処理できなくなってしまう可能性があります。アイドルワーカーが無くて処理できなかった非同期接続は接続を閉じて破棄するようです。そのような状態にならないように接続の受け付けにスロットルが設けられています。以下の条件を満たすときに新しい接続を受け付けるようになっています。

現在の接続数 <= ThreadsPerChild + (AsyncRequestWorkerFactor * アイドルワーカー数)

アイドルワーカー数の数によって非同期接続用の余裕を設定して、現在の接続がそれを上回っている場合は接続の受け入れを停止して非同期接続が消化されるのを待つという訳です。ただ一度acceptしたソケットを戻すことはできないと思うので、そのような状態になった場合は接続のリセットが多発する状況になってしまうと思われます。

# まとめ

ということで設定の指針としてはこんな感じになるのではないかと思います。

* ``MaxRequestWorkers``
    * 同時リクエスト処理数
* ``ThreadsPerChild``
    * アイドルワーカーが枯渇しない程度の数
    * プロセスの入れ替えに対する適度な大きさ
* ``MinSpareThreads``
    * ``MaxSpareThreads``と同じでいいような気がする
* ``MaxSpareThreads``
    * サーバが忙しい時に十分な数で、暇な時にプロセスの数が減る数