Magic Multi-Connectionsを試してみる(MySQL版 + 重み付けによる負荷分散)
http://d.hatena.ne.jp/Rommy/20070514/1179164848
sqlite3を使用したMagic Multi-Connectionsの分かりやすいサンプルコードが合ったのでMySQL版を作ってみた。
ただこれだけじゃ芸がないので、
1.http://blog.tkmr.org/tatsuya/show/311-twitter-db-railsで書かれているロジックを元にしたランダムによるDBアクセス分散を組み込む。
2.自分で書いてみたあんまり良い感じには見えない重み付けによるDBアクセス分散を実装。
以下ソース。
変なコメントとか動作確認用のコードが混じってるけどあんまり気にしない。
require "rubygems" require "fileutils" require "active_record" require "magic_multi_connections" require 'pp' ActiveRecord::Base.logger = Logger.new(STDERR) ActiveRecord::Base.colorize_logging = false ActiveRecord::Base.configurations = { "master"=> {"username"=>"root", "adapter"=>"mysql", "host"=>"localhost", "password"=>"root", "database"=>"test_development", "weight" => 1 }, "slave"=> {"username"=>"root", "adapter"=>"mysql", "host"=>"localhost", "password"=>"root", "database"=>"test_development", "weight" => 10 }, } connection_names = ActiveRecord::Base.configurations.keys #モジュールの作成 + ランダム用のコネクションプール作成 @@connection_pool = connection_names.map do |connection_name| puts "#{connection_name}に接続する#{connection_name.camelize}モジュールを作成します。" #モジュールを作成 Object.class_eval <<-EOS module #{connection_name.camelize} establish_connection :#{connection_name} end EOS #テスト用にユーザテーブルを新規作成 ActiveRecord::Base.establish_connection(connection_name) ActiveRecord::Schema.define { create_table(:users, :force => true){|t| t.column "name", :string } } connection_name.camelize.constantize # => camelizeはhogeをHogeに、constantizeは文字列をクラス・またはモジュールに変換 end #weight用のコネクションプール作成 @@connection_pool_by_weight = ActiveRecord::Base.configurations.inject([]){|result, item| w = item[1]["weight"] result << item[0].camelize.constantize until (w -= 1) < 0 result } p @@connection_pool_by_weight def get_connection_by_random @@connection_pool[rand(@@connection_pool.size)] end alias :random :get_connection_by_random def get_connection_by_weight @@connection_pool_by_weight[rand(@@connection_pool_by_weight.size)] end alias :weight :get_connection_by_weight class User < ActiveRecord::Base end Slave::User.create!(:name => "a") Slave::User.create!(:name => "b") Master::User.create!(:name => "c") Master::User.create!(:name => "d") # データの確認 p Slave::User.find(:all).collect(&:name) #=> ["a", "b"] p Master::User.find(:all).collect(&:name) #=> ["c", "d"] @@connection_pool.each{|v| puts "#{v} : #{v.class}"} #=> Slave : Module \r\n Master : Module #ランダム版の動作確認 table = Hash.new(0) 1.upto(1000){|i| table[random] += 1 } pp table #重み付け版の動作確認 table = Hash.new(0) 1.upto(1000){|i| table[weight] += 1 } pp table
テストとかはUnitテストに書くべきなのかなぁ…。まぁ目的は達成できたからよしとしよう。
実際に色々触ってみて大分イメージが沸いてきた。やっぱり自分でやるの大事。
サンプルソースを提供していただいたお二方には感謝感謝。