Gizzard

http://www.publickey1.jp/blog/10/twittergizzard_scalasharding.html

Gizzardてのはtwitterで使われている技術でシャーディングを行うものだそうです。
シャーディングってのはパーティショニングとレプリケーションを使ってDBを分散して管理できるようにするものらしく
githubで公開されているっと。
でも、使ってみないとよくわかりません。

ということでビルドするにも、git使ってant使ってivyだの、thriftだのいろいろ最新すぎる。
で、途中までid:nigakyさんやってくれたみたいなのでリンク。

http://d.hatena.ne.jp/nigaky/20100411

id:nigakyさん様様です。
ありがとうございます。

自分もthriftがってところでビルドできず止まってたので。

ただ、gitにあるfacebookのthriftをダウンロードした
でもapacheのthriftを使うのがいいみたいなので
apache thriftでググるapache版のthriftが見つかりました。

http://incubator.apache.org/thrift/download/


Thrift(スリフト)は、「スケーラブルな言語間サービス開発」のためにFacebookにて開発されたRPCフレームワークオープンソース化されて
Apacheプロジェクトで開発されているっぽい。

http://codezine.jp/article/detail/4781

Apache Thriftチームは2009年12月11日、オープンソースのRPCフレームワーク「Apache Thrift 0.2.0」をリリースした。

 Apache Thriftは異なるプログラミング言語をシームレスに接続するサービスを構築するフレームワークで、C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、Smalltalk、OCamlの各プログラミング言語をサポートする。

 Thriftプロジェクトは2007年4月にFacebookでスタートし、2008年5月からApacheのインキュベーションとして開発されている。2009年8月に最初のバージョン0.1.0がリリースされていた。国内では、はてなブックマークがプリファードインストラクチャー製レコメンドエンジンとの連携にThriftを利用している。

ということで、けっこう前から開発されてるんですね。
こちらを使えばよいのだと思います。

