tokuhirom's Blog

MySQL binlog API は row based mode でこそ、その真価を発揮する!!

空前の MySQL binlog API ブームですが、みなさん libreplication の examples/basic-[12] を実行するだけで満足してしまっているようです。しかし、libreplication のおもしろいのは examples/mysql2lucene の方なんです。

3つのロギングモード

普段はあまり意識しないかもしれないですが mysql の binlog には statement based, row based, mixed の3種類があります。

statement based は、実際に実行された SQL が記録されます。一部の関数でちょっと危険です。

row based では、実際に変更された行のデータが記録されます。

mixed では、危険な関数をつかった場合などには row based で記録し、そうでなければ statement based で記録します。最近はこれがデフォルトです。

詳細は http://thinkit.co.jp/article/95/1?page=0,2 このへんなどをご欄ください。

ロギングモードを row based にしよう

SET GLOBAL binlog_format = 'ROW';

とかやると、binlog_format が row based に変更されます。

この状態で binlog API をたたくと、TABLE_MAP_EVENT, WRITE_ROWS_EVENT, UPDATE_ROWS_EVENT, DELETE_ROWS_EVENT の4種類のイベントがおくられてくるようになります。

行データが一緒におくられてくるので、それをつかって手元のデータをアップデートすればいいのです。SQL statement を解析したりする必要はありません。

実際に MySQL::BinLog でためしてみる

以下のようなプログラムで簡単に行のデータをとれます。

use 5.016000;
use MySQL::BinLog;

my %table_map;
my $url = shift or pod2usage;
my $binlog = MySQL::BinLog->new(MySQL::BinLog::create_transport($url));
$binlog->connect();
$binlog->set_position(4);
say("connected: $binlog");
while (my $event = $binlog->wait_for_next_event()) {
    my $type = $event->get_event_type();
    if ($type eq TABLE_MAP_EVENT) {
        $table_map{$event->table_id} = $event;
    } elsif ($type ~~ [WRITE_ROWS_EVENT, UPDATE_ROWS_EVENT, DELETE_ROWS_EVENT]) {
        my $table_event = $table_map{$event->table_id}
            or die "Unknown table: " . $event->table_id;
        my $rows = MySQL::BinLog::Row_event_set->new($event, $table_event);
        my $iter = $rows->begin();
        while (my $row = $iter->next()) {
            if ($type eq WRITE_ROWS_EVENT) {
                say("INSERT");
                show_row($row);
            } elsif ($type eq UPDATE_ROWS_EVENT) {
                say("UPDATE BEFORE");
                show_row($row);
                say("UPDATE AFTER");
                show_row($iter->next());
            } elsif ($type eq DELETE_ROWS_EVENT) {
                say("DELETE");
                show_row($row);
            }
        }
    }
}

sub show_row {
    my $row = shift;
    my $fields_iter = $row->begin;
    while (my $field = $fields_iter->next) {
        printf("       TYPE: %-10s STR: %s\n", $field->type_str, $field->as_string);
    }
}

この状態で以下のような SQL を発行します。

INSERT INTO hoi (id, name) values (1, "HOGEHOGE");
UPDATE hoi SET name="FUGAFUGA" WHERE id=1;
DELETE FROM hoi WHERE id=1;

すると、以下のような結果がえられます。

INSERT 
       TYPE: LONG       STR: 1
       TYPE: VARCHAR    STR: HOGEHOGE
       TYPE: TIMESTAMP  STR: 1341954455
UPDATE BEFORE
       TYPE: LONG       STR: 1
       TYPE: VARCHAR    STR: HOGEHOGE
       TYPE: TIMESTAMP  STR: 1341954455
UPDATE AFTER
       TYPE: LONG       STR: 1
       TYPE: VARCHAR    STR: FUGAFUGA
       TYPE: TIMESTAMP  STR: 1341954455
DELETE 
       TYPE: LONG       STR: 1
       TYPE: VARCHAR    STR: FUGAFUGA
       TYPE: TIMESTAMP  STR: 1341954455

まとめ

row based で binlog を記録することにより、行のデータを簡単に取得できます。

binlog API をつかうことで lucene, solr, groonga, rast などの全文検索エンジンにデータをうつすことが容易にできます。通常 solr を運用する場合は minutely で SELECT かけてデータをうつしたりすることが多いとおもいますが、binlog API をつかえばリアルタイムにデータをコピーできます。

mroonga のようなアプローチもありますが、こういうアプローチも面白いのではないでしょうか。mysql storage engine にくらべ制約がすくなく、開発期間も格段に短縮できそうですね。

このような、任意のストレージに replication できるというのが binlog API の主目的だとおもいますが、その他にもデータのモニタリングやらなんやら、アイディアはいろいろ考えられるのではないでしょうか。

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド