apartment gem にはテナント毎に接続先 DB を切り替える multi-server 機能があります。
テナントが増えてきたら DB 負荷を分散できるように、本機能を試してみました。
環境
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 に変更して対処しました。
Apartment#db_config_for のモンキーパッチを適用
ログを見ていると、Tenant#switch
の度に Team
を全件取得する SQL が流れていました。
どうも先程設定した config.tenant_names
が switch
の度に実行されている模様。
調査したところ 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 まで回復