pg_pconnectに気をつけろという話に気をつけるべき理由

pg_pconnectに気をつけろ!! よくきたはてダ 2009/2

経験上pg_pconnect()を使うと本気であまり接続が切れません.切れる条件としては多分この辺.
1. httpdがMaxRequestsPerChildなりSEGFAULTの都合で死んだ
2. PostgreSQL側の設定でtimeoutした
3. initscriptなりで停止や再起動させた

(途中省略)

結論

おこちゃまなので結論が先にないわけですが,やっと結論.

あなたはそれでもpg_pconnect()を使いますか? それとも障害対応で人間辞めますか?

上の「経験上」の話にはApache一般の基本動作にまつわるごく基本的なポイントが抜け落ちている。

Apache(とmod_php)はprefork型とよばれるプロセス構成で稼動する。稼動するhttpdプロセスの数は次のような設定によって左右される。数値はそのデフォルト値。

 StartServers       5
 MinSpareServers       5
 MaxSpareServers      10
 MaxClients          150

ピーク時にアクセスがぶわーっと来て例えばhttpdプロセスが50本くらい並行稼動する状態になったとしよう。それぞれが内部でphpスクリプトを実行しておりそこでpg_pconnect()を使っていたとすると、postgresqlの接続も50本開きっぱなしになる。

試してみよう。testdbデータベースにpg_pconnect()をして簡単なSELECT文を投げるtest.phpスクリプトに、abコマンドでアクセスを浴びせてみる。

$ ab -n 10000 -c 50 http://localhost/test.php
(abの結果は省略)
$ ps ax | grep testdb 
15845 ?        S      0:00 postgres: daemon testdb [local] idle              
15854 ?        S      0:00 postgres: daemon testdb [local] idle              
15868 ?        S      0:00 postgres: daemon testdb [local] idle              
15871 ?        S      0:00 postgres: daemon testdb [local] idle              
15889 ?        S      0:00 postgres: daemon testdb [local] idle              
15896 ?        S      0:00 postgres: daemon testdb [local] idle              
15900 ?        S      0:00 postgres: daemon testdb [local] idle              
15903 ?        S      0:00 postgres: daemon testdb [local] idle              
15905 ?        S      0:00 postgres: daemon testdb [local] idle              
15907 ?        S      0:00 postgres: daemon testdb [local] idle              
15911 ?        S      0:00 postgres: daemon testdb [local] idle              
15913 ?        S      0:00 postgres: daemon testdb [local] idle              
15914 ?        S      0:00 postgres: daemon testdb [local] idle              
15920 ?        S      0:00 postgres: daemon testdb [local] idle              
15923 ?        S      0:00 postgres: daemon testdb [local] idle              
15929 ?        S      0:00 postgres: daemon testdb [local] idle              
15930 ?        S      0:00 postgres: daemon testdb [local] idle              
15931 ?        S      0:00 postgres: daemon testdb [local] idle              
15932 ?        S      0:00 postgres: daemon testdb [local] idle              
15940 ?        S      0:00 postgres: daemon testdb [local] idle              
15941 ?        S      0:00 postgres: daemon testdb [local] idle              
15942 ?        S      0:00 postgres: daemon testdb [local] idle              
15943 ?        S      0:00 postgres: daemon testdb [local] idle              
15944 ?        S      0:00 postgres: daemon testdb [local] idle              
15946 ?        S      0:00 postgres: daemon testdb [local] idle           
(この実験ではこれが40行ほど続く。つまりpg_pconnect()によるDB接続が40本ほど開いている。)

ところが、このあと10秒ないし20秒ほどたったところで同じコマンドを叩くと

