持続的接続を理解するために(その5)

2009/12/05 | apache, MySQL

持続的接続はapacheの子プロセスとの関連性が深い。だからapacheを理解することも必要だ。ということでマニュアルに目を通してみる。

目を通していると昔(サーバ管理をしていた頃)の記憶がちょっとだけよみがえる。PHPのマニュアルとか探すとその記述を見つけた。

http://www.php.net/manual/ja/faq.installation.php

PHPはグルー(糊)です。このグルーは、多くのサードパーティ製のライブラリをくっつけることによりクールなWebアプリケーションを構築するために使用され、直観的で簡単に習得できる言語インターフェイスにより、一つの整合性のある実体として見せることができます。PHPの柔軟性と力は、プラットフォームの安定性と堅牢性に基づいています。グルーによる結合をするためには、OSやWebサーバ、サードパーティ製のライブラリを必要とします。これらの一つの機能が停止した場合、PHPは問題を特定し、速やかに修正する手段を必要とします。実行スレッドを完全に分離しなかったり、メモリセグメントを完全に分離しなかったり、各リクエストで使用される強力なサンドボックスを有さないことで、基本的なフレームワークをより複雑なものにした場合、PHPのシステムに弱点が生まれます。

これは「なぜ、Apache2のマルチスレッドMPMモード(worker)を実運用環境で使用するべきではないのですか?」で記載されている文章。つまり(多分)、モジュール版のPHPを使用する場合はマルチスレッドMPMモードではなくMPM preforkモード(1.3系と同等)で使用しなさい、という意味に取れる。スレッドセーフではない、ってことかな。

マルチスレッドだと、処理の単位はスレッド(だと思う)。workerの場合、スレッドは子プロセスが複数のスレッドを管理しているわけで、子プロセス単位で持続的接続が共有されていると仮定すると、同じ子プロセス内の別のスレッドで同時に別々のリクエストを処理した場合、明らかに問題が出そうだ。こういうことなのかな。

ということで、まず、PHPはpreforkなapacheで動作させないといけないのだろう。

実際調べてみると、Debianやubuntuではapacheのパッケージはapache2-mpm-workerとapache2-mpm-preforkという別々のパッケージがあり、事前にworker版がインストールされていると、mod_phpをインストールする際に、それを削除してprefork版をインストールするようだ。Vineではパッケージこそ分かれていないけど、そもそもworkerで設定されているとPHPが動かないようだ。

そういうことを考えれば、おそらく出回っているサーバ(Linux系)は、ほとんどがpreforkで動作していると思ってよさそうだ。

持続的接続を理解するために(その4)

2009/12/04 | MySQL, PHPの基本

register_shutdown_function()の動作を確認しておく。

http://jp.php.net/manual/ja/function.register-shutdown-function.php

void register_shutdown_function ( callback $function [, mixed $parameter [, mixed $… ]] )

$functionで指定した関数を、当該スクリプトが終了した時点で実行される。出力バッファが空になってから(全ての吐出しが終わった、さらにその後に)呼び出される。コールバック関数を使って出力することも出来るけど、出力バッファの処理と切り離して考える必要がある。出力バッファ内で文字コードの変換とかいれたりしていると、思いもよらぬ結果(複数の文字コードで出力してしまう)といった現象にも遭遇しそう。

あと、この関数は1度コールすると一つの関数を登録する。複数回コールすると複数の関数を登録できる。関数は登録された順番に、スクリプト終了後に実行される。

だから持続的接続での仕様を考慮に入れると、引数にデータベースへのリンクIDなどを渡しておいて、トランザクションをロールバックしたりテーブルのロックを解除する関数を起動すればよさそうだ。

ただし注意が必要で、exit()をコールした場合は、それ以降シャットダウン関数は実行されない。だから、いくらこの関数で後始末をする処理を登録しておいても、スクリプト中にexit()などが記載されていると、やはり後始末がないままで、コネクションが継続してしまうことになる。

exit()はスクリプト中だけでなく、登録された関数中にあっても同様。複数の関数が登録されていて、最初の関数でexit()が呼ばれたりすると、登録された後続の関数は実行されない。ここも注意が必要だ。

テストのときは気軽にexit()することも多いので、気をつけないと。

持続的接続を理解するために(その3)

2009/12/03 | apache, MySQL

Zend Frameworkのマニュアルで、いい記載を見つけた。これも重要だと思う。

http://framework.zend.com/manual/ja/zend.db.html

持続的な接続を使用すると、RDBMSサーバに余計な接続がたまってしまうことに注意しましょう。接続作成時のオーバーヘッドが減ることによるパフォーマンスの向上よりも、それによって引き起こされる問題のほうが多くなりえます。

データベース接続は、その状態を管理しています。つまり、RDBMSサーバのオブジェクトの中にはセッションスコープで存在するものがあるということです。セッションスコープで管理される情報の例としては、ロックやユーザ変数、一時テーブル、直近に実行したクエリの情報(変更された行数、自動生成されたID)などが挙げられます。持続的な接続を使用すると、別のPHPリクエストが作成したデータに誤ってアクセスしてしまう危険が生じてしまいます。

apacheの子プロセスごとにリンクコネクションが引き継がれているわけだから、当然データベース側(MySQL等)では、コネクションがオープンしたままの状態になっていて、直前のスクリプトで生成されたauto_increment値や変更があった行数についても引き継がれてくるわけだ。まぁ、リンクIDが同じになるわけだから当たり前だ。

そういう意味で「パフォーマンスは向上するだろうけど、考慮すべき問題は非常に多くなる」ということだ。

持続的接続を理解するために(その2)

2009/12/02 | MySQL

マニュアルに記載のある、持続的接続を使用した際のメリットとデメリット、その他諸々を列挙してみる。

http://www.php.net/manual/ja/features.persistent-connections.php

機能としての違い

  • 機能の違いは持続するか否か、それだけ。

持続的接続を使用するメリット

  • 確立されたコネクション(apacheの子プロセスごとに管理)があると、それを使用するので、接続を確立する処理が減る。まず最初にapacheによってどの子プロセスに処理が渡されるか決められ、その子プロセスが当該接続を確立済みかどうか調べ、確立された接続があればそれを再利用する。だから処理が減る。つまりその分軽くなる。

持続的接続を使用した際のリスク

  • トランザクション使用時にコミットなりロールバックなりしないでスクリプトが終了(意図する・しないにかかわらず)したり、テーブルをロックしてロックを解除せずにスクリプトを終了すると、当該子プロセスのリンクコネクションは「トランザクション途中」や「ロック中」のままとなる。だから次にその子プロセスに処理が割り当てられると、トランザクション途中やロック中な状態のコネクションを使用する羽目になる(多分希望通りに動作してくれない)。
    結論として、register_shutdown_function()関数を使って、後始末をしてくれる関数を用意しておくか、トランザクションやロックを使用する場合は、そもそも持続的接続を使用しないほうが安全。

なんとなく、少しずつだけど理解できてきたような気がする。

持続的接続を理解するために(その1)

2009/12/02 | MySQL

とりあえず理解していることと理解していないことをメモ。

通常はブラウザからウェブサーバにアクセスがあった場合、アクセスごとに処理される。ページを連続して閲覧したとしても、それらは個々のアクセスとして処理される(連続した処理とみなすためには、別途処理を記述する必要がある)。これをふまえ、クライアントからのアクセスでオープンされたデータベースへのリンクコネクションは、通常そのスクリプト終了時に必ず(自動で)閉じられるが、持続的接続を使用すると、一連のスクリプトが終了しても、リンクコネクションは閉じられない(手動で閉じることは出来ない)。

持続的接続によるリンクコネクションが閉じられるタイミングについての記述が見つけられない。ふとマニュアルの設定項目に目を通したらいくつか関連しそうな記述を見つけた。

http://www.php.net/manual/ja/mysql.configuration.php

  • mysql.allow_persistent
    持続的接続を可能にするか否か 。
  • mysql.max_persistent
    プロセスごとの持続的MySQL接続の最大数 。
    そもそも持続的接続ならプロセスで必要な接続は一つでいいのではないのか。
    それともプロセスで複数の持続的接続(違うデータベースへの接続)をおこなうということなのか。
  • mysql.max_links
    プロセスごとの接続最大数。これは持続的接続に限った話ではない。
  • mysql.connect_timeout
    接続の有効時間。応答待ち時間と兼用。

調べれば調べるほどわからないことは増える。とりあえず接続時間(切断されるタイミング)という意味ではmysql.connect_timeoutという設定項目に関連性がありそうだけど、これってタイムアウト処理だから違うよね。

再度マニュアルに戻り「持続的データベース接続」の項を参照した。ここにapacheとの絡みの記述がある。

2番目は、最も一般的ですが、PHPをマルチプロセスWebサーバー(現在はApacheのみが含まれます)のモジュールとして実行する方法です。マルチプロセスサーバーは、通常、実際にWebページを送信する複数のプロセス(子)を管理するプロセス(親)を有しています。リクエストがクライアントから来ると、親プロセスは、他のクライアントにすでに送信を行っていないクライアントの一つに渡します。このため、同じクライアントが2番目のリクエストをサーバーに送信した際に最初のではなく他の子プロセスにより送信が行われる可能性があります。持続的接続がオープンされているとき、SQLサービスにリクエストを行うそれぞれのページはSQLサーバへの確立された接続を再利用することができます。

ブラウザからリクエストがあった場合、apacheのプロセス(親)は空いている子プロセスを使って応答する。だから連続したアクセスであっても、常に同じ子プロセスから応答がかえるわけではない、ということか。でもよくわからない。もう少し読み進むと・・・。

例えば、20の異なっ た子プロセスがSQLサーバーへの持続的接続を行うスクリプトを実行した場 合、各子プロセス毎にSQLサーバーへの20の異なった接続が行われます。

つまりapacheの子プロセスごとにデータベースへのリンクコネクションが張られるということか。これはapacheの子プロセスの最大数とmysql.max_persistentやmysql.max_linksの設定と密接に関係しそうだ(そういえば昔どこかでそういう記述を見た覚えがある)。

しかし、気をつけなければならないことが一つあります。それはデータベースへの接続数を制限して使用している場合に、持続的な子プロセスの接続数がその数を超えると問題が発生し得ることです。もしデータベースの同時接続数の制限が16だとして、サーバに多くのアクセスがあったため17個の子プロセスが接続しようとするとそのうちの一つは接続に失敗します。もしスクリプトにコネクションをシャットダウンしないようなバグ(例えば無限ループ)があると16程度の同時接続しか許容しないデータベースはすぐにダメになってしまいます。使用しているデータベースが、中断された、もしくは使用されていないコネクションをどのように扱うかを確認してみてください。

こういうことだ。今すぐにイメージできないけれど、図を描けばすぐに理解できそうだ。

このページで少し理解が進んだかもしれない。まずCGI版では動作しないということ。これは当たり前。CGI版では接続するたびにPHPのインスタンスが生成される(逆を言えば接続が終了するごとにインスタンスが破棄される)ので、持続のしようがない。マルチプロセスなウェブサーバ(apache)のモジュールや、マルチスレッドなウェブサーバ(IISなど)のプラグインとして動作する場合に限り、持続する。

ということでapacheの子プロセスの生存期間を調べてみるのがよさそう。


守谷市(まちの情報ポータル) 無料アンケートレンタルjpForm.net