Browse Month: December 2010

Apache チューニング Tips

先日、Web サーバ勉強会 #2 が開かれました。内容は、Apache のチューニングということで、参加したかったのですが、他の予定があって参加できませんでした。

そこで、僕が個人的に行っている Apache のチューニングを紹介したいと思います。最初、スライドで作成しようかと思ったのですが、ブログにまとめたほうがよさそうなのでブログにまとめていきます。

まず、大前提として Apache をチューニングするうえで、大事なことはその Apache が提供する Web サービスの種類のよって大きくチューニングする内容が異なるということです。例えば、動画・写真共有サービスと株価情報のサービスを比較すると、当然のことながら大きくサービスの内容が異なりますし、HTTP レベルでみるとクライアントからのリクエスト数、データサイズ、などがかなり違ってきます。

ですので、まずは自分が扱っているウェブサービスの特性を正しく認識した上で、Apache のチューニングを施すことが大切です。

今回、紹介するのは、個人的に扱うことが多いウェブサービスでの Apache チューニング Tips を紹介しますが、特性としてはたくさんのユニーククライアントからリクエストが来ますが、1回1回のリクエストのときのデータサイズが非常に小さいウェブサービスです。秒間あたりの最大リクエスト数は、合計 10,000 req/sec 程度を想定しています(もちろん 1 台の Apache でさばいているという意味ではありません)。

いわゆるチューニングでない内容を含まれていますが、ご了承ください。

システム環境は、CentOS 5.5 x86_64 + httpd 2.2.3 になります。

アクセスログとエラーログ

Apache のアクセスログとエラーログは、重要です。アクセスログは一定時間おきに解析したり、ちゃんとレスポンスを返しているのかなどを確認するための重要な情報が含まれています。エラーログは、Apache で何らかのエラーが発生していないか確認するための重要な情報が含まれています。

僕は、基本的に前に紹介した mod_log_rotate を使って、アクセスログを日付別に出力しています。アクセスログは1時間おき、エラーログは mod_log_rotate ではサポートされていないため、 cronolog 経由で日別で出力しています。ちなみにアクセスログは、1時間おきに cron で解析処理を行っているため、解析しやすいように CustomFormat を使っていますが、ここでは combined としています。大量のアクセスがある場合、パイプ経由でログを出力してしまうと、そのパイプ経由で実行しているプログラムにも大きな負荷を与えることになるため、mod_log_rotate が断然有利です。mod_log_rotate は、ずっと使っていますが、特に大きな問題は起こっていないのでおすすめです。

具体的な設定は、次のとおりです。

LoadModule log_rotate_module modules/mod_log_rotate.so
RotateLogs On
RotateLogsLocalTime On
RotateInterval 3600

<VirtualHost *>
ServerName example.com

CustomLog “/var/log/httpd/access_log.%Y%m%d%H” combined
ErrorLog “|/usr/sbin/cronolog /var/log/httpd/error_log.%Y%m%d”
</VirtualHost>

このようにしておくと、/var/log/httpd にアクセスログとエラーログが1日おき1時間おきに出力されます。

ただし、このままだとログがたまる一方になるため、1日おきに bz2 で圧縮後移動して、ある一定期間上経過したアクセスログは削除しています。次のようなスクリプトを /etc/cron.daily においてあります。

#!/bin/sh
function compress_log()
{
ARCHIVES_DIR=$1/archives
if [ ! -d $ARCHIVES_DIR ]; then
mkdir $ARCHIVES_DIR
fi

EXT=`date +%Y%m%d%H`
for i in `find $1 -maxdepth 1 -not -name “*.bz2” -not -name “*.$EXT” -type f -print`
do
if [ -f $i ]; then
pbzip2 -p4 $i
fi

YEAR=`echo $i | sed -e ‘s/.*\.//’ | cut -c 1-4`
if [ ! -d $ARCHIVES_DIR/$YEAR ]; then
mkdir $ARCHIVES_DIR/$YEAR
fi
MONTH=`echo $i | sed -e ‘s/.*\.//’ | cut -c 5-6`
if [ ! -d $ARCHIVES_DIR/$YEAR/$MONTH ]; then
mkdir $ARCHIVES_DIR/$YEAR/$MONTH
fi
mv $i.bz2 $ARCHIVES_DIR/$YEAR/$MONTH/
done
}

function delete_log()
{
find $1 -name “*_log*.bz2” -type f -mtime +365  -exec rm -f {} \;
}

DIRS=(“/var/log/httpd”)
d=0
for d in ${DIRS[@]};do
compress_log $d
delete_log $d
done

