88171.net

Apache Web Serverのパフォーマンスを最適化する

これはMatthew Nuzum氏による2013年の記事、 "How To Optimize Apache Web Server Performance" (CC BY-NC-SA 4.0) を日本語訳したものです。

元記事がクラウドインフラのプロバイダである DigitalOcean のサイトに掲載されていることからも分かるように、 リソースが潤沢でない(仮想)マシン上でApache HTTP Serverのパフォーマンスを最大限引き出すことが主なテーマとなっています。

元記事の冒頭に、記事内で触れられているバージョンのUbuntuのEOL(End Of Life: サポート終了)に関する注記がありますが、翻訳は割愛します。 記事中の設定例も最新のディストリビューションにはそのまま適用できない箇所がありますが、コンセプトとしては普遍的なものですので、適宜読み替えてください。


はじめに

Apache HTTP Server (訳注: 正確ではないが以下Apacheと表記) は高機能かつパワフルなWebサーバです。 数多くのモジュールが付属していて、様々な設定を効率的に行なうことができます。 一方で、Webサイトが成長するにつれてパフォーマンス上の問題に行き当たることもあります。

私がDigitalOceanに最初に魅力を感じたのは、利用を始めるのに必要なコストが低かった点です。 最も小さく低コストなドロップレット (訳注: droplet。直訳すると水滴だが、DigitalOcean用語で仮想マシンインスタンスとほぼ同義) の搭載メモリは512MBですが、巨大なフレームワークが当たり前に使われる昨今ではちょっと心細く思えます。 しかしこのような小さなマシンでも、設定を工夫すれば驚くようなパフォーマンスを発揮してくれるのです。

この記事では小さなドロップレットでApacheを運用している場合、もしくはより大きなドロップレットで最大限のパフォーマンスを得たい場合に、いくつか注意すべきポイントを挙げていきます。 Ubuntu 12.04を例として使用しますが、基本的な考え方は他のLinux環境でも十分に通用するはずです。

不必要なモジュールを外す

UbuntuやDebianのシステムでは、/etc/apache2mods-availablemods-enabledというディレクトリがあると思います。 mods-availableにはサーバにインストールされている全てのモジュールが、mods-enabledにはそのうち現在有効化されているモジュールが含まれています。

私のVPSでは、デフォルトでは17個のモジュールが有効化されていましたが、私(のアプリケーション)にとってはほとんどが不要なものでした。 一部のモジュールは互いに依存関係がありますので、あなた(のアプリケーション)にとって不要なモジュールを洗い出す作業は少し手間かもしれません。

私がまずお勧めしたいのは、現在有効なモジュールをリストアップして、あとから確認できるように保存しておくことです。 次に、モジュールを一つずつ無効化してはApacheを再起動する、という手順を繰り返してください。 もしそれが無効化できないモジュールだった場合には、何かしらのエラーが発生するはずです。

UbuntuやDebianでは、以下のコマンドでモジュールを無効化できます。 ここでは例としてautoindexモジュールを無効化しています。

sudo a2dismod autoindex

以下のいくつかのモジュールは特に多くのリソースを消費するので、不要なのであれば無効化を検討した方が良いでしょう。

これらの中には既に無効なものもあるでしょうから、そのままにしておきましょう。 また、実際に使われていて無効化できないということも勿論あります。

rewriteモジュールについて少しお話ししますと、このモジュールが有効化されていても、実際はaliasで代替できるというケースはよくあります。 設定をaliasで置き換えてみて問題なければrewriteモジュールを無効化しましょう。 rewriteはApacheにある種の強力な機能を提供してくれるモジュールですが、リソースを多く消費するモジュールの一つでもあります。

この記事ではrewriteをaliasに置き換える方法を詳しく説明はしません (もちろん、役立つドキュメントはあります)。 ただ、たとえrewriteを完全に無効化できなくても、rewriteルールをいくつかaliasに置き換えるだけでもパフォーマンス上の恩恵は受けられます。

さて、モジュールを無効化してApacheを再起動したあとは、エラーログをチェックしましょう。 UbuntuやDebianでは/var/log/apache2/error.logです。

私の場合は以下のエラーが出ていました。

