DbCharmer commit:b02576644d982f895365e796ba75b825f7104210 時点の README.rdoc のざっくり読み下し
"DbCharmer" は、シンプルでパワフルな ActiveRecord のプラグインです。ActiveRecord が複数のデータベースや複数のデータベースサーバで動作できるよう拡張します。
このライブラリが ActiveRecord に追加する主な機能は以下のとおり:
- AR model のコネクションをシンプルに管理 (switch_connection_to メソッド)
- AR model をデータベースを分割したコネクションに
- クエリをどこにむかわせるか簡単に選べる機能 (
Model.on_*
メソッド群) - 自動でマスタ・スレーブにクエリをむかわせる (参照はスレーブに、更新はマスタで)
- 複数のデータベースのマイグレーションをフレキシブルに
- 複数のシャーディング方法でシンプルなシャーディング機能を利用できる (value, range, mapping table)
詳細は、 http://dbcharmer.net を見てください。
導入
DbCharmer の導入には2つのアプローチがあります:
gem として導入するなら、Gemfile に以下を追加してください:
gem 'db-charmer', :require => 'db_charmer'
Rails のプラグインとして導入するなら以下のコマンドで:
./script/plugin install git://github.com/kovyrin/db-charmer.git
注意: DbCharmer を Rails じゃないプロジェクトで使うなら、コネクションを管理するメソッドを使う前に、DbCharmer.env
に適切な値を設定する必要があります。適切な値は、database.yml
のトップレベルのセクション名です。
簡単な ActiveRecord のコネクションの管理
このプラグインで追加される機能として switch_connection_to
というメソッドがありますが、これはいろんな種類の DB コネクションの指定を受け付け、モデルでその指定を使います。以下をサポートしています:
- 文字列とシンボル: database.yml にある接続設定のブロックを指定
- ActiveRecord のモデル (モデルに対してコネクションを設定する)
- デーテベースのコネクション (
Model.connection
) - nil: デフォルトのコネクションにリセットする
サンプルコード:
class Foo < ActiveRecord::Model; end Foo.switch_connection_to(:blah) Foo.switch_connection_to('foo') Foo.switch_connection_to(Bar) Foo.switch_connection_to(Baz.connection) Foo.switch_connection_to(nil)
サンプルの database.yml
の設定:
production: blah: adapter: mysql username: blah host: blah.local database: blah foo: adapter: mysql username: foo host: foo.local database: foo
switch_connection_to
メソッドは第二引数にオプション引数として should_exist = true
な引数をとります。このオプションは文字列またはシンボルを引数にメソッドが呼ばれたとき、そのコネクションの設定が database.yml
にあるかどうか見ます。この should_exist が true ならば、例外をあげるし、false ならばエラーは無視され、コネクションの変更は起きません。
これは、development モードやテストのときに、複数のデータベースをローカルに作りたくない、ひとつのデータベースに全部テーブルを作りたい、とかいうときにつかえます。
警告: 全てのコネクションのスイッチの呼び出しは、呼んだクラスだけでコネクションをスイッチします。switch_connection_to
の呼び出しと、コネクションのスイッチを継承元のクラスでやってはいけません (たとえば ActiveRecord::Base クラスのコネクションをスイッチすると、全てのモデルが新しいコネクションになってしまいます。そういうことがしたいときには、普通に establish_connection
を使いましょう)。
複数データベースのマイグレーション
複数のデータベースを使うアプリケーションでも、便利なスキーママイグレーションの仕組みが必要でしょう。
Rails ユーザのみなさんは既に Rails の migration という仕組みを持っています。DbCharmer でも、Rails の migration で可能なかぎりシームレスに複数データベースを扱えるようにしています。
複数のデータベースを操作するのに、以下の2つの方法が使えます:
- マイグレーションファイル全体でコネクションを変更する方法: マイグレーション全体を指定のデータベースにスイッチする
- ブロックでコネクションを変更する方法: マイグレーションの一部を指定のデータベースにスイッチする
マイグレーションファイルの例(全体でコネクションを変更する場合):
class MultiDbTest < ActiveRecord::Migration db_magic :connection => :second_db def self.up create_table :test_table, :force => true do |t| t.string :test_string t.timestamps end end def self.down drop_table :test_table end end
マイグレーションファイルの例(ブロックでコネクションを変更する場合):
class MultiDbTest < ActiveRecord::Migration def self.up on_db :second_db do create_table :test_table, :force => true do |t| t.string :test_string t.timestamps end end end def self.down on_db :second_db { drop_table :test_table } end end
マイグレーションファイルの例(全体でコネクションを変更し、同じテーブルの操作を複数コネクションに):
(注: :connection と :connections は、コネクションの配列を指定することができる)
class MultiDbTest < ActiveRecord::Migration db_magic :connections => [:second_db, :default] def self.up create_table :test_table, :force => true do |t| t.string :test_string t.timestamps end end def self.down drop_table :test_table end end
デフォルトのマイグレーションのコネクションについて
DbCharmer 1.6.10 以降では、ActiveRecord::Migration.db_magic
の呼び出しと、マイグレーションでのデフォルトのコネクションの設定(特定のコネクションの変更しない限りこれが使われる)ができるようになりました。マイグレーションでデフォルトの ActiveRecord のコネクションが使いたい場合は db_magic :connection => :default
を使ってください。
不正なコネクション名の扱いについて
どの環境でもデフォルトでは、on_db
と db_magic
の宣言は指定したコネクションが database.yml に存在しない場合失敗します。production 環境以外でひとつのデータベースを使うなどしているときに、こうなるのを無視させることができます(テストコード用のデータベースとかでとくに便利です)。
この振る舞いは、Rails の initializers で DbCharmer.connections_should_exist
の設定をすることでで制御できます。
警告: もしテスト環境でデータベースのコネクションを分けてマスタ・スレーブのサポートをしたいなら、transactional fixtures のサポートをオフにする必要があります。そうしないと、データを使ったテストでいろいろ問題に遭遇することになるでしょう。
モデルをマスタ・スレーブ環境で使う
マスタ・スレーブレプリケーションは、今日の中規模から大規模なデータベースアプリケーションでの、もっともポピュラーなスケールアウト手法です。いくつかの Rails プラグインが、モデルでスレーブサーバを使えるようにしていますが、それらは大きなアプリケーションで使った感じではあまりフレキシブルではありません。
ActsAsReadonlyable プラグインをしばらく使ってきましたが、たくさんの変更を加えながら使っていました。しかし、このプラグインが作者に放置されたので、我々はマスタ・スレーブのためのコードをプラグインにまとめリリースすることに決めました(Rails 2.2以降向け)。DbCharmer は、以下の機能を Rails のモデルに追加します:
全ての参照をスレーブ(たち)に自動スイッチ
モデルを作ったら、db_magic :slave => :blah
か db_magic :slaves => [ :foo, :bar ]
のコマンドをモデルで使うことができます。そうすることで、find, count, exist などの参照系の操作をスレーブ(または複数のスレーブなら、それらをラウンドロビン)に切り替えるモードになります。例は以下のとおり:
class Foo < ActiveRecord::Base db_magic :slave => :slave01 end class Bar < ActiveRecord::Base db_magic :slaves => [ :slave01, :slave02 ] end
デフォルトコネクション切り替え
もし、一組のマスタ・スレーブ(あるいは単純にひとつより多いデータベース)がある環境ならば、いくつかのモデルでデフォルトのコネクションを変更したいかもしれません。db_magic :connection => :foo
をモデルに書くことでそうすることができます。例:
class Foo < ActiveRecord::Base db_magic :connection => :foo end
マスタ・スレーブ構成 (つまりメインのコネクション + スレーブのコネクション) で分割されているモデルの例:
class Bar < ActiveRecord::Base db_magic :connection => :bar, :slave => :bar_slave end
クエリごとのコネクション管理
マスタで select のクエリを走らせたいというケースがあると思います。たとえば、ちょうどデータを追加して、それを読み出す必要があって、でもそれがスレーブに反映されたかどうかわからない、というようなときです。こういうようなときのために、設定する方法を ActiveRecord モデルに追加しています:
1) on_master
- これは block スタイルと proxy スタイルの2種類の書き方があります。
block スタイルでは、コードブロックのコネクションを強制的にスイッチさせることができます:
User.on_master do user = User.find_by_login('foo') user.update_attributes!(:activated => true) end
proxy スタイルは、あるクエリをマスタに向けることができます:
Comment.on_master.last(:limit => 5) User.on_master.find_by_activation_code(code) User.on_master.exists?(:login => login, :password => password)
2) on_slave
- これはマスタを強制的に使うようにしたあとなどに、強制的にスレーブに使うようにさせるメソッドです。スレーブが複数あれば、ランダムでひとつが選ばれます。このメソッドも block と proxy のスタイルがあります。
3) on_db(connection)
- これは前出の2つのメソッドを可能にするものです。これはモデルのコネクションを、コードのブロックや1つの文で、ある DB に切り替えるのに使います(2つの形式があります)。これは switch_connection_to
と同じ類の値を受け付けます。例:
Comment.on_db(:olap).count Post.on_db(:foo).find(:first)
development と test 環境のデフォルトでは、存在しないコネクションを on_db
の呼び出しを使うと、クエリはひとつのデータベースに全てのクエリを送ります。production の on_db
では、存在しない名前は受け付けません。
この振る舞いは、 DbCharmer.connections_should_exist
を Rails の初期化で設定することによって制御できます。
強制的なスレーブ参照
いくつかの場面で、デフォルトで、全ての参照がスレーブにむかうモードが使われるには重大すぎるモデルがありますが、それらをスレーブにむかわせるモードに切り替えたいと思うことがたまにあります。
たとえば、User
モデルがあるとします。ユーザは古いアカウント情報を見たくないので、スレーブ参照での遅延は避けたいです。しかしあるケースでは(たとえば、ログアウト状態でのプロフィールページの表示、などなど)、全ての参照をスレーブに向けるよう切り替えるというのは合理的です。
このユースケースのために、DbCharmer の 1.7.0 から、強制的にスレーブを参照する機能が追加されました。これは、いくつかの小さな別々の機能から成りますが、組み合わせによりパワフルなものになっています:
1) ActiveRecord の db_magic
メソッドの :force_slave_reads => false
オプション。
このオプションはモデルで自動でスレーブ参照するのを無効にします。なので、on_slave
あるいは、その他のスレーブ参照できるようにするメソッドを必要に応じて呼ぶ必要があります。例:
class User < ActiveRecord::Base db_magic :slave => slave01, :force_slave_reads => false end
2) ActionController の force_slave_reads
クラスメソッド。
このメソッドは、コントローラごと(引数なしで呼ばれたとき)、あるいは、アクションごと (:only
と :except
パラメータを渡して呼んだとき) 、強制的にスレーブを参照します。
これは、スレーブ参照によるラグがいくらか許容できるアクションで、スレーブが設定されているモデルの参照先をスレーブに向わせる、というようなときに使えます。例:
class ProfilesController < Application force_slave_reads :except => [ :login, :logout ] ... end
3) ActionController の force_slave_reads! というインスタンスメソッド、このメソッドで、アクション内、あるいは、コントローラのフィルタで、一時的に参照先を強制的にスレーブに向かわせられます。
このメソッドは同じアクションをログインしているユーザとログインしていないユーザに呼ばれるときに使えます。before_filter
でユーザ認証して、認証していないユーザ向けには force_slave_reads!
メソッドを呼びます。
class ProfilesController < Application before_filter do force_slave_reads! unless current_user end ... end
注意: このメソッドを使う前に、DbCharmer の ActionController サポートを有効にする必要があります。
DbCharmer.enable_controller_magic!
を、プロジェクトの初期化コードで呼ぶ必要があります。
4) DbCharmer.force_slave_reads
メソッドは、ブロック内のコードで強制的にスレーブ参照をするのに使われます。これは、とても細かい粒度でのスレーブ参照を強制する機能です。例:
DbCharmer.force_slave_reads do ... total_users = User.count ... end
注意: 今のところ、この機能はベータで、使う場合は注意が必要です。テストしているものの、現実世界のアプリケーションでは想定外の問題が起こる可能性があります。
関連のコネクションの管理
ActiveRecord モデルはそれぞれ互いに関連を持っていて、それぞれがデータベースのコネクションを持っているため、User.posts.count
のようなメソッドチェーンなんかで、コネクションの管理をするのが少し難しいです。 posts の count を別のデータベースでやりたいならば、あるひとつのクラスだけコネクションを変更して、以下の様にメソッドを呼びます:
Post.on_db(:olap) { User.posts.count }
これはあまり良い書き方ではないようなので、関連をうまく扱えるように on_*
メソッド群を準備しました。こんなかんじで使えます:
@user.posts.on_db(:olap).count @user.posts.on_slave.find(:title => 'Hello, world!')
注意: ActiveRecord の関連は、結果のオブジェクトあるいはコレクションのプロキシとして実装されているため、チェインしたメソッド以外もコネクションの切り替えを使えます:
@post.user.on_slave # post の author を返す @photo.owner.on_slave # photo の owner を返す
DbCharmer の 1.4 以降では、has_many と HABTM 関連のコネクション切り替えで prefix 記法を使えます:
@user.on_db(:foo).posts @user.on_slave.posts
named scope のサポート
DbCharmer ユーザの named scope でのコネクションの切り替えを簡単にするため、スコープでも on_*
メソッドをサポートするようにしました。
以下のスコープチェーンは全部やってることは同じです(クエリは :foo データベースコネクションで実行されます):
Post.on_db(:foo).published.with_comments.spam_marked.count Post.published.on_db(:foo).with_comments.spam_marked.count Post.published.with_comments.on_db(:foo).spam_marked.count Post.published.with_comments.spam_marked.on_db(:foo).count
関連のサポートもしているので、以下のようにも使えます:
@user.on_db(:archive).posts.published.all @user.posts.on_db(:olap).published.count @user.posts.published.on_db(:foo).first
一括したコネクションの管理
たくさんのテーブルを使ってコードを書きたい、そして、それらを別のデータベースを使うようにしたいというときがあります。
こんなふうにできます:
DbCharmer.with_remapped_databases(:logs => :big_logs_slave) { ... }
デフォルトが :logs
のどのモデル (たとえば db_charmer :connection => :logs
などと書いているモデル)も、このブロック内ではコネクションを :big_logs_slave
に変えます。
これは、他のどの DbCharmer のメソッドより優先度が低いです。なので、Model.on_db(:foo).find(...)
などは、指定されたデータベースを使いつづけ、そうでないものはリマップされます。
一度に、いくつでもリマップの指定をできます。また、DbCharmer でコネクションを指定していないどのモデルにマッチするデータベース名として :master
を使えます。
ノート: DbCharmer は モデルで alias_method_chain
を使って動いています。注意して、必要な箇所にだけパッチしています。
しかし、with_remapped_databases
とデフォルトのデータベース(:master
) のリマップを使いたいなら ActiveRecord::Base
の全てのサブクラスにパッチするしかありません。
これはひどい問題も、大きなパフォーマンスの影響もおこさないはずです(よくできてる)。
シンプルなシャーディングのサポート
DbCharmer の 1.6.0 から、ActiveRecord の拡張としてシンプルなデータベースシャーディングをサポートしています。
この機能は本番環境でもテストされているとはいえ、この機能の基本をちゃんと理解せずにアプリケーションで使うのはオススメしません。
現時点では4つのシャーディング方法をサポートしています:
1) "range" - とてもシンプルなシャーディング方法で、事前に定義したプライマリキーの範囲でテーブル分割を行い、その小さく分割したテーブルを別のデータベースやデータベースサーバに配置することができます。
こんなとき便利: 非常に大きいテーブルがだんだん大きくなっていくようなとき。テーブルをいくつかのサーバにのせることで複雑なシャーディングの仕組みなしでシンプルさを保ちたいとき。
2) "hash_map" - けっこうシンプルなシャーディング方法で、事前に定義しておいたいくつかのキーでテーブル分割を行います(プライマリキーでなくてよい)。
たとえば、どの州をどのデータベース(あるいはデータベースサーバ)に保存するか定義しておいてアメリカの住所のリストを州ごとにシャーディングするとかできます。
3) "db_block_map" - これはとても複雑なシャーディング方法で、テーブルを小さなブロックのセットに分けて、さらに、シャーディングしたデータベースなりデータベースサーバなりにそれを割り当てます。
ブロックを追加したいときはいつでも、ブロックは自動かつシャーディング間でバランスをとってデータベースに割り当てられます。
この方法は、1000万から10億レコードの巨大なテーブルをスケールするのに使え、比較的簡単にシャーディングしなおせる一番よい方法です。
4) "db_block_group_map" - これは "db_block_map" とよく似た方法で、一点だけが異なります。それは、この方法は
データベースのセット(テーブルのグループ)を各サーバに持てて、各グループがシャーディングとして扱われるところです。
このアプローチはアプリケーションをスケールするまえに事前にシャーディングしておくのに使えます。
ひとつのサーバから簡単にはじめられます。10, 20, 50 に分割したデータベースを持っておき、ひとつのマシンで対応できなくなったら、それらのデータベースを別のサーバに移します。
どうやってシャーディングしたらよいか?
シャーディングの拡張を有効にするには、すこし準備が必要です:
1) シャーディングのコネクションを定義した Rails のイニシャライザを作る (スクリプトやアプリケーションの初期化のときのコード)。それぞれのコネクションに名前と、シャーディング方法と、初期化のためのそのオプションを指定します。
2) シャーディングを使いたいモデルにシャーディングのコネクションを明示する
3) シャーディングしたいモデルを操作する前にシャーディングを明示する
より詳細は、以下のドキュメントをご覧ください。
シャーディング(分割)コネクション
シャーディングコネクションはシンプルな抽象化で、クラスタのためのシャーディングパラメータを一箇所で指定し、このひとところで設定したものをモデルで使えるようにすることができます。
いくつかのシャーディングのコネクションの初期化の例は以下のとおりです:
1) range ベースのシャーディングコネクションの例:
TEXTS_SHARDING_RANGES = { 0...100 => :shard1, 100..200 => :shard2, :default => :shard3 } DbCharmer::Sharding.register_connection( :name => :texts, :method => :range, :ranges => TEXTS_SHARDING_RANGES )
2) ハッシュマップのシャーディングコネクションの例:
SHARDING_MAP = { 'US' => :us_users, 'CA' => :ca_users, :default => :other_users } DbCharmer::Sharding.register_connection( :name => :users, :method => :hash_map, :map => SHARDING_MAP )
3) データベースブロックマップのシャーディングコネクションの例:
DbCharmer::Sharding.register_connection( :name => :social, :method => :db_block_map, :block_size => 10000, # ブロックごとのキーの数 :map_table => :event_shards_map, # シャーディングブロックをマップするテーブル :shards_table => :event_shards_info, # シャーディングコネクション情報のテーブル :connection => :social_shard_info # マップでどのコネクションを使うか )
シャーディングコネクションの定義をしたら、モデルでそれを使えるようになります:
class Text < ActiveRecord::Base db_magic :sharded => { :key => :id, :sharded_connection => :texts } end class Event < ActiveRecord::Base set_table_name :timeline_events db_magic :sharded => { :key => :to_uid, :sharded_connection => :social } end
シャーディングしたモデルでのコネクションの切り替えについて
シャーディングしたモデルを操作するとき、必要があればいつでも、どの分割先を使うか指定することができます。
シャーディングしていない環境での DbCharmer のつかいかたと似た方法で、クエリごとに接続を管理できるメソッドを用意しています:
Event.shard_for(10).find(:conditions => { :to_uid => 123 }, :limit => 5) Text.shard_for(123).find_by_id(123)
その他に、range と hash_map のシャーディングの方法で、デフォルトの分割先に切り替えることができるメソッドがあります:
Text.on_default_shard.create(:body => 'hello', :user_id => 123)
そして最後に、システム内の各シャーディングで、コードを実行するメソッドがあります(現在のところ、db_block_map と db_block_group_map でのみサポートされています):
Event.on_each_shard { |event| event.delete_all }
自分のシャーディング方法の定義
DbCharmer は、ユーザ定義のシャーディング方法が可能です。自分のシャーディング案を実装するには、いくつかやることがあります:
1) DbCharmer::Sharding::Method::YourOwnName
という名前をつけてクラスをつくる
2) 最低でも、コンストラクタ initialize(config)
と、判別するためのインスタンスメソッド shard_for_key(key)
、これは database.yml
ファイルか、rails connection アダプタのコネクションパラメータのハッシュからコネクション名を返すようなメソッドになるでしょう。
3) 以下のようにして、あなたのシャーディングコネクションを登録します:
DbCharmer::Sharding.register_connection( :name => :some_name, :method => :your_own_name, # あなたの定義したシャーディング方法を小文字にしたシンボルで渡します ... ほかにパラメータが必要ならそれも ... )
4) 他の標準のシャーディングコネクションと同様に、あなたの定義したシャーディングコネクションを使います
自分で定義したシャーディングで、デフォルトの分割先をサポートする方法
もし、on_default_shard
メソッドを自分の定義したシャーディングモデルで使いたいなら、2つやることがあります:
1) support_default_shard?
というインスタンスメソッドをシャーディングクラスに実装します。これは、デフォルト分割先の指定をサポートするなら true を、そうでないなら false を返します。
2) shard_for_key
メソッドで、:default
のシンボルをキーとしてサポートするよう実装します。
自分で定義したシャーディング方法で、シャーディング列挙をサポートするには
シャーディング列挙を自分で定義したシャーディングのモデルでサポートするには、shard_connections
というインスタンスメソッドを追加する必要があります。このメソッドは、シャーディングコネクション名か、コネクションの接続で使われる設定の配列を返す必要があります。
ドキュメントと質問について
このライブラリについてのよりくわしい情報については、われわれのサイト http://dbcharmer.net を参照してください。DbCharmer の内部の詳細について知りたいときは、ソースコードをチェックアウトしてください。プラグインのコードは、だいたい100%のカバレッジがあります。test-project ディレクトリに全てのユニットテストと、実際に使うときのテストが入っています。
このプロジェクトについて何か質問があるときは、作者が使っている DbCharmer ユーザーズグループのメーリングリストにコンタクトすることができます:
- グループ情報: http://groups.google.com/group/db-charmer
- このページを購読するか、db-charmer-subscribe@googlegroups.com までメールしてください
動作する Ruby と Rails について
この gem ライブラリは、いくつかの Ruby のバージョンと、Rails 2.3, 3.0, 3.1, 3.2 で CI しています。
CI は TravisCI.org (https://travis-ci.org/kovyrin/db-charmer) でまわしています。
ビルドステータスは https://travis-ci.org/kovyrin/db-charmer を見てください(原文では現在のステータスについてのバッジみたいな画像が表示されています)
今、以下の組み合わせでビルドしています:
くわえて、この gem は Scribd.com の本番でも使われています (世界で最も大規模な Rails で作られたサイトのひとつです)。 REE + Rails 2.2, 2.3 あるいは Sinatra と Rack アプリケーションで使われています。
1.8.0 から、3.2.8 に対応しましたが、3.2.4 はオフィシャルにサポートしてないので注意してください。たぶん動くと思いますが、対応していないバージョンについてバグレポートはうけつけません。
作者について
このプラグインは Scribd.com 社内で使うために作られ、さらにそれ以外のみなさんに使ってもらうために公開されました。コードのほとんどは Oleksiy Kovyrin によって書かれ、MIT ライセンスで公開されています。詳細については LICENSE ファイルを参照してください。
その他のコントリビュータは以下のとおりです(アルファベット順):
- Allen Madsen
- Andrew Geweke
- Ashley Martens
- Cauê Guerra
- David Dai
- Dmytro Shteflyuk
- Eric Lindvall
- Eugene Pimenov
- Jonathan Viney
- Gregory Man
- Michael Birk
- Tyler McMullen
付記
- 原文は https://github.com/kovyrin/db-charmer/blob/73b72bf1aeb9e7f04bf982830050de861e4f291c/README.rdoc です
- 直訳できずにバッサリいっているため解釈が誤っている箇所がたくさんあると思います(指摘してくれる親切なかたがいたらうれしいです)
- Sharding まわりがとくにあやしい
- 明日は Octopus の README 読む予定