Rails6のマルチDBで、普段は参照しないスロークエリ用リードレプリカからデータを読み出す

マルチDB設定をしたRails6で、普段参照するリードレプリカとも異なる、分析クエリ用リードレプリカからデータを読み出すにはどうすればよいかという話。

Rails6.0のマルチDB設定はリリース後に微妙にインターフェイスが変更されていて今となっては推奨されない情報に行きあたってしまうことがあるので(というか僕自身が行きあたったので)、半分メモとして書き残しておきます。

要するに

  • ActiveRecord::Baseを継承した抽象クラスのconnects_toで、writingロールでもreadingロールでもない第三のロールを作る
  • 第三のロールは普段は使用されない
  • 重いSELECTをするときだけActiveRecord::Base.connected_toのロール指定で第三のロールを使う

詳しく

Rails6.0リリース当初のRailsガイドには、「ActiveRecord::Base.connected_toにdatabaseキーワード引数を渡すと、そのdo~endブロックの中はdatabaseキーワード引数で指定したデータベースを用いる」と書いてありました。 github.com

しかし、これは既に非推奨ならびに削除の未来が決まっていて、英語版のRailsガイドからは既に当該の記述は削除されています。 api.rubyonrails.org github.com

Railsガイドの日本語訳ではまだ残っていたので、該当部分を削除するPRは出しました。

ではActiveRecord::Base.connected_toのdatabaseキーワード引数を使わずに実現するにはどうすればいいかといえば、 話は単純でApplicationRecordでconnects_toにデータベース設定を渡すときにwritingでもreadingでもない第三のロールを同時に設定すればよさそう。

database.ymlが

production:
  primary:
    host: プライマリインスタンスのFQDN
    user: root
    adapter: mysql
  replica:
    host: リードレプリカインスタンスのFQDN
    user: root_readonly
    adapter: mysql
    replica: true
  analyze:
    host: 分析用インスタンスのFQDN
    user: root_readonly
    adapter: mysql
    replica: true

となっているとき、

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :replica, analyzing: :analyze }
end

class Post< ApplicationRecord 
  中略
end

というようにconnects_to で三つのロールを指定します。

Rails6 のマルチDB機能は特に指定しなければwritingロールを書き込み、readingロールを読み込みに使用するので、analyzingロールは普段使用されません。

そして普段参照するリードレプリカへ発行すると重すぎてサービス自体に影響が出てしまうようなときにだけ、

posts = ActiveRecord::Base.connected_to(role: :analyzing) do
  Post.all
end

などとすればこの時だけ分析用インスタンスへSELECTが発行されるようになります。