Railsと複数DBとPreloadingと

このエントリは Classi Advent Calendar 2016 8日目の記事です。

そういえばいまだに転職エントリーを書いていませんが7月に転職をしていましてClassiのファースト社員となりました 💪

複数DB on Rails

Classiではセキュリティやら何やらの関係でみんな大好き複数DB on Railsになっています。
R/W Splittingやシャーディングではなく、1つの共通DBとテナント毎のDBがあるマルチテナンシー(?)構成です。

class User < ActiveRecord::Base
  has_many :entries

  # (共通DBへの接続情報)
end

class Entry < ActiveRecord::Base
  belongs_to :user

  # (テナントDBへの接続情報)
end

User.connection # => 共通DBのdatabase名のconnection
Entry.connection # => テナントDBのdatabase名のconnection

といった感じで使用するmodelに応じて適切にコネクションが切り替わるようになっています

異DB間association

基本的にはテナント側で閉じる世界観になるのですが、稀によく頻繁につらいことに共通側とテナント側のモデル間でassociationが貼られていたりもします。
ここで問題になるのがPreloadingの処理です。

preload を使う分にはそれぞれのモデルに対して問い合わせが走るのでコネクションもいい感じに切り替わってくれるのですが、joinseager_loadの場合はそうも行きません😱

# OK
User.preload(:entries)

# NG
User.joins(:entries)
User.eager_load(:entries)

#=> Mysql2::Error: Table 'entries' doesn't exist

そもそもそんなこと出来るの?って思っていたんですが↓の記事を見た感じ、MySQLの場合テーブル名の前にdatabase名を入れることで可能とのこと(もちろん同じDBサーバ内にそれぞれのdatabaseが入ってる必要はあります)
MySQL5:異なるデータベース間のテーブル結合 DB名:db1 TABLE名… - 人力検索はてな

たとえばこんな感じ?

SELECT
    common.users.*,
    tenant_1.entries.*
FROM
    common.users
JOIN tenant_1.entries
    ON common.users.id = tenant_1.entries.user_id

Classiの場合はこの条件を満たしていたので、後はRailsが発行するクエリのテーブル名の前に[database名].を追加するだけで上手くいきそうです。

たとえばtable_name_prefixを使う

困ったときのStack Overflowを頼ったところ以下のようなものが。 Rails 3 - Multiple database with joins condition - Stack Overflow

テーブル名の先頭にprefixを付けるとこで、database名を追加している感じ。 ということで試してました。

 class User < ActiveRecord::Base
+  def self.table_name_prefix
+   "common."
+  end
 end
 class Entry < ActiveRecord::Base
+  def self.table_name_prefix
+    "tenant."
+  end
 end

早速JOINを試してみます

[0] pry(main)> User.joins(:entries).to_sql

SELECT
    `common`.`users`.*
FROM
    `common`.`users`
JOIN `tenant`.`entries`
    ON `common`.`users`.id = `tenant`.`entries`.user_id

ということで上手くいきました。
実際にはbatchなどでtenantを切り替えたりする処理があったりしてtable_name_prefixの更新などが必要になってきますが、 ActiveRecordreset_table_nameを使えばそれもなんとかなりそうです。


と、ここまで書きましたが深淵なる理由によりこのコードはClassiの内部ではまだ動いていません 😫 本当はこの処理をいい感じにgem化して今日までにリリース!!とかっこいい感じにしたかったのですが、自分が 怪盗業プロデューサー業 で多忙だったため進捗ダメでした…
あと1回Advent Calendarが回ってくる予定なのでそれまでに何らかの形にできればなと思ってます


こんな風に複数DBが大好き!!、または日本の教育を変えたい!!というRailsエンジニアの方はぜひClassiに話を聞きに来てください!

明日は @kenjiskywalker の「🍣」です