1
0

Railsのreplicaの負荷分散を自作Resolverで実現する

Last updated at Posted at 2024-09-01

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を作成してみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0