DBIx::Class::Storage::DBI::ReplicationにてMasterで参照クエリを発行する方法

DBIx::Classのちょっとしたメモ、その2。・・・と、思いきや長くなったので一記事に。


Masterで参照クエリを発行する方法なんだけど、ちょこちょこと調べて見たけど情報が殆どないのね。

ただみんな色々考えているようで、例えば

use Blog::Schema;

my $schema = Test::Blog::Schema->connect("dbi:SQLite:$master");

# set slave data sources.
$schema->slave_connections(
    ["dbi:SQLite:$slave_1"],
    ["dbi:SQLite:$slave_2"],
);

# do something in slave connection.
$schema->slave->resultset('Entry')->find(1);
DBIx::Classでスレーブに接続する - libnitsuji.so

このようにslaveメソッドを生やして明示的に呼ばせる方法や、

my $schema = MyApp::Schema->connect( @master_connect_info );
my $master_blog = $schema->resultset('Blog')->find( $id );
my $slave_blog  = $schema->resultset('Blog::Slave')->find( $id );
http://hibinokoto.jp/archives/2008/05/post-263.html

このようにresultsetの引数で対応する方法などまー対処方法は色々あるわけです。

でもやっぱりDBIx::Class::Storage::DBI::Replicationのようにインタフェースをまったく気にせずになんとかできないもんかと思い、ちょっとコードを覗いてみました。

するとですね、DBIx::Class::Storage::DBI::Replicationの実装がカナリ綺麗で結局のところ参照系のメソッドならread_sourceを使い、更新系のメソッドならwrite_sourceを使うというとてもシンプルなものだったのです。

なら話は簡単。Masterでクエリ発行したいなら、参照系のメソッドでもwrite_sourceを使うように一時的に上書きしちゃえばいいだけだと思ったわけです。

### MySchema.pm

package MySchema;
use strict;
use warnings;
use base qw/DBIx::Class::Schema/;

__PACKAGE__->load_classes(qw/Sessions/);

1;

### main.pl

use MySchema;

MySchema->storage_type('::DBI::Replication');
my $schema = MySchema->connect(
   ['dbi:mysql:test:master','user','pass',{ on_connect_do => ['SET NAMES UTF8'] },],
   ['dbi:mysql:test:slave' ,'user','pass',{ on_connect_do => ['SET NAMES UTF8'] },],
   { limit_dialect => 'LimitXY' },
);

my $ses = $schema->resultset('Sessions');

# 一時的に変更する
local $schema->storage->{read_source} = $schema->storage->{write_source};

# Masterで参照クエリが発行される
print $ses->search->count;

計画通り!!!!!うまく動きました。

ちゅーわけでこのようにすればlocalスコープの間だけMasterで参照クエリを発行することができるようになります。

・・・ただただただただただね。

やっぱり、このlocalすらもどこかに隠蔽したいと考えるのが人情というものでして。どうにかならんもんかと思ったわけです。

で何故Masterで参照クエリを発行したいのかを改めて考えてみたんですが、結局のところトランザクション中に更新したテーブルに対してSELECTしたいときとかそーゆー場合だけなんですよ。

ということはつまり、トランザクション中の時に自動的にMasterを参照するような実装にしとけばいいんですよ、そうなんです。

### MyReplication.pm

package MyReplication;
use strict;
use warnings;
use base qw/DBIx::Class::Storage::DBI::Replication/;

sub txn_do {
    my $self = shift;
    my $code = shift;
    local $self->{read_source_orig} = $self->{read_source};
    local $self->{read_source}      = $self->{write_source};
    $self->write_source->txn_do($code);
}

1;

### main.pl

use MySchema;

MySchema->storage_type('MyReplication'); # MyReplicationに変更
my $schema = MySchema->connect(
   ['dbi:mysql:test:master','user','pass',{ on_connect_do => ['SET NAMES UTF8'] },],
   ['dbi:mysql:test:slave' ,'user','pass',{ on_connect_do => ['SET NAMES UTF8'] },],
   { limit_dialect => 'LimitXY' },
);

my $ses = $schema->resultset('Sessions');

# Slaveで参照クエリが発行される
print $ses->search->count;

$schema->txn_do( sub {
	# Masterで参照クエリが発行される
	print $ses->search->count;
} );

# Slaveで参照クエリが発行される
print $ses->search->count;

とゆー感じです。

念のためread_source_origにSlaveのDBハンドルを保持しているのでトランザクション中にSlaveを使いたいというケースが発生しても対応できるようになってます。良かったね!


あと最後に余談というか、最新のDevelop版DBIx::Class(v0.08099_04)を覗いてみたところ、DBIx::Class::Storage::DBI::Replicationの代わりに、DBIx::Class::Storage::DBI::Replicatedというモジュールが入ってます。

あまり中身を詳しくは見てないんですが、基本DBIx::Class::Storage::DBI::Replicationと同じっぽいです。色々機能拡張はされてそうですが。

DBIx::Classレプリケーション周りで他にもこんなやり方あるよー、とか知ってる方がいたらぜひ教えてください><

いじょ!