【訂正】Railsでconnection数を破綻させずにestablish_connectionで別DBを利用する

 前のエントリ(http://d.hatena.ne.jp/raugisu/20120428/1335598633)で、ActiveRecord で別DBに接続するために establish_connection を安易に使うことで発生する地獄について書いた。

 んで、その地獄への対策として挙げたもののなかで、一番良いと思われる、「別DBへのコネクションプールを引き受けるクラスを作って継承する」方法を試してみた。
 ぶっちゃけ、元ネタ(http://d.hatena.ne.jp/rudeboyjet/20101221/p1 )のほうがキチンとしてるので、そっち見た方が参考になる。

(2012/5/8、コメント欄で「self.abstract_class = true」の設定の指摘をいただいたので訂正しました。ありがとうございました!)
(2012/5/29、訂正部分、打ち消し線で訂正とかしてたんだけど、読みづらいので消しました)


 方法は、Railsでモデルを2個(hoge.rb, fuga.rb)作って、適当なコントローラ(foo_controller.rb)のindexアクションで、render :text => (Hoge.count + Fuga.count).to_s
とかやっておしまい。viewまでする必要もなし。

 最初は別DBにつながず、/foo/index を何度か実行して、mysqlのshow processlist; でコネクション数を確認する。

mysql> show processlist;
Id User Host db Command Time State Info
158 root localhost mysql Query 0 NULL show processlist
195 root localhost rider_development Sleep 5 NULL
196 root localhost rider_development Sleep 5 NULL
3 rows in set (0.00 sec)

 ※DB名はドンマイ。
 unicornのワーカー数を2個にしているので、2個(+mysqlコンソールの1個)のコネクションがでる。
 まあ、普通。

 ここで、もう一個 create database rider_dummy でmysqlにDBを作り、database.ymlにもrider_dummy用の設定を書く「dummy:」で作るといいんじゃないかな。
 DBの中身は元のrider_developmentをmysqldumpして書き込めばいいと思うよ。

$ vi database.yml
development:                     
  adapter: mysql2                
  encoding: utf8                 
  reconnect: false               
  database: rider_development    
  pool: 5                        
  username: root                 
  password:                    
  host: localhost                
                                 
dummy:                           
  adapter: mysql2                
  encoding: utf8                 
  reconnect: false               
  database: rider_dummy          
  pool: 5                        
  username: root                 
  password:                      
  host: localhost   

 そして、HogeとFugaの中に、establish_connection :dummy って書き込んでやり、再び foo/index を実行して、mysqlのコネクション数を見る。

mysql> show processlist;
Id User Host db Command Time State Info
158 root localhost mysql Query 0 NULL show processlist
163 root localhost rider_development Sleep 22 NULL
164 root localhost rider_dummy Sleep 26 NULL
165 root localhost rider_dummy Sleep 26 NULL
166 root localhost rider_development Sleep 23 NULL
167 root localhost rider_dummy Sleep 23 NULL
168 root localhost rider_dummy Sleep 23 NULL
7 rows in set (0.00 sec)

 rider_dummyにつなぎに行くクラスが2個なので、コネクションがunicornワーカーひとつにつき2個できたのがわかる。
 rider_developmentへのコネクションは、使ってなくてもできちゃうんだね(でも、pool分のコネクションは作らない模様)

 で、ここで、もう一個ActiveRecord::Baseを継承したクラス(dummy_access.rb)をつくる。

$ vi app/models/dummy_access.rb
class  DummyAccess < ActiveRecord::Base
  self.abstract_class = true
  establish_connection :dummy
end

 DummyAccess に self.abstract_class = true を書き込んでおくと継承用のモデルクラスになるそうで、対応するテーブルなくても大丈夫。
 で、establish_connection :dummy と書き込んで、これが別DBに接続しにいくようにする。
 さらに、HogeとFugaを DummyAccess を継承するように書き換える。

$ vi hoge.rb
class Hoge < DummyAccess
end

$ vi fuga.rb
class Fuga < DummyAccess
end

 で、ここまでやってから、一度 unicorn を再起動させ、foo/index を何度か実行してから mysql で show processlist; してみる。

mysql> show processlist;
Id User Host db Command Time State Info
158 root localhost mysql Query 0 NULL show processlist
187 root localhost rider_development Sleep 3 NULL
188 root localhost rider_dummy Sleep 3 NULL
189 root localhost rider_development Sleep 3 NULL
190 root localhost rider_dummy Sleep 5 NULL
5 rows in set (0.00 sec)

 みごと、別DBであるrider_dummyへの接続が1個になった\(^O^)/
 これで一安心。


(2012/5/29追記)
 当初、「self.abstract_class = true」の設定を知らなくて、DummyAccessでもダミーのテーブルをset_table_nameでセットしたり、それを継承したHogeクラス、Fugaクラスでもわざわざset_table_nameでセットする必要がある、と書いていました。
 でもさすがはRuby On Rails、人気のフレームワークだけあって、そんなアホなことはなかったですね!
 コメント欄でアドバイスくださった通りすがりの方、マジでありがとう!
 すごい助かりました。
 いくつかある地獄の一つを解決できました。