CLOVER🍀

That was when it all began.

ApacheのMPMを設定を確認する

これは、なにをしたくて書いたもの?

ApacheのMPMの設定…接続数やプロセス数、スレッド数まわりの設定をちょいちょい見る割には覚えていられないので、
1度ちゃんと見ておこうかなということで。

環境

今回の環境は、こちら。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal


$ uname -srvmpio
Linux 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu Linux 20.04 LTSです。

こちらにApacheをインストールします。

$ sudo apt install apache2

起動。

$ sudo systemctl enable apache2
$ sudo systemctl start apache2

Apache 2.4.41がインストールされたようです。

$ apachectl -V
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17
Server's Module Magic Number: 20120211:88
Server loaded:  APR 1.6.5, APR-UTIL 1.6.1
Compiled using: APR 1.6.5, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/apache2"
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="mime.types"
 -D SERVER_CONFIG_FILE="apache2.conf"

デフォルトのMPMは、eventのようですね。

Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)

これで、Apacheの用意はできました。

ApacheのMPMの種類とドキュメント

ApacheのMPMというのは、Multi-Processing Modulesの略です。ホストのポートにバインドしてリクエストを受け付け、
リクエストを処理するために子にディスパッチするモジュールのことを言います。

Apache HTTP Server 2.0 extends this modular design to the most basic functions of a web server. The server ships with a selection of Multi-Processing Modules (MPMs) which are responsible for binding to network ports on the machine, accepting requests, and dispatching children to handle the requests.

Multi-Processing Modules (MPMs) - Apache HTTP Server Version 2.4

どのMPMがデフォルトで使われるかは環境によって決まっているようですが、UnixライクなOSだとprefork、worker、eventのどれかに
なるようですね。

自分の環境だと、eventになりました、と。

netware、os2、winntというMPMもありますが、こちらは今回は置いておきましょう。

worker、event、preforkの各MPMの説明のサマリーを見てみます。今のデフォルトのMPMを見るとevent、worker、preforkの順が
正解な気がしますが、event、workerの内容を見ていると先にworkerを見た方がよい気がしたので。

worker MPMは、マルチプロセスサーバー、マルチスレッドサーバーのハイブリッドな実装です。スレッドを使用することで
プロセスベースのサーバーよりも少ないリソースで多くのリクエストを処理できます。ただ、それぞれが多くのスレッドを管理する
プロセス自体を複数持つことで安定性を保っています。

This Multi-Processing Module (MPM) implements a hybrid multi-process multi-threaded server. By using threads to serve requests, it is able to serve a large number of requests with fewer system resources than a process-based server. However, it retains much of the stability of a process-based server by keeping multiple processes available, each with many threads.

worker - Apache HTTP Server Version 2.4

なので、マルチプロセスサーバーとマルチスレッドサーバーのハイブリッドなわけですね。このmpmで重要なディレクティブは
各プロセスごとのスレッド数をコントロールするThreadsPerChild、それから起動できる最大スレッド数をコントロールする

MaxRequestWorkersのようです。

event MPMは、リスナースレッドにいくつかの処理を渡すことでワーカースレッドを開放し、より多くのリクエストを処理できるように
設計されたMPMです。

The event Multi-Processing Module (MPM) is designed to allow more requests to be served simultaneously by passing off some processing work to the listeners threads, freeing up the worker threads to serve new requests.

event - Apache HTTP Server Version 2.4

設定はworker MPMほぼ同じなのですが、AsyncRequestWorkerFactorというディレクティブが導入されており、こちらで
プロセスごとに受け入れられる接続数をコントロールできます。

prefork MPMは、その名の通りプリフォークなWebサーバーです。親プロセスが実際にリクエストを処理する子プロセスを管理する
モデルです。Apacheに組み込むモジュールやライブラリにスレッドセーフではないものが含まれている場合、互換性のために
使用するような位置づけです。また、リクエストがプロセス単位で分離されているので、あるリクエストが他のリクエストに
影響を与えることはありません。最も重要なディレクティブはMaxRequestWorkers(以前はMaxClientsと呼ばれていたもの)です。

