DB Shardingについて2 #17
こんにちわ!秘密主義のnekokakです!
十七日目は昨日のDBIx::SkinnyでのDB Shardingについての続きです。
昨日はミドルウェアでShardingして、アプリケーション側では楽をしようと言うお話でした。
しかし、私はミドルウェア屋さんではないので、ミドルウェアの細かいところまで面倒みれません。
面倒みれないので、ミドルウェアに頼らずに、アプリケーション側で何とかしようと考えてしまいます。
そこで今日は私が今まで試したことのあるDB Shardingの方法を紹介してみたいと思います。
関連記事としては、
http://d.hatena.ne.jp/nekokak/20090917/1253119002
こちらを見ていただけると良いかと思います。
この記事で紹介した方法は、考察であって実際に運用で使ったことはありません。
では本題。
今回紹介するshardingの方法は以前WEB+DBの記事で紹介されたMixiさんでつかわれているというshardingの方法を参考にしたやり方です。
sharding管理用のmaster tableを作成します。
CREATE TABLE db_node ( id int(10) unsigned NOT NULL AUTO_INCREMENT, number int(10) unsigned NOT NULL、 host varchar(20) NOT NULL COMMENT 'DB接続先Host名', role enum('master','slave') NOT NULL COMMENT 'nodeの用途', status enum('ok','down','wait') NOT NULL COMMENT 'nodeの状態', type varchar(20) NOT NULL COMMENT '何に使うnodeなのかex)message', timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY db_node_idx (type,role,status) ) ENGINE=InnoDB COMMENT='DB接続先マスター情報'; CREATE TABLE db_node_map ( id int(10) unsigned NOT NULL AUTO_INCREMENT, db_node_id int(10) unsigned NOT NULL, type_key varchar(30) NOT NULL, timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY db_node_map (db_node_id,type_key), KEY db_node_id (db_node_id) ) ENGINE=InnoDB COMMENT='DB接続先管理テーブル';
db_nodeテーブルには、接続対象とできる、hostの管理をしています。
db_node_mapテーブルでは、db_nodeを任意のキーで割り振るマッピングをしています。
参考レコードとしては
> select id, number, host, role, status, type from db_node; +----+--------+--------------+--------+--------+---------+ | id | number | host | role | status | type | +----+--------+-----------------+-----+--------+---------+ | 1 | 1 | 192.168.1.10 | master | ok | message | | 2 | 2 | 192.168.1.10 | master | ok | message | | 3 | 3 | 192.168.1.10 | master | ok | message | | 4 | 4 | 192.168.1.10 | master | ok | message | | 5 | 5 | 192.168.1.10 | master | ok | message | +----+--------+-----------------+-----+--------+---------+
このようになります。
また、db_node_mapの参考レコードとしては
> select id, db_node_id, type_key from db_node_map; +----+------------+------------+ | id | db_node_id | type_key | +----+------------+------------+ | 1 | 1 | nekokak | | 2 | 2 | yappo | | 3 | 3 | nekoya | | 4 | 4 | walf443 | | 5 | 5 | kan | +----+------------+------------+
このようになります。
db_node_mapは必要に応じて自動でレコードが追加されるようにしますが、
db_nodeは事前にレコードを作成しておく必要があります。
さて、このデータをどのようにDBIx::Skinnyで使うかです。
db_node/db_node_mapのデータを管理利用するmodelを作成します。
このmodelではuserテーブルのnameをキーに使ってどのdb_node/db_node_mapを使うかを決めます。
package Proj::Model::DBShardManager; use strict; use warnings; use Proj::DB; sub new { my $class = shift; bless { # db_node/db_node_mapの入っているdatabaseに接続する db => Proj::DB->new(....), }, $class; } # db_node_mapに$user_nameに対応するレコードを登録する sub setup_handler_setting { my ($self, $user_name) = @_; die "user_name is not defined!" unless $user_name; # db_node_mapにすでに登録されている場合はスルー return if $self->{db}->single('db_node_map',{type_key => $user_name}); # 使える全masterノード取得 my @db_node_ids = map { $_->id } $self->{db}->search_by_sql( q{ SELECT id FROM db_node WHERE type = 'message' AND role = 'master' AND status = 'ok' }, [], 'db_node' ); die 'undefined db_node!!!' unless @db_node_ids; # 既に使用されているノードを取得 my %list = map { $_->db_node_id => $_->count } $self->{db}->search_by_sql( q{ SELECT db_node_map.db_node_id, count(db_node_map.db_node_id) AS count FROM db_node, db_node_map WHERE db_node.id = db_node_map.db_node_id AND db_node.type = 'message' GROUP BY db_node_map.db_node_id ORDER BY count ASC }, [], 'db_node_map' ); my $use_db_node = +{}; for my $db_node_id (@db_node_ids) { next if defined $use_db_node->{count} && ($use_db_node->{count} <= ($list{$db_node_id}||0)); $use_db_node = +{ count => $list{$db_node_id}||0, db_node_id => $db_node_id, }; } $self->{db}->insert('db_node_map', { db_node_id => $use_db_node->{db_node_id}, type_key => $user_name, } ); } my $datasource = +{ dsn => '', username => 'user', password => 'password', }; sub handler_for { my ($self, $user_name) = @_; die "user_name is not defined!" unless $user_name; my $row = $self->{db}->search_by_sql( q{ SELECT db_node.id, db_node.number, db_node.host, db_node.type FROM db_node, db_node_map WHERE db_node.id = db_node_map.db_node_id AND db_node.type = 'message' AND db_node.role = 'master' AND db_node_map.type_key = ? LIMIT 1 }, [$user_name], 'db_node' )->first; if ($row) { $datasource->{dsn} = $row->gen_dsn; } else { $datasource->{dsn} = $self->setup_handler_setting($user_name)->gen_dsn; } my $db = Proj::DB->new($datasource); # 必要であればset namesを打つ $db->do(q{SET NAMES utf8}); $db; }
このProj::Model::DBShardManagerをつかうことで、
dbの接続先を変えたDBIx::Skinnyのインスタンスを取得することができるようになります。
use strict; use warnings; use Proj::Model::DBShardManager; my $sm = Proj::Model::DBShardManager->new; # user_name: nekokakをキーにしたDBIx::Skinnyのインスタンスを取得 my $db = $sm->handler_for('nekokak'); # DBIx::Skinnyのインスタンス取得後は普通のDBIx::Skinnyの操作を行うのみ
このようになります。
Proj::Model::DBShardManagerをもう少し工夫すれば、
slaveへの接続をとりだしたりすることもできますね。
駆け足の説明なので、わかりにくいこともあるかと思いますが
わからないことはどんどんircで質問してみてください。
他にもshardingの方法はあるので、また別の機会にでも。
というかこの方法は特別DBIx::Skinnyに特化しているわけではないので
何かの参考にしていただければと思います。
今日はここまで。
have a nice skinny days!:)