レプリケーションとQ4Mを使う
はじめに
さぁ Driver 集中講義二回目です。
ホントは別々に書こうと思ったのですが MySQL 関連という事でまとめて紹介します。
レプリケーションを使う
MySQL にはレプリケーション機能があるのはとても知られている事です。
レプリケーションの無い MySQL は、ブラウン管の無いブラウン管テレビくらいの物でしょう。
そして ORM でも切っても切れない物では無いでしょうか。
Data::ObjectDriver でも livedoor のどっかのサービスで自作 Driver 書いて使ってるとか
DBIC の世界でも DBIx::Class::Storage::DBI::Replicated なんて物があるようです。
もちろん Data::Model でも対応しています。
Driver::DBI::MasterSlave です。 Driver::DBI を使った事がある人ならとても簡単な物となっています。
use Data::Model::Driver::DBI::MasterSlave; my $dbi_connect_options = {}; my $driver = Data::Model::Driver::DBI::MasterSlave->new( # master の接続設定 master => { dsn => 'dbi:mysql:host=master.server:database=test', username => 'master', password => 'master', connect_options => $dbi_connect_options, }, # slave の接続設定 slave => { dsn => 'dbi:mysql:host=slave.server:database=test', username => 'slave', password => 'slave', connect_options => $dbi_connect_options, }, ); # base driver の設定をする base_driver $driver;
上記の用に master => $conf, slave => $conf という形で設定するだけです。 $conf の中身は先日紹介した Driver::DBI の設定と全く同じ物がつかえます。
仕組み
Driver::DBI も 内部的には DBI のインスタンスを使って DBI 接続を行っていて DBI::MasterSlave では DBI のインスタンスを2個作って、それぞれ master, slave 用として使います。
Driver::DBI は SELECT クエリでは r_handle を使い それ以外のクエリでは rw_handle を使っており、それぞれの handle にたいして master, slave の DBI のインスタンスを割り当てるので、うまい具合 master, slave でクエリを振り分ける事ができるのです。
注意
txn_scope を使ったトランザクション中は、全てのクエリが rw_master に向かいますので注意してください。
複数の slave を扱うにはどうするの?
DBIC の世界だと複数台の slave をうまいこと扱ってくれる仕組みがあるのですが Data::Model では、そのような仕組みはありません。
バグ?手抜き? いえいえ、元からこういう設計です。
そういった複数台の slave を持つ場合には lvs などを使って、より低いレイヤーにて分散環境を整えてください。
このような低いレベルでやるべき処理を Perl のレイヤでやるよりかは、それなりに実績がある低レイヤ処理するべきだと思っています。
しかも slave の分散クエリなんて lvs で十分事足りるし Perl のコード書くよりも高度な事ができるので良いです。
まぁ、インテグレーションというのは時と場合によって思うとおりの道具が使えないでしょうから、細かい要求などは Data::Model::Driver の自分向けに書いてくださいという事になります。この辺の詳しい話は後日書きます。
Q4M を使う
Data::Model の特色の一つとして Q4M 対応が行われているということです。
このあたりのエッセンスは3日目( http://perl-users.jp/articles/advent-calendar/2009/data-model/03.html )に紹介してますが、3日目は超応用編だったので今日は Driver::Queue::Q4M の使い方を紹介します。
Q4M とは
Q4M とは、サイボウズラボの奥一穂(以下略
スキーマ定義
スキーマ定義は、通常の定義と殆どおんなじ感じです。
package TestQueue; use base 'Data::Model'; use Data::Model::Schema; # Queue::Q4M の mixin が必須 use Data::Model::Mixin modules => ['Queue::Q4M']; use Data::Model::Driver::Queue::Q4M; my $driver = Data::Model::Driver::Queue::Q4M->new( dsn => 'dbi:mysql:database=test' ); base_driver $driver; install_model queue_test => schema { columns qw/ id job_name /; # as_sqls の為にも TYPE=Queue を入れとく schema_options create_sql_attributes => { mysql => 'TYPE=Queue', }; };
Q4M 専用のメソッドを追加するために mixin を指定するのと CREATE TABLE 文の為に schema_options create_sql_attributes を指定します。
as_sqls で出来上がる SQL は下記のようになります。
CREATE TABLE queue_test ( id CHAR(255) , job_name CHAR(255) ) TYPE=Queue;
Q4M では index のサポートが行われていないので、 primary key などの事は忘れましょう。
もちろんユニーク制約なんてのも使えませんからね。
Queue を作る
Q4M の Queue を作るのはとても簡単です!
なんてったって普通の MySQL のテーブルの用に扱えるのが Q4M の良いところの一つなんで、普通に set メソッドして INSERT すれば良いんです.
# queue を一つ作る $queue->set( queue_test => { id => 1, job_name => 'get http://example.com/', }, );
Queue を読む
Q4M を使う場合は queue_wait を使って dequeue してからデータを読むことが一般的ですが抜く、一応 Data::Model ごしでも Queue の内容を直接読むことが出来ます。
ただし primary key とかは無いので where とか使って見る必要があるでしょう。
index を使ってないですが、そもそも Q4M に Queue が大量に保存されてることはありえないので問題にならないでしょう。大量にあるんだったらお前のアプリの書き方がとち狂ってる。
# queue を読む my($q) = $queue->get('queue_test'); warn $q->job_name;
Queue の削除
普通は Q4M が勝手に削除するんですが、どうしても手動で消したい人の為に書いておきます。
基本的にはスキーマオブジェクトの delete メソッドを叩くだけです。
# こういう delete はできない $q->delete; # 直接 query を吐いて delete する $queue->delete('queue_test', { where => [ id => 1, ], }); # DELETE FROM queue_test; はこれ $queue->delete('queue_test', {});
$q->delete のように Row オブジェクトの delete メソッドは primary key が無いテーブルには使えないため上記のコードのようなコメントになっています。
update
(略
dequeue する
Q4M では queue_wait を使って dequeue をします、細かいことはドキュメント読んでください。
まぁこんな SQL ですね。
mysql> SELECT queue_wait('high_priority_table', 'low_priority_table', 10);
これに相当する Data::Model の使い方としては queue_running メソッドを使います。
my $queue = TestQueue->new; my $retval = $queue->queue_running( high_priority_table => sub { my $row = shift; # 何かの処理 }, low_priority_table => sub { my $row = shift; # 何かの処理 }, timeout => 10, );
queue_running の引数に table_name => CODE リファレンス という構造の HASH を渡してあげます。
timeout だけは例外的に queue_wait に渡すタイムアウトを指定します。
table_name の queue table に enqueue されると、オーナーモードになった行の Row オブジェクトを引数としてコードを実行します。
簡単ですね。
queue_abort
Q4M では queue が何らかの処理で以上になった時に queue の先頭に enqueue してくれる queue_abort という物があります。
もちろん Data::Model でも使えます。
my $ret = $queue->queue_running( queue_test => sub { my $row = shift; warn $row->id; warn $row->job_name; $queue->queue_abort; }, ); warn $ret;
このようにスキーマオブジェクトから生えている queue_abort メソッドを呼ぶだけです。
$ret の内容は、 queue_running が失敗してるだけ undef になります。
ちなみに CODE リファレンス中で die してしまった時も内部的には queue_abort されます。
eval { my $ret = $queue->queue_running( queue_test => sub { die "abort"; # ここで queue_abort が呼ばれる }, ); }; $@ and warn $@; # 'abort' と表示 warn $ret;
ここの $ret も undef です。
queue_end
Q4M で queue の正常終了を表す queue_end という物が用意されています。
Data::Model では CODE リファレンスの中だけが Q4M のオーナーモードでいるという設計になっているので、 CODE リファレンスの中で queue_abort が呼ばれないまま return した時に自動的に queue_end が呼ばれます。
my $ret = $queue->queue_running( queue_test => sub { return; # ここで queue_end が呼ばれる }, ); warn $ret; # 処理した queue テーブル名を表示
queue の処理が成功した場合に queue_running は queue_wait で処理した queue table 名を戻り値として返します。
queue_running と queue_abort はどこから来たの?
冒頭に Queue::Q4M mixin を使う設定をすると書いてありますが、この Mixin がスキーマオブジェクトに queue_running と queue_abort メソッドを生やしているんです。
単純に Driver に処理を delegation してるだけです。
まとめ
今日は、レプリケーションと Q4M を Data::Model で使う為にどうするかといった事を紹介しました。
サックリ紹介するつもりが長文になって DNBK です。
明日はキャッシュ戦略について紹介します。