2010-11-29 さくらVPSで一日6万PVを処理するためにしたこと
さくらVPSで一日6万PVを処理するためにしたこと
さくらVPSで6万PV程度のサイトを運用することになったので、その際の記録を残しておきます。
さくらレンタルサーバ⇒さくらVPS
さくらレンタルサーバで運用している時は、ちょくちょく503が発生しておりこれを解決するためにさくらVPSへの移行を行いました。
レンタルサーバの時は、ログ解析や監視ツールなどを導入していなかったので503の頻度やパターンは不明です。(安易な判断でVPSに移行したので、この点は反省です)
本来は原因を追及し、プログラムの改修やサーバ負荷の分散などをすべきですが時間の都合で省いてしまいました。
構成
運用するサイトはpukiwikiで構成されたサイトです。
pukiwikiは、PHPで書かれており大量のデータをRDBを利用せずに構築できる点が大きなメリットです。
今回は1サイトですが、複数のサイトを運用する可能性を考慮してVirtualHostも利用します。
さくらVPSの性能
Apacheのインストール
CentOSにはyumがあるのですが、不要なモジュールやパッケージをインストールを避け最新版のApcheを利用したかたったのでソースからインストールしました。
configure
pukiwikiに関わらず基本的なサイトであれば以下のオプションで動作するはずです。
"./configure" \
"--enable-so" \
"--enable-rewrite" \
"--enable-expires" \
"--disable-proxy" \
"--disable-deflate" \
"--disable-cache" \
"--disable-disk-cache" \
"--disable-auth-basic" \
"--disable-authn-file" \
"--disable-authn-default" \
"--disable-authz-groupfile" \
"--disable-authz-user" \
"--disable-filter" \
"--disable-autoindex" \
"--disable-negotiation" \
"--disable-actions" \
"--disable-asis" \
"--disable-cgi" \
"--disable-env" \
"--disable-include" \
"--disable-userdir" \
"--disable-imap" \
"--disable-cgid" \
"--disable-charset-lite" \
"--disable-imagemap" \
"--disable-setenvif" \
httpd.conf
次の設定ファイルのみ有効にします。
Include conf/extra/httpd-mpm.conf
#バーチャルホストの設定
Include conf/extra/httpd-vhosts.conf
Include conf/extra/httpd-default.conf
httpd-mpm.conf
mpm_prefork_moduleのみに注目してください。
mpm_worker_moduleはマルチスレッド対応のmpmですが不安定な一面があるのでpreforkに移行しました。
StartServers 10 # Apache起動時に起動するプロセス数
MinSpareServers 50 #最低待機プロセス数
MaxSpareServers 70 #最大待機プロセス数
MaxClients 110 #最大起動するプロセス数(これ以上はプロセスを起動しない)
MaxRequestsPerChild 1000
MaxMemFree 2048
MaxRequestsPerChildとMaxMemFreeについては後ほど説明したいと思います。
Apacheは起動するとデフォルトで5個ぐらいのプロセスを起動するのですが6万PVを処理するには平均で70プロセス程度、ピーク時で100プロセス程度起動します。
1プロセス1リクエストを処理した後にプロセスが終了するので、5⇔70プロセスの起動と終了を何度も繰り返すので非常にオーバーヘッドが大きくなってしまいます。
Apacheプロセス起動と終了に生じるオーバーヘッドを下げるためにmpmで予めプロセスを起動しておきます。
それがStartServers、MinSpareServers、MaxSpareServersの設定になります。
<IfModule mpm_prefork_module>
StartServers 10
MinSpareServers 50
MaxSpareServers 70
MaxClients 110
MaxRequestsPerChild 1000
MaxMemFree 2048
</IfModule>
httpd-default.conf
KeepAliveは諸刃の刃なので、ONにするときはよく検証しましょう。
できるかぎりサーバの情報は伏せておきたいので情報表示はOffにします。
Timeout 300
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 5
UseCanonicalName Off
AccessFileName .htaccess
ServerTokens Prod
ServerSignature Off
HostnameLookups Off
TraceEnable Off
PHPのインストール
バージョン:5.2.14
configure
'./configure' \
'--with-apxs2=/usr/local/apache2/bin/apxs' \
'--enable-zend-multibyte' \
'--with-zlib' \
'--with-xmlrpc' \
'--with-gd' \
'--with-jpeg-dir=/usr/local' \
'--enable-mbstring' \
cp php.ini-recommended /usr/local/lib/php.ini
zlibがないとエラーがでますが、zlibなどはyumからインストールしていただいて結構です。
やっておくと便利
ln -s /usr/local/lib/php.ini /etc/
ln -s /usr/local/lib/php/extensions/no-debug-non-zts-20060613/ ./extensions
サーバ停止
上記の設定後、3日にロードアベレージが60程度まで上昇しサーバが停止しました。
サーバ停止の原因を探るため、topを見たところ一部のApacheプロセスがメモリを30%程度占有していることが判明しました。
同時にSWAPもどんどん増え、メモリ不足に陥りサーバが停止している事がわかりました。
PHPのメモリを制限する
php.iniのmemory_limit = 128M と恐ろしいことになっていたのでApacheが平均的に使用しているメモリ+αのサイズを設定しました。
memory_limit = 128M ⇒ memory_limit = 16M
APCを入れてみる
その結果、極端にメモリを消費するPHPファイルを除き高速に処理されるようになりました。
約、2倍ぐらい処理速度が向上しました。
導入後、キャッシュ利用サイズが7M程度なのでAPCに8Mのメモリを割り当てる設定にしました。
php.iniの変更
追記
extension=apc.so
副作用
PHPが利用できるメモリを制限したところ、予想通りですが“Fatal error: Allowed memory size”のエラーが一部のPHPファイルで発生しているのが確認できました。
問題のファイル名を調べてみると、Wiki内にテーブルや内部リンクが多くある場合メモリリークが発生しやすいようです。
この副作用で、どのプログラムが問題を起こしているのかを発見することができました。
このプログラムに関しては、Pukiwikiに情報がありメモリリークを軽減させるパッチがあったので適用させることで、比較的テーブルや内部リンクが多いページでもメモリリークが発生することはなくなりました。
Apacheのメモリを制限する
PHP側のメモリリークはなくなったものの、Wikiのテーブルや内部リンクの多いページにアクセスされ続けるとApacheがどんどんメモリを利用して解放しない現象が発生しました。
メモリ利用と解放の動き
Apacheがメモリ要求
Apcheのメモリ使用終了
このような動作をしているようでOSがメモリを要求しない限りlib(メインメモリーアロケータ)がメモリを解放しないので、OSがメモリを要求してこない限りApacheが使用するメモリはどんどん増えていきます。
物理メモリに余裕があれば殆どの場合問題にはなりにくい問題なのですが512MしかないのでOSのメモリ要求とApacheのメモリ要求で使い果たしてしまうようです。
ApacheにはMaxMemFreeという機能があります。
MaxMemFree ディレクティブは free() が呼ばれない限り、 主アロケータが保持できる空のメモリの最大値をキロバイト単位で設定します。 設定されていないか、零に設定されているときは、無制限になります。
なんとも難解な日本語で書かれているのですが、つまりlib(メインアロケータ)が保持できるメモリを制限し設定値以上のメモリはlib(メインアロケータ)はOSに返すのでApacheがメモリを食い続けるという現象がなくなりロードアベレージがかなり下がりました。
メモリ使用量は多いがCPU使用量は低いプロセスが多くあったので、定期的にプロセスのメモリを解放することにしました。
MaxRequestsPerChild 0 ⇒ MaxRequestsPerChild 5000
MaxRequestsPerChildは、プロセスが最大処理できるリクエスト数を設定するための物です。
設定値以上のリクエストを処理するとプロセスを再起動させるので、効率よく負荷の高いプロセスにメモリを割り振ることが可能です。
結果
平均ロードアベレージ:1.07
平均CPU使用率:22.1%
ブラウザがページを表示するまでの平均時間:0.87
平均未使用メモリ:42M
SWAP:30K
HTTP 503発生回数:0回
このような感じで現在も安定して動作しています。
WikiはRDBを利用しないためメモリ使用量が抑えられましたが、MySQLを利用する場合は13%〜17%程度メモリ使用量が増加するのでmpmの設定でプロセス数を減らしメモリをどこかで浮かせる必要があります。
SWAP
SWAPが少し発生していたので、vmstatを見ていたところSWAPインが1日数十回発生するだけでSWAPアウトが発生していないのでメモリに置いていても利用頻度の低いデータはSWAPに移動されるのでそれが原因だと思われます。
監視
レンタルサーバでは、ログ解析や障害検知を行えていなかったのでZabbixとVISITORS Web Log Analyzerを導入しました。
- 507 http://b.hatena.ne.jp/hotentry
- 374 http://reader.livedoor.com/reader/
- 337 http://twitter.com/
- 325 http://b.hatena.ne.jp/hotentry/it
- 288 http://members.jcom.home.ne.jp/sarasiru/
- 202 http://www.ig.gmodules.com/gadgets/ifr?exp_rpc_js=1&exp_track_js=1&url=http://1o4.jp/google/module/slim-reader.xml&container=ig&view=default&lang=ja&country=JP&v=eb43ad8eb80830c&up_titlelink=http://b.hatena.ne.jp/t/php?sort=hot&threshold
- 128 http://www.google.co.jp/reader/view/
- 126 http://www.mew5.com/
- 112 http://www.google.com/reader/view/
- 109 http://www.google.co.jp/reader/view/?hl=ja&tab=wy