今更な話題で恐縮ですがmiyagawaさんがものすごい勢いで教えてくれたのでメモっておきます。
mod_proxyでバックエンドにリクエストを投げたとき、リクエストのうち何個かが極端に遅いという現象が起こりました。その時のabの結果は以下。
% ab -c 5 -n 500 http://127.0.0.1:21082/ Percentage of the requests served within a certain time (ms) 50% 1 66% 1 75% 1 80% 1 90% 4 95% 1008 98% 1994 99% 2003 100% 2020 (longest request)
なお、今回使おうとしたバックエンドはStarmanです。
多分こんな原因
検証不足で断言はできないのですが、多分以下のような感じ。多分。
- Apache のデフォルトのServerLimitは256、Starman のデフォルトのワーカーは5
- Apache にリクエストが来ると Starman と接続するが、HTTP/1.1でやり取りするので持続的になる*1
- ApacheとStarmanとの持続的な接続が5個になると、Starmanは持続接続が切れるまでそれ以上新しい接続ができなくなる*2。しかし、Apache側は後251個クライアントと接続を持つことができる。
- クライアントからApacheへの要求が、Starmanと接続を持っていないApacheのプロセスに割り当てられると、ApacheはStarmanへ新規接続しようとする。
- 結果、待ち状態となって極端に遅くなる。このとき、せっかく張ってあった持続的な接続は使われずアイドル状態。
解決策
3通りくらい思いつきました。でも、ベストな解は得られてない感じです。
1). ワーカーを増やしてリクエストを受けきれるようにする
starmanコマンドに--workersオプションでワーカーの数を指定できます。この場合は256とすれば受けきれます(それが運用として現実的かは別として)。
Apacheがworker MPMの場合はもうちょっと数を絞れます。まず、MaxClients を ThreadsPerChildで割って最大立ち上がる子プロセス数を出します(デフォルトは15)。そして、 ProxyPass にmaxパラメータを指定すれば、1プロセス辺りのバックエンドへの最大接続数を指定できます。このmax値を2とかにすれば、Starman側で必要なワーカー数は 2 * 15 = 30 となります。
ただし、通常時は15個も子プロセスが立ち上がらないので、多くのワーカーが使われず無駄になります。この場合、実際に使われるワーカーはかなり少なくなるので、これがボトルネックになる可能性があります。
もしくは、Starmanのオプションで--max_serversがあるので、これを大きく設定すると足りない場合に動的にワーカーが作られるようになります。ただし、この機能はドキュメント化されてなく、オススメでも無いようです。
2). 持続接続をやめる
Apache2.2だとdisablereuseというパラメータがあるようです。今回はApache2.0で試したので、以下のような書き方で持続接続を止められました。
<Location /backend> SetEnv proxy-nokeepalive 1 </Location>
ただし、持続接続を使わない分多少遅くなります。手元の環境だと10%くらい遅くなりました。
2010-03-28 追記
id:kazuhooku さんによるGreatな解説。
そんなこんなの理由から、アプリケーションサーバの httpd として使うことしか考えてない Starlet (旧 Plack::Server::Standalone::Prefork(::Server::Starter) ) は、デフォルトで keep-alive をオフにしてる。
2010年代には Apache の mpm_prefork とか流行らない (もしくは HTTP keep-alive のメリットとデメリット)