Syntax error on line 6 of /etc/apache2/sites-enabled/site1:
Invalid command 'DAVLockDB', perhaps misspelled or defined by a module not included in the server configuration
Action 'configtest' failed.

私が無効化したモジュールが実は必要とされていたということです。 このケースではdav_fsが必要だとわかりましたので、以下のコマンドで再度有効化しました。

sudo a2enmod dav_fs

そして、Apacheを再起動して次のエラーをチェックします。 不要なモジュールを洗い出すには、このトライ&エラーがあと数回は必要でしょう。 しかし忍耐強く取り組んでください。やるだけの価値はありますから。

Apacheからコードを追い出す

あなたがPHPを使ってサイトを運用しているのであれば、かの有名なmod_phpを利用している可能性が高いです。 Rubyを使っているのであればPassenger Phusion、別名mod_railsもしくはmod_rackを利用しているかもしれません。

問題なのは、これらの言語のインタプリタがApacheに組み込まれているために、常により多くのメモリを消費してしまうということです。 あなたのWebサイトでとある人気のページに、30のHTTPリクエストがあったとします。 そのうち動的に生成されるページは1つだけで、残りの29リクエストは画像やCSSといった静的なリソース、というケースは実際にありがちです。 これらの29リクエストを処理するために、メモリ喰らいになってしまったApacheを使いたいと思いますか?

具体的な数字で見てみましょう。 mod_phpを有効化すると、Apacheの子プロセスひとつあたり100MB近くものメモリが必要になります。 Apacheはデフォルトで25かそれ以上の子プロセスを起動しますので、アクセスが集中するとすぐにメモリが足りなくなりそうです。

これを解決する仕組みはあります。

仕組みを切り替える上でのデメリットは、セットアップの難易度が高いということです。 わかりやすいドキュメントが用意されていることもありますが、たとえばphp-fpmは、まあその、ドキュメントがスッカスカです。

これらの仕組みはどれも考え方が同じです。 アプリケーションを管理するサーバプロセスがApacheとは別に起動し、Apacheは自力でアプリケーションのコードを実行する代わりに、単にアプリケーションサーバにリクエストを投げるだけになります。

実際に試してみると、きっとその効果に驚くでしょう。 私のVPSではmod_phpを無効化したところ、90〜120MBだったApacheプロセスのメモリ使用量が10MB以下にまで減少しました。 動的コンテンツはたった2つのPHPプロセスで全て処理することができ、メモリの使用量はそれぞれわずか60MBです。

Apacheのプロセス数を制限する

Apacheの子プロセス数のデフォルトの設定値は、ほとんどの場合25かそれ以上になっていますが、小さなマシンには多すぎます。 たとえば子プロセスが1つあたり120MBのメモリを使うとしたら、あなたはApacheのためだけに3GBものメモリを用意しないといけない計算になります。

ブラウザはWebサイトに対して、並列で4程度のリクエストを平気で投げてきます。 つまり計算上は7〜8人が同時にアクセスしてきただけで、あなたのサーバが過負荷の状態に陥る可能性があるわけです。 その状態では訪問者からは、Webページが永遠と呼べるほどの長時間に渡って読み込み中に見えるでしょう。

訪問者がアクセスをとっくに諦めた後でも、実質的に死んでいるApacheの子プロセスがずっと残り続けてサーバのリソースを食いつぶす、という状況はよく起こります。 これはいわゆる負の連鎖で、訪問者にとってもあなたにとっても、決して良い状況とは言えません。

これを避けるために、アプリケーションがどれだけのメモリを必要とし、どれだけのメモリが利用可能で、そのうちどれくらいApacheに割り当てるか検討しなければいけません。

たとえば、3つのphp-fpmプロセスが動的コンテンツを処理しているとします。 それらが1つあたり70MB程度、さらにMySQLサーバが120MB程度のメモリを使用するなら、アプリケーションが使用するメモリは合計で330MB程度とわかります。 すると、Apacheに割り当てられるのは150MB程度 (訳注: RAM 512MBのドロップレットを想定していると思われる) ということになります。

Apacheが起動しているときにtopコマンドを実行してみましょう。 出力の一部を、不要な行は削除した上で例示してみます。