$ ps ax | grep testdb 
15984 ?        S      0:00 postgres: daemon testdb [local] idle              
15997 ?        S      0:00 postgres: daemon testdb [local] idle              
16049 ?        S      0:00 postgres: daemon testdb [local] idle              
16060 ?        S      0:00 postgres: daemon testdb [local] idle              
16069 ?        S      0:00 postgres: daemon testdb [local] idle              
16073 ?        S      0:00 postgres: daemon testdb [local] idle              
16075 ?        S      0:00 postgres: daemon testdb [local] idle              
16077 ?        S      0:00 postgres: daemon testdb [local] idle              
16078 ?        S      0:00 postgres: daemon testdb [local] idle              
16079 ?        S      0:00 postgres: daemon testdb [local] idle              
16086 pts/1    R+     0:00 grep testdb

あれあれ?接続が10本ほどしか見当たらない。

つまり、ピークを過ぎてアクセスが少なくなるとapacheは勝手に子プロセスを終了させてゆき、最終的にMaxSpareServersに近いぐらいの数(つまりデフォルトなら10本ぐらい)までhttpdプロセスが減る。そのとき、postgresqlの接続も切れてゆき、やはり10本ぐらいの接続を残すだけになる。「ぐらい」というのは、待機のhttpd子プロセスをどの程度残す(or新たに立ち上げる)かはそのときのアクセス状況と設定値にあわせてapacheが内部で勝手に調整してしまうのではた目には正確に測定しづらいから。

というわけで、pg_pconnect()によるDB接続がなかなか切れないというのはウソである。httpd子プロセスが終了すると同時にそこから呼び出されているDB接続も切れるからだ。

では、httpdのプロセス数が減らない=それにつられてpg_pconnectによるDB接続が減らない=ということがあるとすればその理由はなんだろう?それこそが、

その上でhttpdって何をするかというと,いわゆるHTMLやXML(フィードなど)の生成処理をするだけじゃなく,画像の処理もするんですね.CSSのファイルを読み込んだりJavaScriptのファイルを読み込んだりもしますね.

ということである。つまり、pg_pconnect使ってDB接続しているPHPスクリプトのリクエストを処理したhttpd子プロセスがそのまま後から画像やらcssファイルやらを送り出すために使用され続けてしまっているため、そのDB接続も開きっぱなしのまま残ってしまう現象。

で、そこまでわかっている人間が誰かに伝えるべきことは、pg_pconnect使うと障害対応で人間やめることになるよなどという意味不明な極論ではなくて、例えば「PHPスクリプトを動かすhttpdと画像やCSSを送り出すhttpdは別にしよう」ということだ。 知らずに思考停止しているだけであればまあアレだが(笑)、知っててなおシャレや冗談のつもりで話しているならば誤解を招きすぎである。

なお、pgpoolのことも言及されているようだがpgpoolも結局apacheと同じアイデアでpreforkで複数プロセスを待機させる方式である。pgpoolが常駐するだけでそこそこリソースを食ってしまうというジレンマ。 そんなこともあってか、現代のpgpool-IIではコネクションプーリングよりもレプリケーションや負荷分散のほうに力点が置かれている。

また、MinSpareServersに無意味に大きな値にしていて、実際その数のhttpdが待機しっぱなしになっていたというケースも見たことがあるが、それもまた論外である。 @IT:Apacheパフォーマンス・チューニングの実践(2/2)(@IT 2002/2)でも見て勉強しなおすべきだろう。

話を戻すと、Web層とAP層を分けるというのは環境や言語を問わずWebサイト開発の基本であり、まったく特別なことではない。もちろんPHPを使っていると最初はWeb層もAP層もいっしょくたなのでそのへんの区別があいまいになりがちではあるが、Webサイトがさまざまな負荷が気になるほどのレベルに育ってきたのであれば、画像は別サーバから呼び出そうかな、とか、リバースプロキシ立てようかな、とかは誰もが考えるべきことだ。

Java+Tomcatで開発している人はそのへんをまったく意識せずとも実践している。Web層はApacheに任せて画像やCSSや静的HTMLなファイルを、AP層はTomcatでServletやJSPを実行させ、その間をmod_jk(最近ではmod_proxy_ajp)で接続する、といった方式がデファクトだから。