そして、デフォルトでインストールされる /etc/logrotate.d スクリプトは不要になるので削除しておきます。

/etc/logrotate.d/httpd スクリプトは、毎朝 httpd を再起動されるため、トラフィックがやまないウェブサービスの場合は無効にしておくのがいいと思います。

不要なログは出力しない

Apache のアクセスログやエラーログで不要なログは出力しない方がいいです。不要なログは、ディスク容量の圧迫につながったり、余計なディスク I/O を発生させるもとになります。

個人的には、LVS + Keepalived 配下にある Apache の場合は、設定した間隔で Apache でヘルスチェックのリクエストがくるため、このときのアクセスログは出力しないようにしています。

<VirtualHost *>
ServerName check.example.com
ErrorLog /dev/null
CustomLog /dev/null combined
</VirtualHost>

なるべく worker MPM を使う

もし、worker MPM を使える環境の場合は、worker を使うべきです。CentOS では、/etc/sysconfig/httpd の次の行のコメントをはずして httpd を再起動すれば worker MPM に切り替えることができます。

#HTTPD=/usr/sbin/httpd.worker

worker MPM は、各リクエストに対してスレッドが処理する構造になっているのに対して、prefork MPM は子プロセスが処理する構造になっています。そのため、worker MPM の方はオーバーヘッドが小さいです。

個人的によく Apache を使う構成は、Apache + Passenger、Apache + Tomcat、ですが、どちらも worker MPM で運用しています。どちらも同一サーバ上で動いているため、なるべく Apache 側のリソースは最小限にして、Passenger や Tomcat により多くのリソースを与えることで大量のリクエストをさばくようにしています。また、どちらも httpd のメモリリークはないように思えるため、MaxRequestsPerChild は 0 にして Apache の各子プロセスの再起動をしないようにしています。

<IfModule mpm_worker_module>
StartServers           1
ServerLimit            1
ThreadLimit         2048
ThreadsPerChild     2048
MaxClients          2048
MinSpareThreads     1024
MaxSpareThreads     1024
MaxRequestsPerChild    0
</IfModule>

また、MaxClients などの設定は、長野さんの「プロのサーバ管理者がApacheのStartServers, (Min|Max)SpareServers, MaxClientsを同じにする理由 – blog.nomadscafe.jp」を参考にしています。長野さんのエントリは、prefork MPM の例ですが、これを参考にして worker MPM でも似たいような感じの設定にしています。上の設定だと、最大1子プロセスしか起動しないため、top でみても画面が httpd で埋め尽くされることはないので見やすいという効果もあります(笑)。

あとは、実際に本番に投入してから mod_status の内容を確認して、すべてのスロットがうまっていないか運用したほうがようです。もちろん、モニタリングツールを用いて常に Apache の状態を確認することは重要ですが、初回の場合は手元でしばらく確認すること大切です。その際に watch コマンドを使うと便利です。

$ watch curl -s -H ‘”Host: status.example.com” localhost/server-status?auto’

このコマンドを実行して、mod_status の Scoreboard がいっぱいになっていないか、どういう状態になっているのか確認するのが大切です。

mod_status の設定は、次のように設定しています。

ExtendedStatus On
<VirtualHost *>
ServerName status.example.com
<Location /server-status>
SetHandler server-status
Order deny,allow
Deny from all
Allow from all
</Location>

keepalive はオフにする

個人的には keepalive はオフにしています。理由は、ユニーククライアントがあまりにも多すぎるという点と、1クライアントからのまとまったリクエストはほとんどないタイプのウェブサービスのためです。あまりにユニーククライアントが多い状態で keepalive を有効にしてしまうと、keepalive の処理ですべてのスロット(mod_status の scoreboard)が一杯になってしまいパフォーマンスの劣化につながります。keepalive は、デフォルトで有効になっているのでオフにするにはグローバルディレクティブに、次の設定を追加します。

KeepAlive Off

また、よくある余計なモジュールをロードしないというのは、個人的にはやっていません。一度最小限に読み込むモジュールにしてみてメモリの使用量を比較してみたのですが、それほど大きな効果はなかったためです。デフォルトの httpd.conf も、かなりカスタマイズしてしまうと、設定ファイルの管理も大変になってくるので、管理する手間も考慮して基本的にデフォルトの httpd.conf で読み込んでいるモジュールはロードするようになっています。

この他、高トラフィックなウェブサイトの場合は、Apache 以外にもネットワークレベルのチューニングが必須になりますのが、これは別の機会で紹介したいと思います。どちらかというと、Apache よりもネットワークレベルのチューニングの方が重要ですけれど。