« Pacific という名前の分散ストレージを作り始めた件 | Main | スレッド間で共有する変数のアクセス権制御を C++ コンパイラで強制する方法 »

June 15, 2009

Pacific のクライアントAPI (仮) について

 先週、概要を紹介させていただいた Pacific について。まだ API をフリーズしていないつもりなのですが、だいぶ整ってきた気がするので、ざっくりまとめておきたいと思います。


インストール手順

  1. Thrift をインストール注1
  2. Pacific の svn レポジトリからチェックアウト
  3. Perl ドライバを make (cd driver-perl && perl Makefile.PL && make all test install)
  4. リゾルバを make (cd resolver && make)

テーブルのセットアップ手順

 テーブルのセットアップは、pschema コマンドを使って行います。

# リゾルバの裏側の MySQL は 127.0.0.1:33060 で動作
#
# プライマリテーブル「user」を作成
#   ・ 分散キーの名前は「username」
#      (型は varchar(255) not null charset utf8 collate utf8-bin 固定)
#   ・ カラムとして realname varchar(255) not null,last_tweet_at int unsigned not null
#   ・ ノード内で、セカンダリテーブルとのリレーションを表現するカラム _iid
#      も生成される
#
pschema create-primary --manager=127.0.0.1:33060 --primary=user --hostport=127.0.0.1:33061 --primary-key-name=username 'realname varchar(255) not null,last_tweet_at int unsigned not null'

# セカンダリテーブル「tweet」を作成
#   ・ カラムとして mtime と body (と 上記 _iid)
#
pschema create-secondary --manager=$MANAGER --primary=user --secondary=tweet --primary-key=mtime 'mtime int unsigned not null,body varchar(255) not null'

 この結果、MySQL ノードには、以下のようなスキーマのプライマリテーブルが作成されます注2。ER図で書くと、こんな感じになります。

CREATE TABLE `user` (
  `_iid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `realname` varchar(255) NOT NULL,
  PRIMARY KEY (`_iid`),
  UNIQUE KEY `_pac_key` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `tweet` (
  `_iid` int(10) unsigned NOT NULL,
  `mtime` int(10) unsigned NOT NULL,
  `body` varchar(255) NOT NULL,
  PRIMARY KEY (`_iid`,`mtime`),
  CONSTRAINT `tweet_ibfk_1` FOREIGN KEY (`_iid`) REFERENCES `user` (`_iid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

リゾルバを起動

 先に make した pacific_resolver を起動します。TCP ポート番号は現在のところ 9306 に固定です。リゾルバが使用するデータベースの情報は、環境変数を用いて渡すことができます注3

$ PACIFIC_MGR_MYSQL_HOST=127.0.01 PACIFIC_MGR_MYSQL_PORT=33060 ./pacific_resolver

Perl クライアントからのアクセス方法

 まず、Pacific のドライバオブジェクトを生成します。

use Pacific::Driver::Direct;
use Pacific::Driver::Direct::DBI::DBD::mysql;
use Pacific::Driver::Direct::Resolver;

my $pac = Pacific::Driver::Direct->new({
    dbi => Pacific::Driver::Direct::DBI::DBD::mysql->new({
        user     => 'root',
        pass     => undef,
        database => 'pacific',
    }),
    resolver => Pacific::Driver::Direct::Resolver->new({
        host => '127.0.0.1',
        port => 9306,
    }),
});

 キーによるテーブルのルックアップは、イテレータを使って行います。Pacific は、渡されたキーがどの RDBMS ノードに属するかリゾルバに問い合わせを行い、そのキーに属するデータにリードロックをかけ、順次イテレータに値を渡してきます。

my @rows;
for (my $iter = $pac->query_iter('user', [ qw/Alice Bob Eve/ ]);
     $iter->next;
     undef) {
    my $r = $iter->dbh->selectall_arrayref(
        'SELECT username,realname FROM user WHERE ' . $iter->key_expr,
        {},
        $iter->key_values,
    ) or die $iter->dbh->errstr;
    push @rows, @$r;
}

 レンジクエリ(範囲を指定した検索)も同様に記述することができます。範囲指定の演算子は、< <= > >= を組み合わせて使うことができます。

# Bob 以降10人を取得
my @rows;
for (my $iter = $pac->query_iter('user', { '>=' => 'Bob' });
     $iter->next;
     undef) {
    my $r = $iter->dbh->selectall_arrayref(
        'SELECT username,realname FROM user WHERE ' . $iter->key_expr
            . 'LIMIT ?',
        {},
        $iter->key_values,
        10 - @rows,
    ) or die $iter->dbh->errstr;
    push @rows, @$r;
    last if @rows >= 10;
}

 書き込みにあたっては、query_iter の代わりに modify_iter を使用します注4

for (my $iter = $pac->modify_iter('user', qw[ /Alice Bob/ ]);
     $iter->next;
     undef) {
    $iter->dbh->do(
        'UPDATE user SET hitpoint=hitpoint+10 WHERE ' . $iter->key_expr,
        {},
        $iter->key_values,
    ) or die $iter->dbh->errstr;
}

 ノード内でトランザクションを組むこともできます (下の例のように、単一のキーにアクセスする場合も、イテレータを使います)。

# tweet テーブルに発言を追加し、user テーブルの最終発言時刻を更新
for (my $iter = $pac->modify_iter(
         'user', [ qw/Alice/ ], { transactional => 1 },
     );
     $iter->next;
     undef) {
    $iter->dbh->do(
        'INSERT INTO tweet (_iid,mtime,body) VALUES'
            . ' ((SELECT _iid FROM user WHERE username=?),?,G)',
        {},
        'Alice', $tweet_at, $tweet,
    ) or die $iter->dbh->errstr;
    $iter->dbh->do(
        'UPDATE user SET last_tweet_at=? WHERE user=?',
        {},
        $tweet_at, 'Alice',
    ) or die $iter->dbh->errstr;
}

 また、より高レベルな ORM っぽいインターフェイスを提供する Pacific::Driver::Direct::Accessor モジュールもありますが、自分は元来 ORM 不要派で経験値が低いので、あまり深入りしたくない (深入りしたところでいいものができないと思ってる) 気持ちです。

 それではひとまずこのあたりで。ノードの分割/再配置に使う prelocate コマンドについても、また書きたいと思います。

注1. Pacific の開発は、Thrift の同ページにある Archived release (r760184) を使って行っています。でも、インストールが面倒なわりに Thrift の Perl クライアントはおそいので、何か別のトランスポートに換えようとと考えています。
注2. ノード内部でリレーションを表現するために _iid という値を別途使用するのは、空間効率を高める一方で、データの分断につながります。ですので、今後、_iid を使わず、セカンダリテーブルにも直接分散キーを書き込むモデルをサポートすることも考えています
注3. 使用可能な環境変数については、MySQLDriver.cpp を参考にしてください
注4. 範囲を指定した更新については、現時点で未対応です

TrackBack

TrackBack URL for this entry:
http://bb.lekumo.jp/t/trackback/404050/20195093

Listed below are links to weblogs that reference Pacific のクライアントAPI (仮) について:

Comments

Post a comment