Hatena::Groupcatalyst

dann@catalyst このページをアンテナに追加 RSSフィード

2008-04-10

コネクションが無効になった場合のコネクションの再取得?

22:51 |  コネクションが無効になった場合のコネクションの再取得? - dann@catalyst を含むブックマーク はてなブックマーク -  コネクションが無効になった場合のコネクションの再取得? - dann@catalyst  コネクションが無効になった場合のコネクションの再取得? - dann@catalyst のブックマークコメント

プロセス毎に一つのコネクションを保持している場合、何らかのタイミングでそのconnectionが切れてしまったら、そのプロセスではずっとエラーになってしまう気がする。こういう場合、どういう対応をするのだろう。

「コネクションを破棄して新たなコネクションを作り直なおすのをアプリケーション側で作りこむ」というのがあるけれど、できればアプリケーション側で頑張りたくないなぁという気はする。

Javaではこういうのは、connection pool側の仕事でアプリケーション側からは見えない。connection poolは割と色んな実装があって

  • poolしているconnectionの接続が切れててもそのままpoolしていて、いざ使おうとすると例外があがっちゃうもの
  • プールされたconnectionの接続が切れていると、pool中のコネクションを破棄して、再度コネクションを作りなおそうとするもの

とか、色んな実装があるのは見てきたので(マニュアル読んでも書いてなくて、ソースも見れないアプリケーションサーバーとかね...)、再接続の問題って必ず何かを作っていると問題になるので、Perl界隈でもなんか解があるんじゃないかなぁという。

できれば、アプリケーション側で頑張りたくないなぁという。物理的なコネクションと論理的なコネクションを分けているところに、Connection Poolの意味の一つはある気がしていて、それがないと、Connectionが切れていた場合に、アプリケーション側で頑張らないといけなくなっちゃうなぁと。

Javaの界隈だとアプリケーションサーバーの機能として、無効なConnectionを破棄するようなものが多い

こういうのって、Perl界隈でどうやって解決しているものなのかが、ググってはみたものの良く分からなかった。

頑張れる場所は、多分3つあって、Connection Pool or DBそのものの機能で頑張れれば、アプリケーション側はあまり気にしなくてもいいかもしれない(というのは嘘か...) 。

  • アプリケーション
  • Connection Pool
  • DBそのもの

永続的に無効なコネクションを保持したまま、プロセスが生き残ってしまうということだけが問題なので、コネクションが無効になって何らかの例外があがった段階で、そのプロセスを殺してしまうというのが現実解なのかなぁと思いました。

何か他に解があれば、是非教えてください。

# kazeburoさんから、reconnectというコメントが!

reconnectってこっちのことかなぁ。

http://dev.mysql.com/doc/refman/5.1/ja/mysql-reconnect.html

DB側でがんばるって形だなぁ。ただ、この文面読む限りだと使う場面を選びそうな気はする。ただ、DB側でreconnectして失敗した場合のこともアプリケーションはやっぱり意識しないといけないかな。

# もう少し考えてみると、アプリケーション側でプロセス単位でコネクション保持するんじゃなくて、リクエスト単位で保持したほうがBetterなのかもしれないなぁ。そうすれば、最悪そのリクエストだけで死ぬわけで、害は小さいかもしれない。

物理コネクションと論理コネクション

22:51 |  物理コネクションと論理コネクション - dann@catalyst を含むブックマーク はてなブックマーク -  物理コネクションと論理コネクション - dann@catalyst  物理コネクションと論理コネクション - dann@catalyst のブックマークコメント

DBIでconnectする度に毎回物理connectionが取れちゃうっていうんだと、各DAOでconnection取得すると、そのTransactionコミットするとき、どうなっちゃんだという疑問はある。

Javaの場合だと、コネクションプールから論理コネクションが取得されて、その論理コネクションから対応する物理コネクションに対応している。論理コネクションと物理コネクションの対応関係は、多:1になる。

例えば、あるトランザクション中から複数のDAOで論理コネクションを取得しても、実際に対応する物理コネクションが一つになっているということになる。(これはコネクションプールの実装次第ではあるのですが)

なんで、Perl界隈で論理コネクションの話がでてこないのかと不思議に思っていたのだけれど、Perl界隈ではプロセス単位でコネクションを保持するからじゃないかと思った。

要するにPerlではプロセス中心のモデルが主流だけれど、Javaではスレッド中心のモデルが主流になっているという違いがあるということだ。Javaでは普通スレッドベースのアーキテクチャでしか考えないし、プロセスベースで考えることが殆どない。

確かにプロセスレベルで物理コネクションを共有してしまえば、各DAOで論理コネクションを取得する必要なんて殆どない。

違うかなぁ。

Cowなメモリ共有のプラクティス with mod_perl

22:29 |  Cowなメモリ共有のプラクティス with mod_perl - dann@catalyst を含むブックマーク はてなブックマーク -  Cowなメモリ共有のプラクティス with mod_perl - dann@catalyst  Cowなメモリ共有のプラクティス with mod_perl - dann@catalyst のブックマークコメント

mod_perlのことを全然分かっていないので、最近少しずつid:hidedenに聞きながら勉強している今日この頃です。いくつかの記事とid:hidedenなどに聞いた話を含めて、自分の理解している範囲内でまとめてみました。

forkしたプロセス間でのメモリ領域の共有