サンプルアプリケーションの Rowz (http://github.com/nkallen/Rowz)のビルドはこんなかんじで出来るらしいです。

To build project:

ant dist -DDB_USERNAME=fixme -DDB_PASSWORD=fixmetoo

To run tests:

mysql> CREATE DATABASE rowz_nameserver;

% ant test -DDB_USERNAME=fixme -DDB_PASSWORD=fixmetoo

To run a simple development server:

% ./bin/rowz

To sample the networking service:

% cd src/scripts
% ./gzr.rb

rowz_nameserverDBを作ってant testでそこにつないで
./bin/rowzでシンプル開発サーバを実行して
gzr.rbでネットワークサービスサンプルを試せるらしい。
なにが出来るんじゃ?ってところが問題なのでソースを読むしかない。

まず、./bin/rowzのソース

http://github.com/nkallen/Rowz/blob/master/bin/rowz

git clone git://github.com/nkallen/Rowz.git

でダウンロード

shだ。

これは実験に適した、フォアグランドにおけるRowzサーバを実行します。
使い方: rows MYSQL_USER [MYSQL_PASSWORD]

で、最初にmysqlのチェックをしたあと以下のjavaプログラムを実行するものらしい

java -DDB_USERNAME="$1" -DDB_PASSWORD="$2" -jar dist/rowz/rowz-1.0.jar -f config/development.conf

rowz-1.0.jarを実行するってことか。。。

たぶん、com.twitter.rowz.Mainが起動される。
com.twitter.rowz.Main

で、startThriftでrowzServer,jobServer,shardServerを実行するようだ。

おそらく、jobsディレクトリがjobServerの内容
thriftディレクトリがthrift周り


ということで、、、<どーいうことだ

gizzardのライブラリを使ってscalaでシャーディングの設定をゴリゴリ書いてサービスとして実行する。
クライアントからはthriftを使ってリモート呼び出しがかかる。

で、それを使うクライアントはgzr.rbでthriftを使ってリモート呼び出しでサービスを利用するっと。
gzr.rbは設定が長々続いて最後に簡単な例を入れておしまい。

とりあえず、コメントを入れただけのソースgzr.rbをここに張ります。

#!/usr/bin/env ruby

# drop database rowz_nameserver; create database rowz_nameserver;

$:.push(File.dirname($0))
require 'socket'
require 'simple_thrift'

# localhostのrowzポートは7919 shard_portは7920
HOST = "localhost"
ROWZ_PORT = 7919
SHARD_PORT = 7920

# 設定
SHARD_CLASS = {
  :read_only => "ReadOnlyShard",   # 読み込みオンリーシャード
  :blocked => "BlockedShard",      # ブロックドシャード
  :write_only => "WriteOnlyShard", # 書き込みオンリーシャード
  :replica => "ReplicatingShard"   # レプリケーションシャード
}
# シャード名作成
SHARD_NAME = Hash[*SHARD_CLASS.map { |a, b| [ b, a ] }.flatten]

# Row の構造を作成
Row = SimpleThrift.make_struct(:Row,
                               SimpleThrift::Field.new(:id, SimpleThrift::I64, 1),
                               SimpleThrift::Field.new(:name, SimpleThrift::STRING, 2),
                               SimpleThrift::Field.new(:created_at, SimpleThrift::I32, 3),
                               SimpleThrift::Field.new(:updated_at, SimpleThrift::I32, 4),
                               SimpleThrift::Field.new(:state, SimpleThrift::I32, 5))

# シャード情報構造作成
ShardInfo = SimpleThrift.make_struct(:ShardInfo,
                                     SimpleThrift::Field.new(:class_name, SimpleThrift::STRING, 1),
                                     SimpleThrift::Field.new(:table_prefix, SimpleThrift::STRING, 2),
                                     SimpleThrift::Field.new(:hostname, SimpleThrift::STRING, 3),
                                     SimpleThrift::Field.new(:source_type, SimpleThrift::STRING, 4),
                                     SimpleThrift::Field.new(:destination_type, SimpleThrift::STRING, 5),
                                     SimpleThrift::Field.new(:busy, SimpleThrift::I32, 6),
                                     SimpleThrift::Field.new(:shard_id, SimpleThrift::I32, 7))

# シャード子構造作成
ShardChild = SimpleThrift.make_struct(:ShardChild,
                                      SimpleThrift::Field.new(:shard_id, SimpleThrift::I32, 1),
                                      SimpleThrift::Field.new(:weight, SimpleThrift::I32, 2))

# フォワーディング構造作成
Forwarding = SimpleThrift.make_struct(:Forwarding,
                                      SimpleThrift::Field.new(:table_id, SimpleThrift::ListType.new(SimpleThrift::I32), 1),
                                      SimpleThrift::Field.new(:base_id, SimpleThrift::I64, 2),
                                      SimpleThrift::Field.new(:shard_id, SimpleThrift::I32, 3))

# シャードマイグレーション構造作成
ShardMigration = SimpleThrift.make_struct(:ShardMigration,
                                          SimpleThrift::Field.new(:source_shard_id, SimpleThrift::I32, 1),
                                          SimpleThrift::Field.new(:destination_shard_id, SimpleThrift::I32, 2),
                                          SimpleThrift::Field.new(:replicating_shard_id, SimpleThrift::I32, 3),
                                          SimpleThrift::Field.new(:write_only_shard_id, SimpleThrift::I32, 4))

# シャードマネージャーをサービス継承して作成
class ShardManager < SimpleThrift::ThriftService
  thrift_method :create_shard, i32, field(:shard, struct(ShardInfo), 1)
  thrift_method :find_shard, i32, field(:shard, struct(ShardInfo), 1)
  thrift_method :get_shard, struct(ShardInfo), field(:shard_id, i32, 1)
  thrift_method :update_shard, void, field(:shard, struct(ShardInfo), 1)
  thrift_method :delete_shard, void, field(:shard_id, i32, 1)

  thrift_method :add_child_shard, void, field(:parent_shard_id, i32, 1), field(:child_shard_id, i32, 2), field(:weight, i32, 3)
  thrift_method :remove_child_shard, void, field(:parent_shard_id, i32, 1), field(:child_shard_id, i32, 2)
  thrift_method :replace_child_shard, void, field(:old_child_shard_id, i32, 1), field(:new_child_shard_id, i32, 2)
  thrift_method :list_shard_children, list(struct(ShardChild)), field(:shard_id, i32, 1)

  thrift_method :mark_shard_busy, void, field(:shard_id, i32, 1), field(:busy, i32, 2)
  thrift_method :copy_shard, void, field(:source_shard_id, i32, 1), field(:destination_shard_id, i32, 2)
  thrift_method :setup_migration, struct(ShardMigration), field(:source_shard_info, struct(ShardInfo), 1), field(:destination_shard_info, struct(ShardInfo), 2)
  thrift_method :migrate_shard, void, field(:migration, struct(ShardMigration), 1)

  thrift_method :set_forwarding, void, field(:forwarding, struct(Forwarding), 1)
  thrift_method :replace_forwarding, void, field(:old_shard_id, i32, 1), field(:new_shard_id, i32, 2)
  thrift_method :get_forwarding, struct(ShardInfo), field(:table_id, list(i32), 1), field(:base_id, i64, 2)
  thrift_method :get_forwarding_for_shard, struct(Forwarding), field(:shard_id, i32, 1)
  thrift_method :get_forwardings, list(struct(Forwarding))
  thrift_method :reload_forwardings, void
  thrift_method :find_current_forwarding, struct(ShardInfo), field(:table_id, list(i32), 1), field(:id, i64, 2)

  thrift_method :shard_ids_for_hostname, list(i32), field(:hostname, string, 1), field(:class_name, string, 2)
  thrift_method :shards_for_hostname, list(struct(ShardInfo)), field(:hostname, string, 1), field(:class_name, string, 2)
  thrift_method :get_busy_shards, list(struct(ShardInfo))
  thrift_method :get_parent_shard, struct(ShardInfo), field(:shard_id, i32, 1)
  thrift_method :get_root_shard, struct(ShardInfo), field(:shard_id, i32, 1)
  thrift_method :get_child_shards_of_class, list(struct(ShardInfo)), field(:parent_shard_id, i32, 1), field(:class_name, string, 2)

  thrift_method :rebuild_schema, void
end

# ジョブマネージャーをサービス継承して作成
class JobManager < SimpleThrift::ThriftService
  thrift_method :retry_errors, void
  thrift_method :stop_writes, void
  thrift_method :resume_writes, void
  thrift_method :retry_errors_for, void, field(:priority, i32, 1)
  thrift_method :stop_writes_for, void, field(:priority, i32, 1)
  thrift_method :resume_writes_for, void, field(:priority, i32, 1)
  thrift_method :is_writing, bool, field(:priority, i32, 1)
  thrift_method :inject_job, void, field(:priority, i32, 1), field(:job, string, 2)
end

# Rowzをサービス継承して作成
class Rowz < SimpleThrift::ThriftService
  thrift_method :create, i64, field(:name, string, 1), field(:at, i32, 2)
  thrift_method :read, struct(Row), field(:id, i64, 1)
  thrift_method :destroy, void, field(:id, i64, 1)
end

# シャードマネージャーを作成
$service = ShardManager.new(TCPSocket.new(HOST, SHARD_PORT))
# Rowsを作成
$rowz = Rowz.new(TCPSocket.new(HOST, ROWZ_PORT))
# シャードマネージャのスキーマを再ビルド
$service.rebuild_schema

#パーティションは10個
partitions = 10
#キースペースは64bitくらい
keyspace = 2**64

#パーティション分ループして
partitions.times do |i|
  # data_aのシャード情報を作成
  shard_info_a = ShardInfo.new("com.twitter.rowz.SqlShard", "data_a" + i.to_s, HOST, "", "", 0, 0)
  # data_aのシャードを作成
  shard_id_a = $service.create_shard(shard_info_a)

  # data_bのシャード情報を作成
  shard_info_b = ShardInfo.new("com.twitter.rowz.SqlShard", "data_b" + i.to_s, HOST, "", "", 0, 0)
  # data_bのシャードを作成
  shard_id_b = $service.create_shard(shard_info_b)

  # レプリケーティングのシャード情報を作成
  replicating_shard_info = ShardInfo.new("com.twitter.gizzard.shards.ReplicatingShard", "replicating_" + i.to_s, HOST, "", "", 0, 0)
  # レプリケーティングのシャードを作成
  replicating_shard_id = $service.create_shard(replicating_shard_info)

  # レプリケーティングシャードの子にdata_aシャードを追加
  $service.add_child_shard(replicating_shard_id, shard_id_a, 1)
  # レプリケーティングシャードの子にdata_bシャードを追加
  $service.add_child_shard(replicating_shard_id, shard_id_b, 1)

  # キースペースをパーティションで分割してそのオフセットを取得
  lower_bound = keyspace / partitions * i
  # フォワーディングを設定
  $service.set_forwarding(Forwarding.new([], lower_bound, replicating_shard_id))
end

# フォワーディングをリロード
$service.reload_forwardings

# 10回ループ

10.times do |i|

  # rowを作成
  id = $rowz.create("row #{i}", Time.now.to_i)
  # id出力
  p id
  # 0.1秒待ち
  sleep 0.1
  # idをのrowzを読み込み出力
  p $rowz.read(id)
end

一番最後はrowzサービスの使い方だけど、その手前が毎回行うものなのか、1回行えばあとはやらなくてもいいものなのか?
使ってみないとよく分からないところだと思います。