Apache HTTP Serverのパフォーマンス最適化とモジュール設定

1. Apacheのマルチプロセッシングモジュール(MPM)

Apache HTTP Serverは、クライアントからのリクエストを効率的に処理するために複数のマルチプロセッシングモジュール(MPM)を提供しています。主要な安定版として、Prefork、Worker、Eventの3つのモードが存在します。

1.1. Prefork MPM

Preforkは、非スレッド型のプロセスベースのウェブサーバーモデルです。Apacheが起動する際に、事前に複数の子プロセスを生成し、これらのプロセスが接続を待機します。これにより、頻繁なプロセス生成および破棄のオーバーヘッドを削減します。各子プロセスは単一のスレッドのみを持ち、一度に一つのリクエストを処理します。

長所:

  • 高い安定性を誇り、スレッドセーフティの問題を考慮する必要がありません。
  • 新旧の様々なモジュールとの互換性が良好です。
  • 各プロセスが独立したメモリ空間を使用するため、一つのプロセスに障害が発生しても他のプロセスには影響しません。

短所:

  • プロセスあたりのリソース消費(特にメモリ)が大きく、大量のメモリを使用します。
  • 高並行処理のシナリオには不向きです。
  • 現在ではあまり使用されず、Apache 2.4以降のデフォルトはWorker MPMです。

設定においては、MaxRequestWorkers(旧MaxClients)の値を、物理メモリ容量を超過しない範囲で十分に大きく設定することが重要です。

1.2. Worker MPM

Worker MPMは、マルチプロセスとマルチスレッドを組み合わせたハイブリッドモデルです。Preforkと同様に起動時に複数の子プロセスを生成しますが、各子プロセスはさらに複数のスレッドを生成し、これらのスレッドがリクエストを処理します。リスニングスレッドも含まれます。スレッドはプロセスよりも軽量であり、親プロセスのメモリ空間を共有するため、メモリ消費を抑えることができます。

長所:

  • 高並行シナリオにおいてPreforkよりも優れたパフォーマンスを発揮し、少ないリソースでより多くのリクエストを処理できます。
  • 大量のリクエストを効率的に処理可能です。

短所:

  • マルチスレッドを使用するため、スレッドセーフティの考慮が必要です。
  • 一つのスレッドに問題が発生した場合、同一プロセス内の他のスレッドにも影響を及ぼす可能性があります。
  • Keep-Alive(持続的接続)が有効な場合、スレッドがタイムアウトするまで解放されないことがあります(Preforkでも同様の課題が存在します)。

1.3. Event MPM

Event MPMはApache 2.4以降で安定稼働する最新のワークモードです。Worker MPMと多くの点で類似していますが、Keep-Alive接続におけるスレッドリソースの非効率な占有問題を解決しています。Event MPMでは、Keep-Aliveタイプの接続を管理するための専用スレッドが存在し、実際のデータ送信がないアイドル状態の間は、リクエストを処理するワーカースレッドを解放できます。これにより、高並行処理時のリクエスト処理能力がさらに向上します。

長所:

  • Keep-Alive接続を効率的に管理し、アイドル状態の接続でスレッドリソースを浪費しません。
  • 高並行シナリオにおいてWorker MPMよりも優れたパフォーマンスを発揮します。

1.4. MPMの選択と設定

Apacheのコンパイル時に --with-mpm={prefork|worker|event} オプションを使用して、特定のMPMを静的に組み込むことができます。また、--enable-mpms-shared=all を指定して全てのMPMを共有モジュールとしてビルドし、httpd.conf ファイル内で LoadModule ディレクティブを使って任意のMPMを選択的に有効化することも可能です(選択したいMPMのLoadModule行を有効にし、他のMPMの行をコメントアウトします)。

2. 現在のMPMの確認方法

現在稼働中のApacheがどのMPMを使用しているかを確認するには、以下のコマンドを実行します。

2.1. httpd -l を使用

このコマンドは、Apacheに静的にコンパイルされたモジュールを一覧表示します。MPMの名前が含まれているはずです。

# Apacheのバイナリパスに合わせて調整してください
$ /usr/local/apache/bin/httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c
  event.c   # ここに現在のMPMが表示されます

2.2. httpd -V を使用

このコマンドは、Apacheのバージョン情報とコンパイル時の設定詳細を表示します。出力の「Server MPM」の項目で現在のMPMを確認できます。

# Apacheのバイナリパスに合わせて調整してください
$ /usr/local/apache/bin/httpd -V
Server version: Apache/2.4.40 (Unix)
Server built:   Sep 15 2023 10:30:00
Server's Module Magic Number: 20120211:84
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   # 現在のMPMがここに表示されます
  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)
# ... (その他、コンパイルオプションの詳細)

3. Apache MPMの切り替え(ソースインストール版)

ソースからインストールされたApacheの場合、MPMの切り替えにはApacheの再コンパイルが必要になることがあります。特にMPMが静的にコンパイルされている場合は、以下の手順で新しいMPMに切り替えます。

3.1. 現在のMPMとコンパイル設定の確認

まず、既存のMPMがどのようにインストールされているか、および現在のコンパイル設定を確認します。

# モジュールが静的にコンパイルされているか共有モジュールかを確認
$ /usr/local/apache/bin/apachectl -M | grep mpm
 mpm_event_module (static) # 例: eventモジュールが静的にコンパイルされている場合

# 詳細なコンパイルオプションを確認
$ /usr/local/apache/bin/httpd -V
# ... (中略) ...
Server MPM:     event
# ... (中略) ...

3.2. MPMを変更して再コンパイルする

既存のコンパイルオプションを基に、--with-mpm オプションだけを変更してApacheを再コンパイルします。ここでは、Event MPMからWorker MPMに切り替える例を示します。

# Apacheのソースコードディレクトリへ移動
$ cd /usr/local/src/httpd-2.4.40/

# configureスクリプトを再実行し、MPMをworkerに変更
# (既存の --prefix, --enable-so などのオプションは保持)
$ ./configure --prefix=/usr/local/apache --enable-so --enable-rewrite --enable-ssl --enable-deflate --enable-expires --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util --with-mpm=worker

# makeコマンドでコンパイル
$ make

# 既存のhttpdバイナリをバックアップ
$ mv /usr/local/apache/bin/httpd /usr/local/apache/bin/httpd.bak

# 新しくコンパイルされたhttpdバイナリを配置
$ cp httpd /usr/local/apache/bin/

# Apacheサービスを再起動(gracefulリスタートが推奨)
$ /usr/local/apache/bin/apachectl graceful

3.3. 切り替えの確認

再起動後、httpd -V コマンドを実行して、Server MPMが正しく変更されていることを確認します。

$ /usr/local/apache/bin/httpd -V
Server version: Apache/2.4.40 (Unix)
# ... (中略) ...
Server MPM:     worker # MPMがworkerになっていることを確認
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
# ... (中略) ...

もし、Apacheを --enable-mpms-shared=all でコンパイルしている場合は、/usr/local/apache/conf/httpd.conf を編集し、LoadModule ディレクティブで目的のMPM(例: mod_mpm_worker.so)を有効にし、他のMPMモジュールはコメントアウトするだけで切り替えが可能です。

4. Apache MPMのパフォーマンスチューニング

各MPMには、その動作を制御するための固有のチューニングパラメータがあります。これらのパラメータをシステムの特性やトラフィックパターンに合わせて調整することで、Apacheのパフォーマンスを最適化できます。通常、これらの設定は httpd.conf からインクルードされる conf/extra/httpd-mpm.conf ファイルで行われます。

4.1. Prefork MPM の設定パラメータ

Prefork MPMはプロセスベースであるため、主に子プロセスの数を制御するパラメータを設定します。

<IfModule mpm_prefork_module>
    StartServers             8       # Apache起動時に作成される初期子プロセス数
    MinSpareServers          5       # 最小のアイドル状態の子プロセス数
    MaxSpareServers         15       # 最大のアイドル状態の子プロセス数
    MaxRequestWorkers      256       # 同時に処理できる最大リクエスト数(プロセス総数)
    MaxConnectionsPerChild   0       # 各子プロセスが処理する最大接続数 (0は無制限)
</IfModule>
  • StartServers: Apache起動時に最初に生成される子プロセスの数です。
  • MinSpareServers: アイドル状態の子プロセスの最小数を設定します。この数を下回ると、Apacheは新しい子プロセスを生成して数を補充します。
  • MaxSpareServers: アイドル状態の子プロセスの最大数を設定します。この数を超えると、Apacheは余分な子プロセスを終了させます。この値が大きすぎると、不必要なメモリを消費するため注意が必要です。
  • MaxRequestWorkers: 同時に処理できるリクエストの最大数、すなわちアクティブな子プロセスの最大数を指定します。これを超えるリクエストはキューに入れられ、処理を待つことになります。
  • MaxConnectionsPerChild: 各子プロセスが処理できるリクエストの最大数です。この数に達すると、プロセスは終了し、新しいプロセスが起動されます。0に設定すると無制限になります。メモリリークの可能性を回避し、プロセスを定期的にリフレッシュするために、非ゼロの値に設定することが推奨されます。Keep-Alive接続の場合、一つの長期間接続全体で1リクエストとカウントされます。

4.2. Worker MPM および Event MPM の設定パラメータ

WorkerおよびEvent MPMは、プロセスとスレッドの組み合わせを使用するため、プロセス数とスレッド数の両方を制御するパラメータを設定します。Event MPMの設定はWorker MPMとほぼ同じです。

<IfModule mpm_worker_module>
    StartServers             4       # 起動時の子プロセス数
    MinSpareThreads         75       # 最小のアイドルスレッド数(サーバー全体)
    MaxSpareThreads        200       # 最大のアイドルスレッド数(サーバー全体)
    ThreadsPerChild         25       # 各子プロセスが持つスレッド数
    MaxRequestWorkers      400       # 同時に処理できる最大リクエスト数(スレッド総数)
    MaxConnectionsPerChild   0       # 各子プロセスが処理する最大接続数
</IfModule>
  • StartServers: Apache起動時に生成される初期子プロセスの数です。通常、CPUコア数やその2倍程度に設定されることが多いです。
  • MinSpareThreads: サーバー全体で維持されるべきアイドル状態のスレッドの最小数です。この数を下回ると、Apacheは追加のスレッドを生成します。
  • MaxSpareThreads: サーバー全体で維持されるアイドル状態のスレッドの最大数です。この数を超えると、余分なスレッドは終了されます。
  • ThreadsPerChild: 各子プロセスが生成するスレッドの数を指定します。この値が大きすぎると、個々のプロセスが不安定になる可能性があります。したがって、この値を増やすよりも、StartServers を増やしてプロセス数を増やす方が安定性に寄与することがあります。
  • MaxRequestWorkers: 同時に処理できるリクエストの最大数(スレッドの総数)です。この値は通常 StartServers * ThreadsPerChild の積として計算されます。この値を超過するリクエストはキューに入れられます。
  • MaxConnectionsPerChild: 各子プロセスが処理できる接続の最大数です。0は無制限です。この値もメモリリーク対策やリソース解放のために非ゼロに設定することが推奨されます。

サーバーリソースとMPM設定の考慮点:

  • **最大子プロセス数の決定**: サーバーが利用可能な総メモリからシステムが使用するメモリ(例: 8GBサーバーで2GB、16GBサーバーで4GBなど)を差し引き、残りのメモリを子プロセスあたりの平均メモリ使用量で割ることで、最大子プロセス数を見積もることができます。
  • **CPUとメモリの比率**: アプリケーションの特性に応じて、サーバーのCPUとメモリの最適な比率を考慮することも重要です。
    • 汎用的なWebサーバー: CPU 1 : メモリ 4
    • 計算集約型サーバー: CPU 1 : メモリ 2
    • メモリ集約型サーバー: CPU 1 : メモリ 8

5. mod_rewrite の設定と利用

mod_rewrite モジュールは、URLのパターンマッチングと書き換え機能を提供し、リダイレクト、内部転送、URLの正規化など、強力な柔軟性を可能にします。Perl互換の正規表現を使用します。

5.1. mod_rewrite の有効化

mod_rewrite を利用するには、まずApacheにこのモジュールがインストールされ、有効化されている必要があります。

  • **コンパイル時に有効化**: Apacheをコンパイルする際に --enable-rewrite オプションを指定します。
  • **apxs ツールで後からインストール**: Apacheインストール後、apxs ツールを使用してモジュールをコンパイル・インストールし、有効化できます。
# mod_rewrite.c のパスを確認
$ find /usr/local/src/httpd-2.4.40 -name "mod_rewrite.c"
/usr/local/src/httpd-2.4.40/modules/mappers/mod_rewrite.c

# apxs を使ってモジュールをコンパイルし、httpd.confにLoadModuleディレクティブを追加
$ /usr/local/apache/bin/apxs -cia /usr/local/src/httpd-2.4.40/modules/mappers/mod_rewrite.c

# モジュールが正しくインストールされたことを確認
$ ls /usr/local/apache/modules/mod_rewrite.so
/usr/local/apache/modules/mod_rewrite.so

# httpd.conf に LoadModule rewrite_module modules/mod_rewrite.so が存在し、有効になっていることを確認
$ vim /usr/local/apache/conf/httpd.conf

# Apacheサービスを再起動(または graceful リロード)
$ /usr/local/apache/bin/apachectl graceful

# モジュールがロードされていることを確認
$ /usr/local/apache/bin/apachectl -M | grep rewrite
rewrite_module (shared)

