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テストに書くべきなのかなぁ…。まぁ目的は達成できたからよしとしよう。
実際に色々触ってみて大分イメージが沸いてきた。やっぱり自分でやるの大事。
サンプルソースを提供していただいたお二方には感謝感謝。