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

2009/12/07 | apache, MySQL

なんとなく持続的接続が理解できたような気がする。一部想像で話を進めてきた感はあるが、そこは改めて調べないといけない。で、ここまでの一連の流れの中で、設定すべき箇所を列挙しておくことにする。

apacheの設定

http://httpd.apache.org/docs/2.2/ja/mod/prefork.htm

  • MaxClients
    最大同時リクエスト数を設定。でも当然apacheで使用できるメモリは決まっていて、個々の子プロセスで使用できるメモリ量は、子プロセス数が多いほど割り当てが少なくなる。だからちょうどよさそうなところを割り当てる必要あり。 preforkの場合は子プロセス数に相当するがworkerで設定したapacheでは、この値はスレッド数に相当することに注意。
    値を増やす際はServerLimit値も増やすこと。
  • ServerLimit
    preforkの場合はMaxClientsと同じ値を設定する。
  • StartServers
    apache(親プロセス)起動時に起動させる子プロセスの数。preforkならデフォルト5。
  • MinSpareServers
    アイドル状態にある子プロセスの最小値。つまり常にいくつの子プロセスを待機させておくか。実際の待機数がここで設定した値よりも小さくなると、親プロセスが最大1秒に1個の割合で新しい子プロセスを生成する。
  • MaxSpareServers
    アイドル状態にある子プロセスの最大値。待機中の子プロセスがここで設定した値よりも多い場合は破棄される。例えば一時的にリクエストが増えた場合はそれに応じて子プロセスが起動されるが、リクエストがなくなると必然的に子プロセスが待機状態となる。子プロセスが残っているということはその分メモリを使用している(他にも使っていると思う)ことになるので、必要な分は残し、不要な部分は破棄する。
  • MaxRequestsPerChild
    子プロセスの処理回数の上限。デフォルトは1000。
    子プロセスは基本的に処理状態と待機状態を繰り返す。ここで設定した値を最大処理数として何度でも処理を実行する。しかしお行儀の悪いPHPプログラムとかその他の原因でメモリリークを引き起こすことがある。子プロセスに割り当てられたメモリを一度に食いきってしまうようなことがあると、その子プロセスは落ちるから、当該アクセスに限りエラーとなるが、少しずつリークした場合、当然その子プロセスで使用できる メモリの量が減る。そうしてメモリリークがたまると、結局どこかで落ちてしまう。ここで値を設定しておくと、自動的に子プロセスが再起動されるわけで、メモリリークも解消される。
    いちおうこのディレクティブはKeepAliveディレクティブにも関連するらしいが、それはまた後日調べることにする。

PHPの設定(以前にも書いたけどおさらい)

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

  • mysql.allow_persistent
    持続的接続を可能にするか否か 。デフォルト1(接続可能)。
  • mysql.max_persistent
    プロセスごとの持続的MySQL接続の最大数。デフォルト-1(無制限)。
  • mysql.max_links
    プロセスごとの接続最大数。デフォルト-1(無制限)。
  • mysql.connect_timeout
    タイムアウト時間。デフォルト60(単位は秒)。接続しようと試みて、ここで指定した時間を過ぎても応答が帰ってこなかったらエラー。最初に接続を確立する際(connect)や、SQLを投げた際に結果が返ってくるまでにかかる時間で最大どのくらい待ってやるか、ということ。

あー、よく調べた。一つ賢くなった。

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

2009/12/06 | apache, MySQL
一つの制御用プロセス(親)が子プロセスを起動します。子プロセスはThreadsPerChildディレクティブで指定された一定数のサーバスレッドと接続をlistenするスレッドを一つ作ります。Listenerスレッドは接続が来たときにサーバプロセスに渡します。
子プロセスの位置づけだ。子プロセスは、親プロセス(制御用に起動された子プロセスを管理するプロセス)によって起動される。起動された子プロセスはさらに複数のスレッドを作成する(とりあえず今回は単位を子プロセスまでに絞るのでスレッドのことは調べなかった)。
Apacheはスペアの、つまりアイドルなサーバスレッドのプールを常に維持していて、それらは入ってくるリクエストに答えられるように待機しています。このようにして、クライアントはリクエストの応答が得られるようになるために新しいスレッドやプロセスが生成されるのを待たなくてもよいようになっています。起動初期時のプロセス総数は、StartServersディレクティブで設定されます。その後の稼働中に、Apacheは全プロセスのアイドルスレッドの合計数を見積もって、MinSpareThreadsとMaxSpareThreadsで指定された範囲の中にこの数が収まるようにforkしたりkillしたりします。この操作は非常に自律的なので、これらのディレクティブをデフォルト値から変更する必要はめったにないでしょう。同時に応答することのできるクライアント数の最大数(つまり全プロセス中の総スレッド数の最大値)はMaxClientsディレクティブで決定されます。活動中の子プロセス数の最大値はMaxClientsをThreadsPerChildで割ったものになります。
子プロセスはリクエストのたびに起動されるのではなく(そういうこともあるかもしれないが)、apache起動時(親プロセス起動時)に、設定(httpd.conf等)にしたがって複数の子プロセスを起動する(当然起動直後はアイドル状態だ)。その後、apache(親プロセス)は、アイドル状態にあるスレッド(子プロセス下で動いている)の合計数を見積もって、設定されている上限値と下限値の間で、自動でfork(起動)とkill(破棄)をする。

いちおうapacheがprefolkである(もしくは1.3系である)という前提で調査を進めた。apacheのマニュアルを見る。

http://httpd.apache.org/docs/2.2/ja/mod/prefork.html

一つのコントロールプロセスが、コネクションに対してlistenして、しかるべき時に応答する子プロセスを起動します。Apacheは常に幾つかのスペアかアイドルなサーバプロセスを維持していて、それらは入ってきたリクエストに応答できるように待機しています。このようにしてクライアントは、リクエストが応答される前に、新しい子プロセスがforkされるのを待たなくてもよいようになっています。

つまり親プロセスがポートをlistenしていて、リクエストがあると子プロセスに処理をさせるといった具合。といっても子プロセスはリクエストのたびに生成(fork)されるのではなく、親プロセスによって、サーバ設定に従い、自動的に適切な数の子プロセスをforkしたりkill(破棄)したりする。

持続的接続とは関係ないけれど、PHPに関連する部分を一つ見つけた。

通常Unixでは親プロセスは80番ポートにバインドするためにrootで起動されますが、子プロセスやスレッドはもっと低い権限のユーザでApacheによって起動されます。UserとGroupディレクティブはApacheの子プロセスの権限を設定するのに用いられます。子プロセスはクライアントに送るコンテンツ全てを読めないといけませんが、可能な限り必要最小限の権限のみを持っているようにするべきです。

apacheはrootで起動するのに、なぜapacheで(PHPで)作成したファイルの所有者がapacheになるのか(httpd.confで設定したユーザになるのか)ということに関連している。apache自体(親プロセス)はrootが起動するのだが、その親プロセスが、より権限の小さい(httpd.confで設定された)ユーザやグループの権限で子プロセスを起動する。そういう権限で動作している子プロセスの元で動いているPHPなわけだから、PHPで作成したファイルの所有者はapacheになる。

今までは「httpd.conf」でUserとGroupで設定するから、と単純に思っていたけど、動作を紐解くとそういう原理になっていたわけか。意外と面白いかも。

持続的接続を理解するために(その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が同じになるわけだから当たり前だ。

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


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