forkでは全てのメモリが共有された状態・親プロセスと子プロセスでメモリが共有された状態になっている。子プロセスをforkするときには、forkする前に確保したメモリは共有される。しかし、子プロセスがforkしてからモジュールをロードされた場合は、子プロセス数分のメモリを確保しなくてはなりません。

メモリ領域×MaxClients(子プロセス数)分のメモリが、新たに消費されることになります。従って、子プロセスのサイズを小さくできれば、最大で、(減少できるメモリ量)×MaxClients分だけメモリを小さくできるので、極力親プロセスと子プロセスでメモリを共有したほうがよいということになる。

特にmod_perlのプロセスは基本的に1プロセスサイズあたりのメモリサイズが大きくなってしまう。だから、1プロセスのメモリのサイズを小さくすることが重要になってくる。

親プロセスと子プロセスでメモリを共有するためには、以下のことをする必要がある。

  • 親プロセスと子プロセスで共有されているメモリ量を調べる
  • GTopで計測して、特にメモリ使用量が多いものについては、startup.plでロードするようにする
  • 共有されている率が低い場合は、動的にロードされていないモジュールがあるかを調べる
  • useするだけではロードされないものもあって、それはモジュール毎にmod_perl用の初期化方法があるものもあるので、モジュールに従う

ということが必要。

共有メモリの割合

共有メモリのサイズはLinux::Smapsで確認。具体的には、id:naoyaさんの以下のスクリプトで確認。Shared_Clean と Shared_Dirtyのサイズが他のプロセスと共有しているメモリのサイズになるのがポイント。

#!/usr/bin/env perl
use strict;
use warnings;
use Linux::Smaps;

@ARGV or die "usage: %0 [pid ...]";

printf "PID\tRSS\tSHARED\n";

for my $pid (@ARGV) {
    my $map = Linux::Smaps->new($pid);
    unless ($map) {
        warn $!;
        next;
    }

    printf
        "%d\t%d\t%d (%d%%)\n",
        $pid,
        $map->rss,
        $map->shared_dirty + $map->shared_clean,
        int((($map->shared_dirty + $map->shared_clean) / $map->rss) * 100)
}

各モジュールをuseしたときにかかるメモリ量は、Gtopで確認。iandeathさんのサイトから。

http://iandeth.dyndns.org/mt/ian/archives/000624.html

#!/usr/bin/perl
use strict;
use warnings;
use GTop;
my $gtop = GTop->new;
my $before = $gtop->proc_mem($$)->size;
eval $ARGV[0];
die $@ if $@;
my $after = $gtop->proc_mem($$)->size;
my $diff = GTop::size_string($after - $before);
print "$diff : $ARGV[0]\n";

startup.plで読み込まれたファイルでは、apache restartでは読み込みされないので、apache stop/startを実行する。

動的にロードされるモジュールの調査

動的にロードされるモジュールも事前にロードしておけば、親プロセスと子プロセスでメモリを共有できる。

動的にロードされたモジュールを調べる方法は、id:hidedenが詳しく解説している。

http://d.hatena.ne.jp/hideden/20080409/1207740439

ポイントは、子プロセスが死んだときに、INCの差分を見ればよいというものだ。これで動的モジュールがロードできる。

プロセスサイズを小さくするためのその他の検討事項

プロセスのサイズを小さくするという点に限れば、

  • 不要なApacheのモジュールを読み込まない
  • mod_perl上ではなく、Geamanなどに処理を委譲して別のプロセスにする

などをまず先に検討した上で、さらに上記を検討していくという形なのだとは思います。

プロセスサイズを小さくするために、CPANモジュールを使わない!というレベルまでチューニングをやっているという某所もあるらしいので、ここらはスケールアウトさせるレベルと、保守性とのバランスの兼ね合いだとは思います。

Catalystでのプラクティス

id:spiritlooseさんの話も反映すると、startup.plでは、

  • use MyApp ();
  • DBImysqlを指定
  • 動的モジュールのロードチェックして、ロード
use DBI ();
DBI->install_driver("mysql");


その他

  • runtimeでforkさせないのがベストではあるが、メモリリーク対策でMaxRequestsPerChildなどを使っていて、子プロセスが再forkするケースもありうるので、そのときにはfork+fork後に子プロセスが動き新しくモジュールがロードされたときにはコストが高くなるわけなので、そのためにもメモリが共有された状態にしておいたほうがよい
  • startup.plでuseすると、挙動不審になる子もいるので注意が必要
    • これについては、どんなケースでどんなモジュールがダメなのかがよくわかってないので、またわかったら。

参考

  • CoWについてはkounoikeさんの説明が詳しいのと、naoyaさんの実際の例での説明はとてもわかりやすいですね。
  • 動的にロードされているモジュールを調べる方法についてはhidedenの説明が詳しいです。

http://www.typemiss.net/blog/kounoike/20060202-61

http://www.typemiss.net/blog/kounoike/20060212-65

http://devel.aquahill.net/doc/apache_mod_perl_tune.html

http://d.hatena.ne.jp/naoya/20050830/1125365504

http://d.hatena.ne.jp/naoya/20080212/1202830671

http://d.hatena.ne.jp/tokuhirom/20071017/1192589429

http://iandeth.dyndns.org/mt/ian/archives/000624.html

http://d.hatena.ne.jp/hideden/20080409/1207740439

トラックバック - http://catalyst.g.hatena.ne.jp/dann/20080410