top -bn 1
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
[...]
15015 www-data  20   0  232m 9644 1900 S  0.0  1.6   0:00.02 apache2
15016 www-data  20   0  232m 9644 1900 S  0.0  1.6   0:00.01 apache2
15017 www-data  20   0  232m 9644 1900 S  0.0  1.6   0:00.02 apache2

Apacheの子プロセスのRESの項目に注目してください。 たとえばこのサーバは最適化されていますので、9,644という値が表示されています。 つまり子プロセス1つあたりのメモリ使用量は10MB足らずということです。 Apacheの設定で子プロセスの最大数を15に設定すれば、Apache全体のメモリ使用量は最大150MB程度になります。

実際に設定を調整しましょう。 UbuntuやDebianでは/etc/apache2/apache2.confに設定ファイルがあります。 mpm_prefork_moduleの設定箇所を探して、MaxClientsの値を15に変更して保存し、Apacheを再起動してください。

たとえばUbuntuではこのような設定があります。

<IfModule mpm_prefork_module>
    StartServers          3
    MinSpareServers       3
    MaxSpareServers       5
    MaxClients           30
    MaxRequestsPerChild   0
</IfModule>

MaxClientsの行がわかりますか? ここを値を下げましょう。

VPSが過負荷状態になって、サーバが応答可能なクライアントの最大数に達した場合、新たな訪問者は即座にエラーを受け取ることになります。 彼らはページを再読み込みすることで、次はきっと正常にページにアクセスできるでしょう。

これは悪い挙動にも見えますが、無限に読み込み中の状態に陥るより、接続を絞ってサーバを健全に保つ方がずっと良いです。 腑に落ちないかもしれませんが、子プロセスがたくさんいてもリソースを奪い合って処理が追いつかない状態に比べれば、子プロセスが少なくてもそれぞれが健全な方が、全体としてはより高いパフォーマンスを得ることができるのです。

(訳注: ここは元記事が不正確。2.4系ではMaxRequestWorkersを超える接続はキューイングされるとドキュメントに書いてあるし、2.2系のMaxClientsでも同様。即座に接続失敗に至る閾値としてはListenBackLogもしくはOSの制約があってどちらも3桁のオーダー。最悪のケースを示そうとして言葉足らずになったのかもしれないが、実際は書かれているよりもう少しマシな挙動をする。)

実例を挙げましょう。 私が管理しているWordpressサイトではメモリ1GBのドロップレットでphp-fpmが4プロセスが稼働していますが、950ユーザのアクセスを同時に処理できます。 計算上は1日あたり最大4,200万のページビューを捌くことができるわけで、これだけのアクセスがあれば相当な人気サイトでしょう。

(訳注: 元記事のコメントでも噛み付かれてるけど、これはどういう計算だろう。まあ本題とは関係ないけど。)

別のMPMモジュールを検討する

ほとんどのApacheは、歴史的経緯によりprefork MPMを利用する設定になっています。 これはスレッドセーフなモジュールで、PHPなどの組み込み処理系と親和性が高いものです。

もしPHPやRailsのような組み込み処理系を晴れて無効化することができたのであれば、是非worker MPMの利用を検討しましょう。 preforkよりもリソースを節約できる可能性が高いです。

worker MPMを利用するにはまずインストールする必要があります。

sudo apt-get install apache2-mpm-worker

すると、このようなメッセージが表示されます。

The following packages will be REMOVED:
  apache2-mpm-prefork libapache2-mod-php5
The following NEW packages will be installed:
  apache2-mpm-worker
0 upgraded, 1 newly installed, 2 to remove and 2 not upgraded.
Need to get 2,284 B of archives.
After this operation, 8,718 kB disk space will be freed.
Do you want to continue [Y/n]?

Ubuntuではworker MPMをインストールすると、prefork MPMや、mod_php等の非互換のモジュールが自動的にアンインストールされることに注意してください。

この記事では、4つの最適化手法を紹介してきました。 これらの手法を組み合わせれば、たとえ小さなドロップレットでも最大限のパフォーマンスを発揮させることができます。

これらの手法を実際に試してみる際には、是非テスト用の環境を用意しましょう。 DigitalOceanでは素敵なことに、テスト用に新しいドロップレットをサッと起動し、必要な時間だけ利用して不要になったらシャットダウンすることができます。 時間単位で課金が行なわれますので、低リスク・低コストで最適な設定を導き出すことができるでしょう。