Thursday, September 29, 2011

Apacheパフォーマンスチューニング

VPS構築ついでにApacheの最適化を行おうと思い立ち、例によって備忘録ついでに書いておきます。

この記事の内容
  • はじめに
  • ホスト名逆引きの停止
  • 持続的接続の調整
  • MPMの調整
  • コンテンツの圧縮転送
  • ログの整理
  • 不要なモジュールの停止

はじめに

この記事はApache HTTPDの動作効率改善を目的とし、その最適化を行うものです。環境や条件によって最適解は異なってきますので、あくまで参考としてご利用ください。

なお、この記事では主にApache 2.0系を対象にしています。その他のバージョンでは設定内容が異なることがありますのでご注意ください。

ホスト名逆引きの停止

Apacheは標準でリクエスト毎にDNSの逆引きを行なっており、ホスト名をログに記録したりドメインベースでの認証に利用しています。しかしこの処理は非常に遅く、リクエストの処理もそれだけ遅れてしまいます。

特に事情がなければ逆引きを停止してしまいましょう。ただしPHPなどにはホスト名を渡すようにします。

HostnameLookups off
<File ~ "\.(py|pl|php|cgi)$">
    HostnameLookups on
</File>

持続的接続の調整

これはクライアントとの接続を一定時間持続させ再接続のコストを軽減する「KeepAlive」。これはぜひ有効にしたいものですが、この設定を誤ると処理効率が悪化し、逆にこれを無効にしたり極端に短くすると負荷が増えてしまうという悩ましい設定です。

KeepAliveTimeoutは一般的には2秒程度がもっとも効率がいいようです。MaxKeepAliveRequests(持続接続で一定以上リクエストをこなしたら一旦切断する)は1ページで読み込む平均的なファイル数に少し余裕を加えた数を指定します。

KeepAlive On
KeepAliveTimeout 2
MaxKeepAliveRequests 50

MPMの調整

MPM(Multi Processing Module)の設定は、もっとも大きくパフォーマンスに関わる部分です。主に「同時並行でどれだけのリクエストを処理するか」に関わる設定で、大きくするとそれだけサーバーの負荷が増大し、小さくしすぎると大量のリクエストを捌ききれなくなってしまいます。

prefork

標準の動作モードであるprefork MPMでは、プロセスの数を設定します。この設定は標準のままにしておくとメモリの少ないサーバーで過負荷になることがあります。特にPHPなどを走らせればそれだけメモリを食いますので、サーバーにあわせて設定しておきましょう。

説明
<IfModule prefork.c>
    StartServers       8
    MinSpareServers    5
    MaxSpareServers   20
    ServerLimit      256
    MaxClients       256
    MaxRequestsPerChild  4000
</IfModule>

StartServersは起動時に生成するサーバープロセスの数です。MinSpareServersMaxSpareServersはそれぞれクライアントの要求を待ち受けるアイドルプロセスの最小・最大数を指定します。これらの値は非常にアクセスの多いサーバーを除いて初期値のまま運用すべきであり、設定次第ではパフォーマンスが大きく低下してしまう恐れもあります。

MaxClientsは同時に処理できるクライアントの数、つまり最大同時接続数を設定します。preforkでは一つの接続に対しプロセスが一つ割り当てられるため、最大常駐プロセス数を制限するServerLimitは通常MaxClientsと同じ値にします。

プロセスはMaxRequestsPerChildの数だけリクエストを処理すると強制終了し再生成されます。0にすると無制限になりますが、メモリリークがあると悲惨なことになります。プロセスの再生成にはそれなりのコストがかかるとはいえ、できれば定期的に終了しメモリを解放すべきでしょう。

サーバーにあった設定を

MaxClients=ServerLimitは、設定を大きくすればそれだけメモリを消費します。たとえば1つのプロセスが平均2MBのメモリを消費するなら、標準設定の256個だと最大で2×256=512MBが使用されます。メモリが底をつくとスワップに入りますが、アクセス過多時にスワップが起こると極端にパフォーマンスが低下します。