prefork - Apache HTTP Server Version 2.4

MPMに関する共通的なディレクトティブは、こちらに書かれています。

mpm_common - Apache HTTP Server Version 2.4

各MPMの概要は、ざっくりこんな感じですね。

MPMを設定する

これらのMPMの設定を行うことで、Apacheで受け付けられる接続数、そしてリクエストを処理するのに使うプロセス数やスレッド数を
設定するわけですが、これを1度整理したいなと。

各MPMごとに、ドキュメントを読んで書いていきます。

worker MPM

まずはworker MPMの設定を見ていきましょう。

worker - Apache HTTP Server Version 2.4

ディレクティブ名 説明
ThreadsPerChild プロセスごとに生成するスレッド数
ThreadLimit プロセスごとに持てるスレッド数の上限 (ThreadsPerChildと同じ、もしくは大きい値にする)
StartServers 最初に起動するプロセス数
MaxRequestWorkers 同時に処理できる最大数(各プロセスごとのスレッド数の和の上限)
ServerLimit プロセス数の上限(MaxRequestWorkers / ThreadsPerChild より大きい値である必要がある)
MinSpareThreads 待機スレッドの最少数
MaxSpareThreads 待機スレッドの最大数(MinSpareThreadsとThreadsPerChildの和に等しい、もしくは大きい値である必要がある)

Serverがプロセスに関する設定を指し、Threadがスレッドに関する設定を指す、と見ていけば良さそうです。

基本的には、こちらに書かれている内容です。

mpm_common - Apache HTTP Server Version 2.4

なお、プロセス数の指定するディレクティブがありませんが、MaxRequestWorkersの値をThreadsPerChildで割った値で算出されます。

Ubuntu Linux 20.04 LTS/Apache 2.4.41での、worker MPMのデフォルト設定はこちらです。

/etc/apache2/mods-available/mpm_worker.conf

<IfModule mpm_worker_module>
    StartServers             2
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>

MaxConnectionsPerChildというのは、各プロセスが処理するリクエスト数です。0以外の値を指定している場合、
プロセスはMaxConnectionsPerChildで指定された数だけリクエストを処理すると、プロセスが終了します。

MaxConnectionsPerChild Directive

メモリリーク回避のために使われ、サーバープロセスを作り直したい場合に使う感じですね。

0の場合、プロセスはリクエストの処理数で終了しなくなります。

あと、ServerLimitは書かれていませんね。worker MPM、もしくはevent MPMの場合、デフォルト値は16です。

ServerLimit Directive

event MPM

event MPMの設定は、worker MPMとほぼ同じです。AsyncRequestWorkerFactorが増えるだけですね。

event - Apache HTTP Server Version 2.4

ディレクティブ名 説明
AsyncRequestWorkerFactor プロセスごとの同時接続数の制限値
ThreadsPerChild プロセスごとに生成するスレッド数
ThreadLimit プロセスごとに持てるスレッド数の上限 (ThreadsPerChildと同じ、もしくは大きい値にする)
StartServers 最初に起動するプロセス数
MaxRequestWorkers 同時に処理できる最大数(各プロセスごとのスレッド数の和の上限)
ServerLimit プロセス数の上限(MaxRequestWorkers / ThreadsPerChild より大きい値である必要がある)
MinSpareThreads 待機スレッドの最少数
MaxSpareThreads 待機スレッドの最大数(MinSpareThreadsとThreadsPerChildの和に等しい、もしくは大きい値である必要がある)

event MPMは、一部の接続を非同期で処理します。このケースでは、ワーカースレッドは必要に応じて短時間だけ割り当てられ、
その他のケースだと接続ごとにひとつのワーカースレッドが使用されます。ワーカースレッドを使い尽くした場合でも非同期処理が
できるように、非同期処理用のワーカースレッドをどのくらい用意するかという設定です。

event MPMがどのくらいの接続数を扱えるかは、以下に計算式が書かれています。

AsyncRequestWorkerFactor Directive

各プロセスは、接続数が以下の値よりも低ければ、新しい接続を受け付けます。

