Ruby
Rails
apartment

Rails + apartment gem + multi-server を試してみた

apartment gem にはテナント毎に接続先 DB を切り替える multi-server 機能があります。
テナントが増えてきたら DB 負荷を分散できるように、本機能を試してみました。

https://github.com/influitive/apartment#tenants-on-different-servers

環境

Ruby 2.4.4
Rails 5.1.5
MySQL 5.7

database.yml にテナント DB 接続設定を追加する

今回は調査が目的なので、メイン DB とテナント DB は同一としました。

development_tenant_group_1:
  <<: *default
  pool: 1
  username:
  password:
  host: mysql
  socket: /tmp/mysql.sock

apartment の設定を変更する

README の設定例を見ると、テナント名が固定なら Hash で、動的なら lambda で記述する必要があるようです。
作成しているサービスはユーザーの申込みに応じてテナントが動的に増えていくので、lambda で記述する方式としました。

db_config = Rails.configuration.database_configuration["#{Rails.env}_tenant_group_1"]
config.tenant_names = -> { Team.all.each_with_object({}) { |e, hash| hash[e.tenant_name] = db_config } }

config.use_schemas = false
config.with_multi_server_setup = true

Puma から Unicorn に変更

動作確認をしてみると、DB 接続でエラーを吐くようになり正常に動作しませんでした。
Issues を漁ってみると、そもそもスレッドセーフでないため Puma 環境では動作しない、ということがわかりました。
Rails + unicorn に変更して対処しました。

https://github.com/influitive/apartment/issues/413

Apartment#db_config_for のモンキーパッチを適用

ログを見ていると、Tenant#switch の度に Team を全件取得する SQL が流れていました。
どうも先程設定した config.tenant_namesswitch の度に実行されている模様。

調査したところ Apartment#db_config_for が呼ばれるために lambda が実行されてしまうようなので、モンキーパッチを適用してしのぎました。

テナント名から DB 接続情報を取得する箇所をモンキーパッチ
module Apartment
  def self.db_config_for(tenant)
    # TODO: 将来的に tenant 毎に DB を切り替える
    Rails.configuration.database_configuration["#{Rails.env}_tenant_group_1"]
  end
end

その他発生した問題

  • Mac 環境で RSpec を流すと途中で DB エラーが発生する (未解決)
    • CircleCI や Docker 上で実行する分には発生しなかった
  • パフォーマンスが落ちた
    • ローカルで開発していると時折遅くなる
    • NewRelic 上のレスポンスタイムの数値で 40-60ms -> 100-120ms くらいまで落ちた
    • チューニングして 60-80ms まで回復