静的リソースばかりならまだしも、PHPなどを走らせる場合は特に注意が必要です。軽量なスクリプトやとても行儀よく設計されたアプリケーションはともかく、たとえばWikiなどでは内容の多いページでかなりのメモリを消費します。php.iniのmemory_limitを大きくしすぎない(デフォルトでは128MB!)のも重要ですが、なにより平均的なメモリ使用量を監視して適切な設定を行うようにしましょう。

MaxRequestsPerChildも同様で、特にmod_perl、modo_phpなどを動作させる場合は念のため50-100程度の値にすべきでしょう。そうでない場合もあまり多くない値にしておくのが安全です。

worker

大量のアクセスをより効率よく処理できるworker MPMでは、プロセス数ではなくスレッド数を設定します(他にもさらに効率のいいevent MPMなどがあります)。

ちなみにマルチプロセスモデルのpreforkが仕組み上プロセス数(つまり同時処理するリクエストの数)に応じてメモリを消費するのに対しworkerは行儀よくメモリを節約するなど高効率な処理が望めるのですが、PHPなど一部のモジュールが正常に動作しないという話もあり(詳細)、導入には慎重な姿勢が必要です。

設定

Note: 一般的にはpreforkで動作させているケースがほとんどですので、ここでは概要のみ説明します。

基本的にはpreforkと似通っていますが、こちらはMinSpareServersおよびMaxSpareServersのかわりにそれぞれMinSpareThreadsMaxSpareThreadsで待機スレッド数の上限と下限を設定します。

workerでは一つのスレッドが一つの接続を受け持つため、最大同時接続数(MaxClients)はすなわち最大スレッド数となります。スレッドはプロセス起動時にThreadsPerChildの数だけ生成され、スレッド総数はプロセスの生成・削除により増減されます。

コンテンツの圧縮転送

非圧縮のテキストファイルをそのまま転送するのはとても非効率です。できれば圧縮して転送時間を短縮しましょう。転送量も削減できます。

以下の手順のどちらかを設定します。

mod_deflate

mod_deflateを使用して、毎回ファイルを圧縮します。ただしCPUリソースを食うので処理能力に余裕が無いならこの設定は禁物です。後述のあらかじめ圧縮しておく手法を使うならこの設定は不要です。

手順は対象のMIMEタイプに対し「AddOutputFilterByType」を指定するだけです。

<Directory /path/to/directory>
    AddOutputFilterByType DEFLATE text/plain text/html text/css application/javascript application/xml
</Directory>
MultiViews

あるいは手間が多くなりますが、あらかじめファイルを圧縮しておくほうがより有効です。これなら少ない処理で転送時間を短縮できるので、パフォーマンスを大きく改善できます。

手順としては、まずGZip圧縮したファイル(example.html.gz)と非圧縮のファイル(example.html.html)を用意します。後は「Options MultiViews」を有効にすれば完了です。

Note: 非圧縮ファイルの拡張子が重複していることに注目してください。これはApacheにどちらのファイルを転送させるか選択させる上で必要です。

<Directory /path/to/directory>
    Options MultiViews
</Directory>

このファイルに対して~/example.htmlというアドレスでアクセスするとApacheにより最適なファイルが選択され、対応ブラウザには圧縮されたファイルが、そうでない環境には非圧縮のファイルが転送されます。

ログの整理

Apacheでは、リクエストの都度アクセスログをログファイルへ書き出しています。毎度余分な書き込みアクセスが発生するとなると、積もり積もれば結構なオーバーヘッドになります。

ログのバッファ

この負荷を減らすには、単純に書き込みの頻度を少なくしてやります。Apache 2.0.41からはこのアクセスログを一旦メモリに貯めておき、ある程度溜まってからまとめてログファイルに書きだすBufferedLogsというディレクティブが追加されました。まだexperimental(試験的)な機能ですが、パフォーマンスは確実に改善されるのでこれを検討しない手はありません。

httpd.confに以下の設定を追加します。

BufferedLogs On
ログの分割

アクセスログファイルは際限なく追記され、たとえそれが何十GBになろうとApacheはお構いなしに記録を続けます。もちろんアクセスの多いサーバーでディスク容量が食いつぶされるのは目に見えていますし、肥大化するファイルに追記を続ければパフォーマンスの低下も避けられません。rotatelogsを使用して定期的にログファイルを分割します。

httpd.confのログファイル保存先を以下のように書き換えます。ログの保存先は適宜変更してください。

CustomLog "|/usr/sbin/rotatelogs /path/to/logs/access.%Y%m%d.log 86400 540" combined
ErrorLog "|/usr/sbin/rotatelogs /path/to/logs/error.%Y%m%d.log 604800 540"

もっともこの方法はパイプ出力しているので単純な書き出しに比べるとどうしても負荷が増えますが、どうしようもない巨大ファイルができることを思えばはるかにマシです。

古いログの圧縮

分割されたログはそのままだとディスク容量を圧迫するので、古いログは圧縮してしまいましょう。

ログを圧縮するシェルスクリプトを作成します。ログの保存先は先ほど指定した場所に読み替えてください。

Note: この例では高圧縮のbzip2を使用していますが、これはシングルスレッドで動作します。マルチコアCPUを積んでいるなら、並列処理に対応したpbzip2を利用するとより高速に処理できます。

# sudo vim /usr/bin/logcompress.sh
#!/bin/sh
LANG=C
cd /path/to/logs
for i in `find . -name "*.log" -mtime +2`
do
    bzip2 -9 $i
done

次にこれを実行可能にした後、定期的に起動するためcronに登録します。

#sudo chmod 755 /usr/bin/logcompress.sh
# sudo crontab -e
15 9 * * *   /usr/bin/logcompress.sh >/dev/null

ログファイルは午前9時(UTCの午前0時)を以て分割されるので、9時過ぎに走らせています。

不要なモジュールの停止

Apacheでは標準で様々なモジュールが読み込まれていますが、中には一般的には必要ないモジュールも多く含まれています。どうせ使わないなら、思い切って不要なモジュールの読み込みをコメントアウトしてしまいましょう。

認証

LDAP認証など一般用途では使わいでしょうから、これを外しておきます。他にも目的に応じてコメントアウトしておきましょう。

#LoadModule ldap_module modules/mod_ldap.so
#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
プロキシ

通常のWebサーバーではプロキシ機能はまったく不要です。すべて無効化しましょう。

#LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
#LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.so

ただしconf.dディレクトリ内にproxy_ajp.confファイルがある場合は、これを忘れず編集しておきます。さもなければ設定の構文エラーと判断されhttpdの起動に失敗します。

# sudo vim /etc/httpd/conf.d/proxy_ajp.conf

mod_proxy_ajp.soを無効化します。

#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
ログ、ステータス、バージョン

mod_usertrackはCookieを用いてユーザーの行動を追跡し、ログに記録する機能です。Apacheのアクセスログでそこまで行う意味については疑問です。mod_statusmod_infoはそれぞれステータスやサーバー情報のページを吐き出す機能です。mod_versionは複数バージョンのApacheを動作させる時にそれぞれの設定を切り替えるディレクティブを提供します。これらはすべて不要でしょう。

#LoadModule usertrack_module modules/mod_usertrack.so
#LoadModule status_module modules/mod_status.so
#LoadModule info_module modules/mod_info.so
#LoadModule version_module modules/mod_version.so
キャッシュ

コンテンツのキャッシュを行わないならこれらのモジュールをすべて無効にします。

#LoadModule cache_module modules/mod_cache.so
#LoadModule disk_cache_module modules/mod_disk_cache.so
#LoadModule file_cache_module modules/mod_file_cache.so
#LoadModule mem_cache_module modules/mod_mem_cache.so

参考サイト

No comments:

Post a Comment