ThreadsPerChild + (AsyncRequestWorkerFactor × アイドル状態のワーカースレッド数)

アイドル状態のワーカースレッドの平均値がわかる場合、全プロセスでの最大接続数は以下の式で算出できます。

(ThreadsPerChild +( AsyncRequestWorkerFactor × アイドル状態のワーカースレッド数)) × ServerLimit

デフォルトではAsyncRequestWorkerFactorの値は2なので、アイドル状態のワーカースレッドの2倍、非同期用のスレッドとして
扱えることになるわけですね。
※ なのですが、この後で試してみたら(後述)、ちょっとよくわからなくなりました…

Ubuntu Linux 20.04 LTS/Apache 2.4.41での、event MPMのデフォルト設定はこちらです。

/etc/apache2/mods-available/mpm_event.conf

<IfModule mpm_event_module>
    StartServers             2
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>
prefork MPM

最後は、prefork MPMです。

prefork - Apache HTTP Server Version 2.4

ディレクティブ名 説明
StartServers 最初に起動するプロセス数
MaxRequestWorkers 同時に処理できる最大数(各プロセスごとのスレッド数の和の上限)
ServerLimit プロセス数の上限(MaxRequestWorkersよりも大きい値である必要がある)
MinSpareServers 待機プロセスの最少数
MaxSpareServers 待機プロセスの最大数

worker MPMやevent MPMと比べると、スレッドがない分だけシンプルになりましたね。待機分の設定も、スレッドではなくプロセス
(ディレクティブとしてはServer)になっています。

Ubuntu Linux 20.04 LTS/Apache 2.4.41での、prefork MPMのデフォルト設定はこちらです。

/etc/apache2/mods-available/mpm_prefork.conf

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers       5
    MaxSpareServers      10
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>

prefork MPMの場合、ServerLimitのデフォルト値は256です。

各MPMで、起動するプロセスやスレッド、受け付けられる接続数を実際に試して確認してみる

ここまでドキュメントを読みつつ各MPMを見ていきましたが、その設定と効果を実際に動かして確認してみたいと思います。

以下の内容で確認してみましょう。

  • 簡単なHTTPサーバーをApacheとは別に立て、Apacheからはmod_proxy_httpでプロキシする
    • このアプリケーションは、ApacheのMaxRequestWorkers以上の接続数を受け入れられるものとする
    • かつ、とても処理が遅いものとする
  • ApacheのMPMを切り替えながら、Apacheに対して多数のアクセスを行う
    • プロキシ先が遅いので可能な接続数を使い切るはずなので、その時の接続数やプロセス数、スレッド数などを確認してみる
    • Apacheの各MPMの設定は、Ubuntu Linux 20.04 LTS/Apache 2.4.41でのデフォルト設定とする

簡単なHTTPサーバーは、Javaで作ります。Javaのバージョンはこちら。

$ java --version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)

プログラムは、こういうものを用意しました。

SimpleHttpd.java

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class SimpleHttpd {
    public static void main(String... args) throws IOException {
        ExecutorService es = Executors.newFixedThreadPool(200);

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.setExecutor(es);
        server.createContext("/", new SimpleHttpHandler());
        server.start();

        System.out.println(LocalDateTime.now() + " server startup.");
    }

    static class SimpleHttpHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            try {
                TimeUnit.SECONDS.sleep(60);
            } catch (InterruptedException e) {
                // ignore
            }

            byte[] message = "Hello World!!".getBytes(StandardCharsets.UTF_8);

            exchange.sendResponseHeaders(200, message.length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(message);
            }
        }
    }
}

起動は、こんな感じで。

$ java SimpleHttpd.java 
2021-02-07T14:21:33.252448 server startup.

「Hello World!!」と返すだけの簡単なサーバーですが、接続数は200、1リクエストごとに1分のスリープを行います。

このサーバーに対して、mod_proxy_httpでプロキシしましょう。

mod_proxy_httpを有効にして

$ sudo a2enmod proxy_http

Apacheをプロキシするように設定。

$ grep -vE '^\s*#' /etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html


    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined


    ProxyPass / http://localhost:8080
    ProxyPassReverse / http://localhost:8080
