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

うまいぼうぶろぐ

linuxとhttpdとperlのメモ

mod_proxy - ProxyPass、BalancerMemberのパラメータのdocumentを読んだのでまとめた

timeoutとかconnectionとか。今まで真面目にみてなかったので。mod_proxyのdocumentは翻訳が追いついてないためか、日本語のページに記載されている内容は古いversionのものがあるので、なるべく英語のページを見たほうが良い。

とりまメモった内容を記録。体裁は後で整える。

あと、"まとめた"とか言ってるけど、全部のパラメータについて調べたわけではないです。

parameter: default

Description

timeout: ProxyTimeoutの設定値

  • ProxyTimeoutのdefault値はTimeoutと同じ値
  • Timeoutのdefault値は300

つまりProxyPassのtimeoutのdefaultは300秒。timeoutはbackendに接続してからのコネクションタイムアウト。timeoutで指定した秒だけ、backendからのデータ受信(への送信) を待つ。(Apache waits for data sent by / to the backend.) この間にデータの送受信がなかったらたbackendとの接続をタイムアウトと見なして、clientには502 Proxy Errorを返す。

アプリケーションによるだろうけど、300秒待つのは長いので数10秒程度に減らしたほうが良いような気がします。300秒も待つ状況って既にbackendのサーバはピンチな状態だと思うので。それなら、もっと早い段階でclientにエラーを返しちゃえばいいじゃないっていう。

apache 日本語のtimeoutの説明には "特に指定されなければ、 フリーなコネクションを取得できるまで待ちます" と書いているけど、たぶんこれはconnectiontimeoutの話だと思う。apache 2.2.10 からconnectiontimeout パラメータが追加されたようなので、たぶん日本語の翻訳のほうが追いついていない。apacheのCHANGESを見ると、Changes with Apache 2.2.10のとこに次の文章があるので。

mod_proxy: Add connectiontimeout parameter for proxy workers in order to be able to set the timeout for connecting to the backend separately.

個人的にはtimeoutっていう言葉が紛らわしく感じるんだけど、ProxyPassのtimeoutは、backendに接続してからレスポンスが帰ってくるまでの全体の時間ではなくて、接続中にtimeout秒間データの送受信がなかったらタイムアウトするためのもの。

これについてはhiroseさんのエントリ フロント/バックのreverse proxy構成で、指定秒数以内に必ずレスポンスを返す方法 がとってもに参考になる。ProxyPassのtimeoutを5秒に設定している状態で、8秒間sleepするURL "?t=8" にアクセスするとタイムアウトする。次に、2秒おきにデータを返して合計12秒のレスポンスがかかるURL "?d=1" ではタイムアウトが発生していない。
curlの結果だけではわかりにくいけど、ngrep -t -q -W byline port "backend appサーバのport" とかでパケットキャプチャしつつcurlを叩けば、ちょろちょろレスポンスが返ってきつつ、最後までレスポンスを受信していることがわかる。(timeoutしていない)

# ngrep -t -d any -q -W byline port 8888
T 2010/09/05 10:56:26.362847 10.0.0.1:53995 -> 10.0.0.2:8888 [AP]
GET /app/?d=1 HTTP/1.1.
Host: backend:8888.
User-Agent: curl/7.19.7 (i486-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15.
Accept: */*.
X-Forwarded-For: 192.168.0.1.
X-Forwarded-Host: www.example.com.
X-Forwarded-Server: www.example.com.
Connection: Keep-Alive.
.

T 2010/09/05 10:56:26.371997 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
HTTP/1.0 200 OK.
Date: Sun, 05 Sep 2010 01:56:26 GMT.
Server: Plack::Handler::Starlet.
Content-Type: text/plain.

T 2010/09/05 10:56:26.372048 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 1

T 2010/09/05 10:56:28.374015 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 2

T 2010/09/05 10:56:30.374786 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 3

T 2010/09/05 10:56:32.375671 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 4

T 2010/09/05 10:56:34.376626 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 5

T 2010/09/05 10:56:36.377451 10.0.0.2:8888 -> 10.0.0.1:53995 [AP]
delayed 6

あと、キャプチャして初めてしったんだけど、ProxyPassのkeepalive=off はbackendに通信するHTTPのkeepaliveのことではなくて、OSレベル(TCP)の話のようだ。この話は次のkeepaliveの項目で。

connectiontimeout: timeoutと同じ

  • timeoutと同じ == defaultでは300秒

apache 2.2.10で追加されたパラメータ。backendにコネクションを確立するまでのtimeout(秒)。数字の後ろにms をつけるとミリ秒単位になる。(By adding a postfix of ms the timeout can be also set in milliseconds)
timeoutの設定と同じく、defaultの300秒は長い気がする。connectiontimeoutはfrontのapacheがbackendに接続するまでの時間なので、300秒も待つって長過ぎじゃね?っていう。

keepalive: off

backendとの接続をTCPレベルでkeepaliveするかどうか。普通はoffで良い。apache documentには、apache <=> backend間にfirewallがあって、firewallにpacketを落とされる場合はkeepalive on にしてね、って書いている。

また、timeoutのとこでも書いたけど、backendに通信するHTTPのkeepaliveの話ではない。なのでkeepalive=offにしてても、backendには"Connection: Keep-Alive"でHTTPしてる。(disablereuse=onにしてもダメ)

HTTPのkeepaliveをoffにしたい場合は、"SetEnv proxy-nokeepalive 1" を設定するとbackendとのHTTP接続をkeepaliveしなくなる。

For circumstances where mod_proxy is sending requests to an origin server that doesn't properly implement keepalives or HTTP/1.1, there are two environment variables that can force the request to use HTTP/1.0 with no keepalive. These are set via the SetEnv directive.

<Location /buggyappserver/>
  ProxyPass http://buggyappserver:7001/foo/
  SetEnv force-proxy-request-1.0 1
  SetEnv proxy-nokeepalive 1
</Location>

(この例のforce-proxy-request-1.0 はHTTP/1.1 に対応してないappサーバに対する処理)

backendへの接続をkeepaliveする/しないの是非については以下のurlがとっても参考になる。

retry: 60

backendへのコネクションプールがエラー状態になった際に、リトライするまでの間隔。0にすると、間隔を開けずに常にリトライする。
最大60秒間リトライしないのは少し長い気がするんだけど、どうでしょうか。10~30 ぐらいに減らすかな。apache documentには、backendサーバのメンテナンスのためにretryパラメータ使ってねと書いてるんだけど、個人的にはメンテナンスする場合はBalancerMemberを触る派(一時的にコメントしたり、/balancer-managerから操作したり)なのでretryは60秒もいらないかなと。

disablereuse: off

backendとのコネクションを使い終わったら、再利用せずにすぐ閉じるときにはonに設定。apache documentでは、apache <=> backend間にfirewallが居て、しれっとコネクションを落とされたりする場合や、backendサーバがDNS ラウンドロビン下で動いてる場合に役立つよ、って書いてる。(keepaliveに似ている?)

kazuhoさんのエントリに書いてらっしゃるけど、frontのapacheがコネクションを生成するオーバヘッドって、backendのアプリケーションの負荷(やシステム全体のオーバヘッド)よりはずっと小さいと思うので、onで毎回閉じてるのが安全な気がする。
mod_proxyってまだ枯れてないためか、結構あやしげなbugがあったりしますよね。apache 2.2.12で修正されたけど、mod_proxy_ajpが違うスレッドのコンテンツ返すバグとかあったし。(そのbugがdisablereuse on にしてたら発生しないかどうかは知らないけど)

loadfactor: 1

BalancerMember用の重み付けの値で1 から 100までの数字を設定。複数のBalancerMemberが存在する場合は正規化した割合で振り分ける。基本的には全BalancerMemberで同じ値にして、等分に振り分ければ良い。で、スペックのいいサーバにはloadfactorを大きい値に設定するとか。

route: -

mod_proxy_balancerで振り分ける際のルーティングで使うworker。セッションIDの末尾に付加される。jvm1とかapp1とか適当に。


max: preforkは常に1, workerはThreadsPerChild

"1プロセスあたりの"backendサーバへの最大接続数(hard maximimum)。maxの値はapache全体の数ではなくて、1プロセスあたりの数。
つまり、ProxyPassに設定したbackendへの最大接続数はapache全体では以下の通り。

  • prefork: ServerLimit * max(常に1)
  • worker: ServerLimit * max(defaultはThreadsPerChildの数値)

となるので、defaultだとpreforkもworkerもMaxClients分だけbackendへ同時接続する。
(prefork: MaxClients(256) == ServerLimit)
(worker: MaxClients(400) == ServerLimit(16) * ThreadsPerChild(25))

apacheのMaxClientsよりも、backendへの最大接続数をしぼりたい場合

前述のようにdefaultのProxyPassの場合は、apacheのMaxClinets分だけbackendへ同時接続する可能性があって、通常これは大きすぎる値なので何とかしたい。
ただ、preforkの場合はapacheがプロセス単位で動いているため減らしようがない、timeout等を駆使して何とかしよう。
一方、workerの場合はmaxに小さい値を設定すれば、MaxClientsよりもずっと小さい値にすることが可能。
ex)

## workerのdefault
MaxClients      400
ServerLimit     16
ThreadsPerChild 25

ProxyPass /app http://backend:8080/app min=0 max=5 

にすれば、apache(worker)の最大同時接続数は400で、backendへの最大同時接続数は16 * 5 = 80 になる。

maxの値についての勘違い

勘違いっていうか俺がdocumentをちゃんと読んでなかったのが悪いんだけど。

Hard Maximum number of connections that will be allowed to the backend server. The default for a Hard Maximum for the number of connections is the number of threads per process in the active MPM. In the Prefork MPM, this is always 1, while with the Worker MPM it is controlled by the ThreadsPerChild. Apache will never create more than the Hard Maximum connections to the backend server.

当初、最後の一文が印象に残って、maxの設定値がapache全体のbackendへの接続数だと勘違いしてしまった。↓のMLの人も俺と同じ勘違いをしている

ProxyPass /myapp ajp://localhost:8009/myapp max=2

This is a simplified config, but is enough to reproduce the issue, which is that the max parameter has no effect. If I through 10 concurrent requests to Apache, all 10 are forwarded to Tomcat at the same time, while I would like to have them forwarded 2 by 2. Should I use something other than the max parameter for this?

"In the Prefork MPM, this is always 1"なんだから、もしProxyPassのmaxがapache全体のhard limitだったら、prefork では backendへの接続が最大1になってしまう。どう考えてもそれはおかしい。

min: 0

1プロセスあたりのbackendサーバと常に接続しているコネクションの最小数

smax: max

1プロセスあたりのbackendとの接続に応じて生成されるコネクションのsoft limit。ttlを過ぎたコネクションは切断される

ttl: -

smaxを越えたコネクションの生存時間(秒)

mod_proxy_balancer用のparameter

ProxyPassをbalancer://~ に使用する際のparameter

timeout: 0

フリーのworkerを取得するまでの待機時間(秒)。defaultの0秒では待機しない。

stickysession: -

backendへロードバランスする際に使用するスティッキーセッション名。普通はCookie等で使用されるJSESSIONID(tomcat)、PHPSESSIONID(php) など。大文字、小文字は判別される。

apache 2.2.6 からは "|"で区切って、cookie/url用のsession名を指定できる。

If the backend application server uses different name for cookies and url encoded id (like servlet containers) use | to to separate them. The first part is for the cookie the second for the path.

Changes with Apache 2.2.6

*) mod_proxy: Allow to use different values for sessionid
in url encoded id and cookies. PR 41897. [Jean-Frederic Clere]

というのも、tomcatの場合cookieでは"JSESSIONID"だけど、cookie不可の場合はurlに;jsessionid という文字を埋め込むのでこの書き方が必要になる。

ProxyPass / balancer://mycluster/ stickysession=JSESSIONID|jsessionid nofailover=On

まとめ

  • ex) workerで、backendへの同時接続数を少なめにして、timeout/retryを短めにする設定

timeoutとconnectiontimeoutはProxyTimeoutで設定しても良い。

MaxClients      400
ServerLimit     16
ThreadsPerChild 25

<Proxy balancer://mycluster>
  BalancerMember http://backend1:8080 min=0 max=5 timeout=30 connectiontimeout=30 retry=30 disablereuse=on keepalive=off
  BalancerMember http://backend2:8080 min=0 max=5 timeout=30 connectiontimeout=30  retry=30 disablereuse=on keepalive=off
</Proxy>

<Location /app >
  SetEnv            proxy-nokeepalive 1
  ProxyPass              balancer://mycluster/app
  ## apache 2.2.12 and later
  ProxyPassReverse  /app balancer://mycluster/app
</Location>

ProxyPassReverse内のbalancerについては、こっちで書いた。

あとで調べる

  • ProxySet

parameterはProxySetでも設定できるので、複数のBalancerMemberに同じparameterを設定する場合は ProxySet balancer://~ を使うほうが便利そう。

ProxySet balancer://foo lbmethod=bytraffic timeout=15 

ただ、書いてみたらtimeoutは書けるけどmin,max,keepalive等が書けないっていう微妙な感じ。
そもそも balancer:// にはkeepalive の設定が出来ないだけであって、

ProxySet http://foo keepalive=on

であれば問題なかった。