perlとmod_perlでやっている人たちもそのへんの歴史はとても古く、例えば

大規模なeコマースサイトを Apache と mod_perl で構築する(2001年に書かれた古い文書だがこの頃から考え方はあったということ)

プロキシサーバでは、mod_perl を組み込まずに、小さなバイナリの Apache を走らせています。この Apache には、いくつかのApache 標準モジュー ルと、セッションの Cookie を発行するカスタムバージョンの mod_session をインストールしてあります。プロセスサイズが小さいため、 1台のマシンで 400 個程度のApache プロセスを走らせることが可能です。これらのサーバでは、画像ファイルへのリクエストは自身で制御し、ページへのリクエストはアプリケーションサーバに転送します。

といった感じ。

その他のsee also:

追記

ごめんなさい.もしかしたら勘違いしていました. - よくきたはてダ

っで,エンジニアはどこまでやらないといけないのか書いてあげないの? - よくきたはてダ

同一サーバー資産で複数目的のhttpdの立ち上げをすると当然メモリー消費の具合も変わってきます.大丈夫すか? リバースプロキシーとかやるとさらにメモリーが必要になるでしょう.

ちなみにCSSなどだけではなく静的HTMLのリクエストも同様に無駄な接続が維持されることになります.これも「動的HTML生成と静的HTML生成のhttpdは別にするべき」とかですかね.

さてこれってサーバー構成の対処だけでできるんですかね? ファイル配置の設計見直しとかが必要だろうし話がずいぶんでかくなってくる気がするんですけど.

(途中省略)
結論としてはこのネタもエントリーも所詮木,もしくは枝の話です.

無駄な接続ってどの接続のことだろう?わからん。まあともかく、

  • 最低限のmoduleをロードしたapache(httpd)のプロセスと、 mod_php4/5やmod_perl(mod_perl2)をロードしたapacheのプロセスとが それぞれどの程度のメモリを食うかを測るためのpsコマンドの使い方とか、
  • それらを用いてリバースプロキシを組むときに今のディレクトリ構造とうまくあわせるためのProxyPass指定やAliasの切り方、

なんて話はもう何年も前からそう珍しくないノウハウとして蓄積されていることなんだから、ググレカスとしか言いようがない。かなり古いブログ記事も含めて上にsee also...として並べているのがその一部であり傍証である。

そもそもWeb層とAP層を分ける=静的コンテンツ用を送出するhttpdと動的処理をするhttpdとを分ける=という初歩の初歩を実務レベルでやれてないサイトはすでに障害対応の嵐で人間やめるはめになって淘汰されているのだろう。なぜならそんなノウハウは現代のWebサイト開発業界において枝葉でも木でも森でもなく地面だから。Java+Tomcatでやってる人は知らず知らずにこれを実践できてしまっているよという先述の話もその傍証のひとつである。

Apacheクックブック 第2版 ― Webサーバ管理者のためのレシピ集
Ken Coar Rich Bowen
オライリージャパン
売り上げランキング: 29057

LinuxサーバHacks―プロが使うテクニック&ツール100選
ロブ フリッケンガー
オライリージャパン
売り上げランキング: 75833

トラックバックURL

このエントリーのトラックバックURL:
http://www.ywcafe.net/mt/mt-tb.cgi/950

トラックバック

» [Neta] っで,エンジニアはどこまでやらないといけないのか書いてあげないの? from よくきたはてダ
それをやればOK? で、そこまでわかっている人間が誰かに伝えるべきことは、pg_pconnect使うと障害対応で人間やめることになるよなどという意味不明... 続きを読む

コメントする

(初めてのコメントの時は、コメントが表示されるためにこのブログのオーナーの承認が必要になることがあります。承認されるまでコメントは表示されませんのでしばらくお待ちください)


画像の中に見える文字を入力してください。