</VirtualHost>

確認。

$ time curl -i localhost
HTTP/1.1 200 OK
Date: Sun, 07 Feb 2021 05:30:04 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-length: 13

Hello World!!
real    1m0.584s
user    0m0.013s
sys 0m0.025s

OKです。

アクセス数を出すためのツールとしては、Vegetaを使いました。

GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!

インストール。

$ curl -sLO https://github.com/tsenart/vegeta/releases/download/v12.8.4/vegeta_12.8.4_linux_amd64.tar.gz
$ tar xf vegeta_12.8.4_linux_amd64.tar.gz

今回の環境での、バックログの上限も確認しておきます。

$ sysctl net.core.somaxconn
net.core.somaxconn = 4096

Apacheの設定としてはListenBackLogを使うようで、デフォルトで511のようです(今回は未設定)。

ListenBackLog Directive

worker MPM

まずは、worker MPMから確認しましょう。

event MPMからworker MPMへ切り替え、Apacheを再起動します。

$ sudo a2dismod mpm_event
$ sudo a2enmod mpm_worker
$ sudo systemctl restart apache2

worker MPMに切り替わったことを確認(Server MPM)。

$ apachectl -V
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17
Server's Module Magic Number: 20120211:88
Server loaded:  APR 1.6.5, APR-UTIL 1.6.1
Compiled using: APR 1.6.5, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     worker
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/apache2"
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="mime.types"
 -D SERVER_CONFIG_FILE="apache2.conf"

worker MPMの今回の設定はこちらです(再掲)。

<IfModule mpm_worker_module>
    StartServers             2
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>

起動した時点でのプロセス数は、こうなっています。StartServersが2なので、子プロセスが2つある状態ですね。

$ ps aux | grep apache2 | grep -v grep
root         745  0.0  0.2   6752  4740 ?        Ss   20:14   0:00 /usr/sbin/apache2 -k start
www-data     746  0.0  0.2 1211648 4480 ?        Sl   20:14   0:00 /usr/sbin/apache2 -k start
www-data     747  0.0  0.2 1211648 4480 ?        Sl   20:14   0:00 /usr/sbin/apache2 -k start

スレッド数を見てみましょう。次のものは、PID単位でLWPをグルーピング、カウントしたものです。なので、プロセス自身の数も
含まれます。

$ ps aux -L | grep apache2 | grep -v grep | perl -awln -F'\s+' -e 'print "$F[0] $F[1]"' | sort | uniq -c
      1 root 1778
     27 www-data 1779
     27 www-data 1780

ですが、プロセス数の分を差し引いても26あります。ThreadsPerChildもMinSpareThreadsも25なのですが、
ひとつ多いですね…。

まあ、この内訳には今回はこだわらず進みましょう。

Vegetaでワーカー数を300(今回のMaxRequestWorkersの倍)にして、待ち時間でタイムアウトしないように設定しつつアクセス。

$ echo 'GET http://localhost' | ./vegeta attack -duration 600s -max-workers 300 -timeout 120s

Vegetaでアクセスしている間の、Apacheの状態を見てみます。

子プロセスの数が6になりました。MaxRequestWorkers(150) / ThreadsPerChild(25)は6になるので、設定と合った結果に
なっていますね。

$ ps aux | grep apache2 | grep -v grep
root         745  0.0  0.2   6752  4740 ?        Ss   20:14   0:00 /usr/sbin/apache2 -k start
www-data     746  0.0  0.2 1213632 4480 ?        Sl   20:14   0:00 /usr/sbin/apache2 -k start
www-data     747  0.0  0.2 1213632 4480 ?        Sl   20:14   0:00 /usr/sbin/apache2 -k start
www-data    1646  0.2  0.2 1213632 4540 ?        Sl   20:27   0:00 /usr/sbin/apache2 -k start
www-data    1701  0.2  0.2 1213632 4540 ?        Sl   20:27   0:00 /usr/sbin/apache2 -k start
www-data    1702  0.2  0.2 1213632 4604 ?        Sl   20:27   0:00 /usr/sbin/apache2 -k start
www-data    1807  0.3  0.2 1213632 4540 ?        Sl   20:27   0:00 /usr/sbin/apache2 -k start

一方でスレッド数を見てみると、こうなっています。

$ ps aux -L | grep apache2 | grep -v grep | perl -awln -F'\s+' -e 'print "$F[0] $F[1]"' | sort | uniq -c
      1 root 745
     27 www-data 1646
     27 www-data 1701
     27 www-data 1702
     27 www-data 1807
     27 www-data 746
     27 www-data 747

Apacheのエラーログを見ると、MaxRequestWorkersに達したことが出力されています。

/var/log/apache2/error.log

[Sun Feb 07 20:27:11.011377 2021] [mpm_worker:error] [pid 745:tid 139631994985536] AH00286: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting

プロセスあたりのスレッド数の+1分はちょっとわかっていませんが、概ね説明のとおりですね。

Apacheのバックログも見てみましょう。アクセス元の300ワーカーのうち、150がRecv-Qに入っているのでこちらとも合ってそうです。

$ ss -tnl | grep -E 'State|:80 '
State    Recv-Q   Send-Q     Local Address:Port     Peer Address:Port  Process  
LISTEN   150      511                    *:80                  *:*
event MPM

次は、event MPMを確認しましょう。

event MPMに切り替えます。

$ sudo a2dismod mpm_worker
$ sudo a2enmod mpm_event
$ sudo systemctl restart apache2

確認。

$ apachectl -V
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17
Server's Module Magic Number: 20120211:88
Server loaded:  APR 1.6.5, APR-UTIL 1.6.1
Compiled using: APR 1.6.5, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/apache2"
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="mime.types"
 -D SERVER_CONFIG_FILE="apache2.conf"

event MPMの設定は、こちらです。

<IfModule mpm_event_module>
    StartServers             2
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>

プロセスを確認。worker MPMの時と同じですね。

$ ps aux | grep apache2 | grep -v grep
root        2350  0.0  0.2   6772  4712 ?        Ss   21:02   0:00 /usr/sbin/apache2 -k start
www-data    2352  0.0  0.2 1211668 4436 ?        Sl   21:02   0:00 /usr/sbin/apache2 -k start
www-data    2353  0.0  0.2 1211668 4468 ?        Sl   21:02   0:00 /usr/sbin/apache2 -k start

プロセスごとのスレッド数に関しても同じです。

$ ps aux -L | grep apache2 | grep -v grep | perl -awln -F'\s+' -e 'print "$F[0] $F[1]"' | sort | uniq -c
      1 root 2350
     27 www-data 2352
     27 www-data 2353

では、負荷をかけてみます。

$ echo 'GET http://localhost' | ./vegeta attack -duration 600s -max-workers 300 -timeout 120s

この状態でプロセス数を見ると、worker MPMの時よりも多い9になっています(worker MPMは6)。

$ ps aux | grep apache2 | grep -v grep
root        2350  0.0  0.2   6772  4840 ?        Ss   21:02   0:00 /usr/sbin/apache2 -k start
www-data    2352  0.0  0.2 1213588 4436 ?        Sl   21:02   0:00 /usr/sbin/apache2 -k start
www-data    2353  0.0  0.2 1213660 4468 ?        Sl   21:02   0:00 /usr/sbin/apache2 -k start
www-data    2481  0.0  0.2 1213660 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2534  0.0  0.2 1213340 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2535  0.0  0.2 1211900 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2614  0.0  0.2 1213660 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2615  0.0  0.2 1213660 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2616  0.0  0.2 1213020 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start
www-data    2617  0.2  0.2 1213660 4472 ?        Sl   21:04   0:00 /usr/sbin/apache2 -k start

スレッド数。この計算だと、225スレッドあることになりますね。

$ ps aux -L | grep apache2 | grep -v grep | perl -awln -F'\s+' -e 'print "$F[0] $F[1]"' | sort | uniq -c
      1 root 2350
     27 www-data 2352
     27 www-data 2353
     27 www-data 2481
     27 www-data 2534
     27 www-data 2535
     27 www-data 2614
     27 www-data 2615
     27 www-data 2616
     27 www-data 2617

エラーログを見ると、MaxRequestWorkersに達したことが出力されています。

/var/log/apache2/error.log

[Sun Feb 07 21:04:43.278710 2021] [mpm_event:error] [pid 2350:tid 139858399816768] AH00484: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting

バックログを見ると110なので、150だったworker MPMよりも40減っていますね。

$ ss -tnl | grep -E 'State|:80 '
State    Recv-Q   Send-Q     Local Address:Port     Peer Address:Port  Process  
LISTEN   110      511                    *:80                  *:*

つまり、worker MPMの設定と同じなのに40個の接続を受け入れていることになりますね。

さて、いろいろ計算がわからなくなりました。mod_statusで見ると、225のスレッドがビジーなワーカースレッドと認識されている
ようだったのですが、この225がどう計算したら出てくるのかがよくわかりません…。バックログとMaxRequestWorkersの差である
40もそうなのですが…。

AsyncRequestWorkerFactor Directive

worker MPMより多めになるのはドキュメントからもわかるのですが…この確認は、今回はパスしましょう。

prefork MPM

最後はprefork MPMです。

prefork MPMに切り替えます。

$ sudo a2dismod mpm_event
$ sudo a2enmod mpm_prefork
$ sudo systemctl restart apache2

確認。

$ apachectl -V
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17
Server's Module Magic Number: 20120211:88
Server loaded:  APR 1.6.5, APR-UTIL 1.6.1
Compiled using: APR 1.6.5, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/apache2"
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="mime.types"
 -D SERVER_CONFIG_FILE="apache2.conf"

prefork MPMの設定は、こうですね。

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers       5
    MaxSpareServers      10
    MaxRequestWorkers     150
    MaxConnectionsPerChild   0
</IfModule>

StartServersが5なので、起動時のプロセス数も5です、と。

$ ps aux | grep apache2 | grep -v grep
root        4674  0.0  0.2   6500  4452 ?        Ss   21:33   0:00 /usr/sbin/apache2 -k start
www-data    4675  0.0  0.1   6772  3824 ?        S    21:33   0:00 /usr/sbin/apache2 -k start
www-data    4676  0.0  0.1   6772  3824 ?        S    21:33   0:00 /usr/sbin/apache2 -k start
www-data    4677  0.0  0.1   6772  3824 ?        S    21:33   0:00 /usr/sbin/apache2 -k start
www-data    4678  0.0  0.1   6772  3824 ?        S    21:33   0:00 /usr/sbin/apache2 -k start
www-data    4679  0.0  0.1   6772  3824 ?        S    21:33   0:00 /usr/sbin/apache2 -k start

スレッドに関しては、確認する意味がありません。

$ ps aux -L | grep apache2 | grep -v grep | perl -awln -F'\s+' -e 'print "$F[0] $F[1]"' | sort | uniq -c
      1 root 4674
      1 www-data 4675
      1 www-data 4676
      1 www-data 4677
      1 www-data 4678
      1 www-data 4679

では、負荷をかけてみます。

$ echo 'GET http://localhost' | ./vegeta attack -duration 600s -max-workers 300 -timeout 120s

子プロセスが150まで増えました。

$ ps aux | grep apache2 | grep -v grep | wc -l
151

エラーログを確認すると、MaxRequestWorkersに達したことが出力されています。

/var/log/apache2/error.log

[Sun Feb 07 21:41:02.467235 2021] [mpm_prefork:error] [pid 5049] AH00161: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting

バックログには、150ありますね。

$ ss -tnl | grep -E 'State|:80 '
State    Recv-Q   Send-Q     Local Address:Port     Peer Address:Port  Process  
LISTEN   150      511                    *:80                  *:*

だいたい、雰囲気はわかったのではないでしょうか。

まとめ

よくApacheのMPMの設定がわからなくなるので、この機会にちょっと確認してみました。

event MPMについてはちょっとよくわからないところがあるのですが、気が向いたらもうちょっと確認してみようかなと…。

いったん、雰囲気はわかったので良しとしましょう。