Railsでは6.0から複数データベースへの対応を開始しました。
primaryと単一replicaのへの接続が実装されており、GETリクエストをreplicaで処理することなどで負荷分散を行うことができます。
しかし、複数のreplicaを利用しての負荷分散は実装されていません。
とはいえ、ちょっと工夫をすると簡単に行うことができるので実際に作成してみましょう。
※ 以下の文章は Active Record の複数データベース対応 を一度読んでいただいているのが前提になります
以下のような手順で行います。
- 複数のreplicaの定義
- モデルとの関連付け
- resolverの定義
複数のreplicaの定義
-
config/database.yml
に primary と複数の replica に対するデータベース接続の定義を記述します- 今回は既に replica が一つある状態から replica は二つに変更します
- development環境で定義します
config/database.yml
development:
primary:
database: my_database
adapter: mysql2
replica:
database: my_database_replica
adapter: mysql2
+ secondary_replica:
+ database: my_database_secondary_replica
+ adapter: mysql2
モデルとの関連付け
- 先ほど定義したデータベース接続の定義を使って、各データベースに接続できるように定義します
- 新しく secondary_replica という role を作成して、呼び出せるようにします
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
# 中略
- connects_to database: { writing: :primary, reading: :replica }
+ connects_to database: { writing: :primary, reading: :replica, secondary_reading: :secondary_replica }
end
自作Resolverを作成する
- ActiveRecord::Middleware::DatabaseSelector::Resolver を継承した自作Resolverを作成します
- primaryを利用しないGETリクエストの半分をreplica、もう半分をsencondary_replicaに行わせる場合、
application_record.rb
のrole定義を利用して以下のように記述できます- replicaで処理するところをsecondary_replicaを使うように上書きしています
- 今回はrand関数で分散していますが、ここを工夫すると負荷を自在に変えられたりできるようになります
lib/database_resolver/secondary_replica_resolver.rb
module DatabaseSelector
class SecondaryReplicaResolver < ActiveRecord::Middleware::DatabaseSelector::Resolver
private
# @override {::ActiveRecord::Middleware::DatabaseSelector::Resolver#read_from_replica}
def read_from_replica(&blk)
if rand(2).eql?(0)
ActiveRecord::Base.connected_to(role: :secondary_reading, prevent_writes: true) do
instrumenter.instrument('database_selector.active_record.read_from_secondary_replica', &blk)
end
else
super
end
end
end
end
自作Resolverを利用するように指定する
- 自作Resolverを扱うようにResolverを変更
- これにより、replicaとsecondary_replicaのリクエスト負荷が半々になる
config/initializers/multi_db.rb
require Rails.root.join('lib', 'database_selector', 'secondary_replica_resolver')
Rails.application.configure do
config.active_record.database_selector = { delay: 2.seconds }
- config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
+ config.active_record.database_resolver = DatabaseSelector::SecondaryReplicaResolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end
まとめ
自作Resolverが行えることは多岐にわたり、その一つとして簡単に複数replicaを活用した負荷分散は実現できます。
他にもデータベースの設定に合わせて様々な構成を試せるので、ぜひ要件に対して適切なResolverを作成してみてください。