🦔

graphql-rubyで、queryだけのリクエストの場合リードレプリカに接続する

2022/07/18に公開

Railsのマルチデータベース機能を利用しWriterインスタンスとReaderインスタンスを分けて利用しているRailsプロジェクトで、リクエストのGraphQLクエリがデータ取得しか行わないのであればReaderインスタンスに自動で接続するようにしたいです。

Why

  • Writerインスタンスの負荷軽減のため、Readerインスタンスを可能な限り利用したい。
  • ActiveRecord::Base.connected_to(role: :reading) を利用せず、自動で機械的にDBロールを切り替えたい。
    • 利用者の裁量に委ねるとなると切り替えの漏れが発生するため

解決策

実装

module Tracers
  class DatabaseRoleSelector
    EVENT_NAME = 'execute_multiplex'.freeze

    # @param [String] event
    # @param [Hash] data
    # @option data [GraphQL::Execution::Multiplex] multiplex
    def trace(event, data, &block)
      if event == EVENT_NAME
        multiplex = data[:multiplex]
        role = if multiplex.queries.all?(&:query?)
                 ActiveRecord::Base.reading_role
               else
                 ActiveRecord::Base.writing_role
               end

        Rails.logger.debug("[#{self.class.name}] ActiveRecord::Base.connected_to(role: #{role.inspect})")
        ActiveRecord::Base.connected_to(role: role, &block)
      else
        yield
      end
    end
  end
end

# app/graphql/my_schema.rb
class MySchema < GraphQL::Schema
  tracer Tracers::DatabaseRoleSelector.new
end

解説

Trancers

graphql–rubyのtracingという仕組みを利用します。

https://graphql-ruby.org/queries/tracing.html

これはGraphQLクエリに対してrackミドルウェア的にコードを挿入する仕組みで、trace メソッドを実装し、処理を追加した後 yield を呼び出すことで、クエリに対して前処理を追加できます。

Datadog等の監視プラットフォームからクエリの追跡に使われることが多いです。

eventはイベント名を表します。一覧はこちらです: https://graphql-ruby.org/api-doc/2.0.9/GraphQL/Tracing

data は後述します。

multiplex

dataハッシュの中のmultiplexを利用します。

https://graphql-ruby.org/queries/multiplex.html

multiplexは、Clientから複数のクエリがバッチ的にリクエストされた場合でも処理を行えるような仕組みです。

とはいえ、GraphQLの全てのクエリはmultiplexとして実装されています。バッチを意図していなくとも、1つの要素を含む配列として渡ってきます。

database role

database roleの判定ロジックは、multiplexオブジェクトのqueriesが全てクエリである(== readのみを行うクエリである)場合はReaderインスタンスに接続します。

まだ検討していないこと

queryだがDBのwriteがしたい。迂回できないか?

  • write処理は本当にqueryで必要ですか?mutationに分割できませんか?
  • 必要になったタイミングで考えるのが良さそうです。迂回できるように実装してください。

DB分割をするなど、違うDBインスタンスを見る必要が出てきた

  • 何かしらのフラグを見てどのDBに接続するか判定するロジックを追加するのが良さそうです。

参考

https://github.com/rmosolgo/graphql-ruby/issues/2929

  • マルチデータベースのサポートについて議論されているIssueです。今回の実装の参考にさせていただきました。
  • マルチデータベースのサポートをgraphql-ruby側で取り込むことは考えておらず、利用者側でオプトインしてほしいとのことでした。
GitHubで編集を提案

Discussion