DBIのSubclassを書く 
Saturday, January 8, 2011, 07:48 AM - Programing, Programing / Perl
いつだかTwitterでMySQL+Memcachedがどうたらこうたら言っていたが、最終的な結論としてDBIのSubclassとして実装するのが最強なんじゃないのかなあ、とか思う、これが大晦日だか元旦
そんな事を思い付いてしまったら年越し蕎麦をゆっくり食って餅の無差別殺人事件をゲラゲラ笑いながら見ている場合ではない
ともかく最適化を捨てても、既存のDBIなコードを変更せずともMemcachedの恩恵を受けるメリットは非常に大きいと思う
こうなれば「あけましておめでとう」などと心にもない挨拶回りをしている場合ではない、大体お年玉貰えないし

と、いう事でDBIを解読しながら色々模索するが、悩む部分がいくつかある

#!/usr/bin/perl
use DBI;

printf("dbh=%s\n",my $dbh = DBI->connect("dbi:mysql:database=a;host=172.16.2.130;port=3306","a",undef));
$dbh->{myparam} = "^o^";
printf("dbh->myparam = %s\n",$dbh->{myparam});
$dbh->disconnect();

__END__

の、出力
> dbh=DBI::db=HASH(0x13b13c60)
> dbh->myparam =
これは一体どういうことだろうか
HASHと見えているが、値が反映されない

少なくともこの状態はSubclassを書く上での例えば
package MyDBI;
use base qw(DBI);
use DBI;
use Cache::Memcached::libmemcached;

sub connect
{
my($h,@p) = @_;
$h->SUPER::connect(@_);
$h->{memcached} = new Cache::Memcached::libmemcached(...);
return($h);
}

こういうコードでCache::Memcached::libmemcachedのオブジェクトを維持できないことになる

全くわからなくてtieしてblessしてなんてやっていたが、実に単純
種が割れてしまえばなんて事はないのだが、丸一日要してしまった
DBIのdbh->{}やsth->{}な変数アクセスはtieされているので
package MyDBI::db;
use base qw(DBI::db);

sub FETCH
{
my($h,$k) = @_;
if($k =~ /^mydbi_/){
$h->{$k};
}else{
$h->SUPER::FETCH($k);
}
}

sub STORE
{
my($h,$k,$v) = @_;
if($k =~ /^mydbi_/){
$h->{$k} = $v;
}else{
$h->SUPER::STORE($k,$v);
}
}

このように割り込んで変数処理をしなければならない
よく考えれば当たり前の話なのだが

また$dbhとか$sthをhashとして扱うと値が見えないが、当然FETCH/STORE内で出力させれば
> FetchHashKeyName = NAME
> TraceLevel = 0
> ImplementorClass = DBD::mysql::db
> dbi_imp_data =
> State = SCALAR(0x1b287450)
> Username = miko
> Errstr = SCALAR(0x1b287410)
> Driver = DBI::dr=HASH(0x1b7bcab0)
> Statement =
> Name = database=a;host=172.16.2.130;port=3306
> dbi_connect_closure = CODE(0x1b7bcbc0)
> RootClass = CachedDBI
> Err = SCALAR(0x1b2873d0)
ちゃんとhashとして見える
これが見えなくて->{Statement}とか->{ParamValues}とか->{Database}とかで困っていた

こういう変数絡みでいくらか問題を抱えていたが、結局FETCH/STOREのこれだけで解決
脳味噌の柔軟性が失われつつあるなあ、とか思う事件

残るはアプローチの問題

Memcachedへ値を置く時のkeyをどうするか
当然DBIと異なる方法(executeでkeyを与えなければならない、とか)は方向性と反してしまう(useの一行を書き換えるのみで済ませたい)
普通に考えればSQL Queryがkeyだが、この場合insert/updateの後、一度selectしなければMemcachedを使えない

