DBIx::Skinnyを使った際のDBShardingの方法考察

DBIx::SkinnyDBIへの薄いラッパーなので
ネイティブにDBShardingをサポートはしていません。
また、Shardingに限らずSlaveに勝手につないだりしてくれる便利機能もありません。


ただ、ShardingとかSlaveにつないだりはしたくなる事が有ると思うので、
サンプルコードを書いてみました。


サンプルコードはgithubにあります。
http://github.com/nekokak/p5-dbix-skinny-sample
ただ、この記事を書いている時点ではgithubがぶっ壊れてるぽくcloneできません。:(
無料で使わせていただいているので文句は言えませんが。


サンプルコードでは
DBIx::ShardManagerをつかってみました。
http://svn.coderepos.org/share/lang/perl/DBIx-ShardManager/
kazuho++


使ってみたんですが、使い方がよくわからなかったので(調べる時間がなかったので)
DBIx::ShardManager::Definition::JSONだけを使ってみました。


とりあえずコードをはっつけときあす。


サンプルとしては
ユーザテーブルとユーザが一言を書き込むテーブルを用意して、
ユーザを登録したときにAUTOINCREMENTに発行されるIDをベース(range base)に
一言テーブルをshardingするようにしています。

テーブル構成

-- shardingしないユーザテーブル
CREATE TABLE user (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    name       VARCHAR(255) NOT NULL,
    UNIQUE(name)
);
-- 別DBにshardingされる一言テーブル 
CREATE TABLE status (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id    INTEGER NOT NULL,
    body       VARCHAR(255) NOT NULL
);


ユーザテーブルの定義

package Demo::DB;
use DBIx::Skinny;

package Demo::DB::Schema;
use DBIx::Skinny::Schema;

install_table user => schema {
    pk 'id';
    columns qw/id name/;
};

1;


一言テーブルの定義

package Demo::ShardDB;
use DBIx::Skinny;

package Demo::ShardDB::Schema;
use DBIx::Skinny::Schema;

install_table status => schema {
    pk 'id';
    columns qw/id user_id body/;
};

1;


テーブルをshardingするモジュール

package Demo::ShardManager;
use strict;
use warnings;
use DBIx::ShardManager::Definition::JSON;
use Demo::ShardDB;

sub new {
    my $class = shift;
    bless {}, $class;
}

sub handler_for {
    my ($self, $shard_key) = @_;

    $self->{_sm} ||= DBIx::ShardManager::Definition::JSON->new(
        file => './assets/shard_def.json',
    );

    my $info = $self->{_sm}->get_conn_info($shard_key);

    $self->{'_cache'.$info->{dbname}} or do {
        my $db = Demo::ShardDB->new;
        $db->connect({dsn => 'dbi:'.$info->{driver}.':dbname='.$info->{dbname}});
        $self->{'_cache'.$info->{dbname}} = $db;
    };
}

1;


shardingの定義ファイル

{
  "algorithm": "range-int",
  "map" : {
    "0"    : {
      "driver" : "SQLite",
      "dbname" : "./db/demo1.db"
    },
    "6" : {
      "driver" : "SQLite",
      "dbname" : "./db/demo2.db"
    }
  }
}


これだけです。
実際の使い方サンプルは

#! /usr/local/bin/perl
use strict;
use warnings;
use lib qw(./lib);
use Demo::DB;
use Demo::ShardManager;

my $sm = Demo::ShardManager->new;
my $db = Demo::DB->new;
$db->connect({dsn => 'dbi:SQLite:dbname=./db/demo_user.db', });

$db->bulk_insert('user',
    [
        { name => 'nekokak'  },
        { name => 'kazuho'   },
        { name => 'yappo'    },
        { name => 'nekoya'   },
        { name => 'oinume'   },
        { name => 'kan'      },
        { name => 'walf443'  },
        { name => 'yusukebe' },
        { name => 'hirose31' },
        { name => 'boofy'    },
    ]
);

{
    my $user = $db->single('user', {name => 'nekokak'});

    my $shard_db = $sm->handler_for($user->id);
    my $status = $shard_db->insert('status',{ user_id => $user->id, body => 'hi!' });
    print $status->body, "\n";
}

{
    my $user = $db->single('user', {name => 'boofy'});

    my $shard_db = $sm->handler_for($user->id);
    my $status = $shard_db->insert('status',{ user_id => $user->id, body => 'booooooooofy' });
    print $status->body, "\n";
}

{

    my $user = $db->single('user', {name => 'nekokak'});
    my $shard_db = $sm->handler_for($user->id);
    $shard_db->delete('status');
    $user = $db->single('user', {name => 'boofy'});
    $shard_db = $sm->handler_for($user->id);
    $shard_db->delete('status');
    $db->delete('user');
}

こんな感じでuserのidをベースにDBの接続先をわけて
異なるDBに一言を書き込む事ができるようになります。

これは一例なのでまぁこんな感じで接続先を変えられるという事をしっていれば良いと思います。
slaveへの接続などもこんなノリでやればいいんじゃないかとおもっております。

DBIx::Skinnyをどうぞよろしく!