5.2. RewriteRuleの主なフラグ

RewriteRule ディレクティブの末尾に付加されるフラグは、書き換えの動作を詳細に制御します。

  • R[=code] (redirect): 強制的に外部リダイレクトを実行します。ブラウザは指定されたURLへリダイレクトされます。code を指定しない場合、デフォルトでHTTP 302 (一時的な移動)が使用されます。
  • L (last): 現在のルールが最終ルールであることを示し、これ以降のRewriteRuleの処理を停止します。
  • NC (nocase): パターンマッチングにおいて大文字小文字を区別しないようにします。
  • QSA (qsappend): クエリ文字列を既存のものに追加します。
  • P (proxy): リクエストを内部的にプロキシ(転送)します。
  • F (forbidden): URLへのアクセスを禁止し、HTTP 403 Forbiddenレスポンスを返します。
  • G (gone): URLを「存在しない」ものとして扱い、HTTP 410 Goneレスポンスを返します。
  • C (chained with next rule): 次のルールと連結します。このルールがマッチした場合のみ、次の連結されたルールが処理されます。
  • NE (no URI escaping of output): 置換文字列内の特殊文字をエスケープしません。
  • S=num (skip next rule(s)): 次の num 個のRewriteRuleをスキップします。

5.3. Rewriteルールの記述例(ドメイン正規化)

すべてのアクセスを特定のドメイン(例: www.example.jp)に正規化するRewriteルールの例です。

# httpd.conf または .htaccess ファイルに記述
<IfModule mod_rewrite.c>
    RewriteEngine On # Rewrite機能を有効化

    # ホストが www.example.jp でない
    RewriteCond %{HTTP_HOST} !^www\.example\.jp [NC]
    # ホストが特定のIPアドレスでない
    RewriteCond %{HTTP_HOST} !^192\.0\.2\.10 [NC]
    # ホストが空でない
    RewriteCond %{HTTP_HOST} !^$
    # 上記の条件に合致した場合、www.example.jp へ301リダイレクト
    RewriteRule ^/(.*)$ http://www.example.jp/$1 [L,R=301]
</IfModule>
  • RewriteEngine On: mod_rewrite の機能を有効にします。
  • RewriteCond: RewriteRule が適用される条件を指定します。複数のRewriteCondがある場合、全てがAND条件で評価されます。
    • %{HTTP_HOST}: クライアントからのリクエストに含まれる Host ヘッダの値を取得します。
    • !: 条件の否定を意味します。
    • ^www\.example\.jp: 正規表現で www.example.jp で始まる文字列にマッチします。ドット(.)は正規表現の特殊文字なのでエスケープします。
    • [NC]: 大文字小文字を区別しません。
  • RewriteRule: 実際の書き換えルールを定義します。
    • ^/(.*)$: リクエストされたURIのパターンです。ルートパス (/) から始まる全ての文字列にマッチし、(.*) でキャプチャした部分が $1 で参照されます。
    • http://www.example.jp/$1: 置換文字列です。キャプチャしたURIパスを新しいドメインの後に付加します。
    • [L,R=301]: L (last) フラグにより、このルールが適用されたら以降のRewriteルールは処理されません。R=301 フラグにより、恒久的な移動を意味するHTTP 301ステータスコードで外部リダイレクトを実行します。

6. ホットリンク対策

他サイトが自身のコンテンツ(画像、動画など)を直接リンクして表示する「ホットリンク」は、サーバーの帯域幅とリソースを不当に消費する原因となります。mod_rewrite を使用してホットリンクを防止できます。

# httpd.conf または .htaccess ファイルに記述
<IfModule mod_rewrite.c>
    RewriteEngine On

    # リファラが空でない場合(直接アクセスではない)
    RewriteCond %{HTTP_REFERER} !^$
    # リファラが自サイト(mywebsite.com)からのものでない場合
    RewriteCond %{HTTP_REFERER} !^https?://(www\.)?mywebsite\.com/.* [NC]
    # 特定の画像・メディアファイルへのアクセスを禁止し、403 Forbidden を返す
    RewriteRule .*\.(gif|jpg|png|swf|jpeg)$ - [F]
</IfModule>
  • %{HTTP_REFERER}: クライアントがどのページからリンクをたどってアクセスしてきたかを示すHTTPヘッダの値を取得します。
  • 最初の RewriteCond は、リファラヘッダが空でない(つまり、直接アクセスではない)ことを確認します。
  • 2番目の RewriteCond は、リファラが mywebsite.com ドメイン(サブドメインを含む、HTTPまたはHTTPS)からではないことを確認します。
  • RewriteRule は、.gif, .jpg, .png などの拡張子を持つファイルへのリクエストに対して適用されます。- はURLを置換しないことを意味し、[F] フラグはアクセスを拒否してHTTP 403 Forbiddenステータスを返します。

7. ディレクトリ一覧表示の禁止

ディレクトリインデックス(一覧表示)が有効になっていると、Webサーバーのディレクトリ構造や内容が外部から閲覧可能になり、セキュリティ上のリスクとなります。これを無効にすることが強く推奨されます。

# httpd.conf ファイルに記述
<Directory "/var/www/html/mysite">
    Options FollowSymLinks # Indexes オプションを削除
    AllowOverride All
    Require all granted
</Directory>
  • <Directory> ディレクティブ内で、Options Indexes を削除するか、または Options -Indexes を明示的に追加することで、ディレクトリの一覧表示機能を無効にできます。
  • Options FollowSymLinks はシンボリックリンクをたどることを許可します。

8. 特定ディレクトリでのPHPファイル解析の禁止

ユーザーがファイルをアップロードするディレクトリなど、PHPスクリプトの実行を許可すべきではない場所では、セキュリティのためにPHPファイルの解析を禁止する必要があります。これにより、悪意のあるPHPスクリプトのアップロードと実行を防ぎます。

# httpd.conf ファイルに記述
<Directory "/var/www/html/uploads"> # ユーザーアップロードディレクトリの例
    <FilesMatch "\.php$"> # .php 拡張子を持つファイルにマッチ
        Require all denied # Apache 2.4以降でのアクセス拒否
    </FilesMatch>
    # 他の必要な設定...
</Directory>
  • <Directory> ブロック内で、PHPファイルの解析を禁止したいディレクトリを指定します。
  • <FilesMatch "\.php$"> は、正規表現 \.php$ にマッチするファイル(つまり、.php で終わるファイル)に対して内部の設定を適用します。
  • Require all denied は、Apache 2.4以降で、そのファイルへのすべてのアクセスを拒否する設定です。Apache 2.2以前では Order allow,denyDeny from all を使用します。

9. CDNによるWebサイトの高速化

コンテンツデリバリーネットワーク(CDN)は、Webサイトのコンテンツ(画像、CSS、JavaScriptファイルなど)を、世界中に分散配置されたキャッシュサーバーに複製・配信することで、Webサイトの高速化を実現します。

ユーザーがWebサイトにアクセスすると、DNSの仕組みを利用して、最も地理的に近いCDNエッジサーバーからコンテンツが提供されます。これにより、データの転送経路が短縮され、ユーザーの待ち時間が大幅に削減されます。静的コンテンツをCDNにオフロードすることで、Webサイトの応答時間が20%以上短縮されることも珍しくありません。

CDNの利用は、オリジンサーバーの負荷軽減にも繋がり、トラフィックの急増時にも安定したサービス提供を可能にします。

10. Apacheウェブサイトアーキテクチャの最適化

堅牢で高性能なWebサイトを構築するためには、適切なアーキテクチャ設計が不可欠です。特に、プログラムページ、画像・添付ファイル、ユーザーアップロードなどの機能を分離することが推奨されます。

10.1. 分離の方法

機能分離には主に以下の2つの方法があります。

  1. 独立したサーバーの使用:

    最も理想的なのは、プログラムページを処理するアプリケーションサーバー、画像や静的ファイルを配信するアセットサーバー、ユーザーがファイルをアップロードするアップロードサーバーをそれぞれ独立したサーバーで運用することです。この方法では、アプリケーション側での対応も必要となります。

  2. フロントエンドロードバランサーによる振り分け:

    NginxやHAProxyのようなロードバランサーをフロントエンドに配置し、ユーザーからのリクエストURLのパスやファイル拡張子に基づいて、バックエンドの適切なサーバーへトラフィックを振り分けます。

    • 例1: http://www.example-service.com/assets/images/logo.png のような画像ファイルへのリクエストは、画像アセットサーバー(またはCDN)に転送します。
    • 例2: http://www.example-service.com/app/user/profile.php のようなアプリケーションロジックを含むリクエストは、アプリケーションサーバーへ転送します。
    • 上記いずれのルールにも合致しないリクエストは、デフォルトのWebサーバー(通常はアプリケーションサーバー)へルーティングします。

    この分離により、各サーバーはそれぞれの役割に特化し、リソースの競合を減らし、スケーラビリティと耐障害性を向上させることができます。

タグ: Apache MPM Prefork Worker event

5月21日 06:33 投稿