どのタイミングでMemcachedへ聞きにいくか
->execute(...)でSQL Queryを発行している以上、ここしかない
しかし実際の値を引っ張るのは->fetch(...)なので関数を跨ぐことになる、余り好ましくない
->execute(...)にしても内部へ割り込みたい(StatementやNUM_OF_FIELDSの関係)が、xsなので厳しい

->execute(...)でチェックのみ、->fetch(...)するとしても->fetch(...)は複数回呼ばれる
対してMemcachedのgetは一度
と、なると->execute(...)か最初の->fetch(...)で値を全て読み込む事になる、好ましくない
しかし->fetch(...)ごとにkeyを与えると、分散している場合に整合性が保てなくなる可能性がある、それを回避するために->fetch(...)し続けて値が無ければ->execute(...)というのもおかしな話だ

なんて頭の中で巡り巡る
この割り込み部分はまだいい案が出ない

とりあえず試し書きしたコードを貼っておく


package CachedDBI;
use base qw(DBI);
use DBI;
use Cache::Memcached::libmemcached;

package CachedDBI::db;
use base qw(DBI::db);

sub FETCH
{
printf("%s::FETCH(%s);\n",__PACKAGE__,join(",",@_));
my($h,$k) = @_;

if($k =~ /^cache/){
#while(my($k,$v) = each(%{$h})){
# printf("->%s = %s\n",$k,$v);
#}
$h->{$k};
}else{
$h->SUPER::FETCH($k);
}
}

sub STORE
{
printf("%s::STORE(%s);\n",__PACKAGE__,join(",",@_));
my($h,$k,$v) = @_;

if($k =~ /^cache/){
$h->{$k} = $v;
}else{
$h->SUPER::STORE($k,$v);
}
}

sub cache
{
printf("%s::cache(%s);\n",__PACKAGE__,join(",",@_));
my($h,@param) = @_;

$h->{cache} = new Cache::Memcached::libmemcached(@param);
$h->{cache}->set_binary_protocol(1);
printf("->cache = %s\n",$h->{cache});
}

package CachedDBI::st;
use base qw(DBI::st);

sub execute
{
printf("%s::execute(%s);\n",__PACKAGE__,join(",",@_));
my($h,@param) = @_;

printf("->Statement = %s\n",$h->{Statement});
printf("->ParamArrays = %s\n",$h->{ParamArrays});
printf("->ParamValues = %s\n",$h->{ParamValues});
printf("->ParamTypes = %s\n",$h->{ParamTypes});
printf("->NUM_OF_PARAMS = %s\n",$h->{NUM_OF_PARAMS});
printf("->Database = %s\n",$h->{Database});
printf("->Database/cache = %s\n",$h->{Database}->{cache});

my $i = 0;
(my $s = $h->{Statement}) =~s/\?/$h->{ParamValues}->{$i++}/go;

if($h->{Database}->{cache} && !($h->{Database}->{cache_fetch} = $h->{Database}->{cache}->get($s))){
$h->SUPER::execute(@param);
}
}

sub fetch
{
printf("%s::fetch(%s);\n",__PACKAGE__,join(",",@_));
my($h,@param) = @_;

if($h->{Database}->{cache_fetch}){
shift(@{$h->{Database}->{cache_fetch}});
}else{
push(@{$h->{Database}->{cache_store}},my $r = $h->SUPER::fetch());
return $r;
}
}

sub finish
{
printf("%s::finish(%s);\n",__PACKAGE__,join(",",@_));
my($h,@param) = @_;
if($h->{Database}->{cache_store}){
my $i = 0;
(my $s = $h->{Statement}) =~s/\?/$h->{ParamValues}->{$i++}/go;
printf("[%s]#%d\n",$s,$h->{Database}->{cache}->set($s,$h->{Database}->{cache_store}));
undef $h->{Database}->{cache_store};
}
$h->SUPER::finish();
}

__PACKAGE__

Comments

Add Comment

Fill out the form below to add your own comments.









Insert Special: