読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

Apache2+mod_proxy+持続接続で時々レスポンスが悪くなる現象のメモ

perl+web

今更な話題で恐縮ですが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
  • ApacheStarmanとの持続的な接続が5個になると、Starmanは持続接続が切れるまでそれ以上新しい接続ができなくなる*2。しかし、Apache側は後251個クライアントと接続を持つことができる。
  • クライアントからApacheへの要求が、Starmanと接続を持っていないApacheのプロセスに割り当てられると、ApacheStarmanへ新規接続しようとする。
  • 結果、待ち状態となって極端に遅くなる。このとき、せっかく張ってあった持続的な接続は使われずアイドル状態。

解決策

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%くらい遅くなりました。

3). Apacheを使わない

also it's really a mod_proxy issue. Use real proxy like nginx :)

@miyagawa

mpm_prefork + mod_proxy + persistent connections = broken by design

@kazuho

はい、ごもっともです。。。

余談

starman起動時に環境変数STARMAN_DEBUG=1 を渡すと、色々調査しやすくなります。

2010-03-28 追記

id:kazuhooku さんによるGreatな解説。

そんなこんなの理由から、アプリケーションサーバhttpd として使うことしか考えてない Starlet (旧 Plack::Server::Standalone::Prefork(::Server::Starter) ) は、デフォルトで keep-alive をオフにしてる。

2010年代には Apache の mpm_prefork とか流行らない (もしくは HTTP keep-alive のメリットとデメリット)

2011-05-27 追記

はてブのコメントにもありますが、現在はStarmanにも「--disable-keepalive」オプションがあります。

*1:解決法の(2)で解決

*2:解決法の(1)で解決