【Rails6.1】モデルでのコネクション自動切り替えを試す

ZAICOでは、Android・iOS・Rubyエンジニアを絶賛募集中です! 詳しくは、採用ページをご覧ください。

好きな場所で働こう

こんにちは、ZAICO開発チームです。

私の地元は結構雪が降る地域で、今年は寒さが厳しかったですが、ようやく過ごしやすくなってきました。春が待ち遠しいです。

さて、今回はRails6.1のいわゆる複数データベース機能について触れたいと思います。

これに関しては様々な記事で紹介されていますが、今回はコネクション切り替え、すなわちprimary/replicaの挙動を試してみます。

前提条件

  • Ruby 2.6.6
  • Rails 6.1.3
  • APIモードを利用

データベース・replicaの設定

他記事では複数データベースも設定されていますが、今回はコネクション切り替えだけを試したく、シンプルに書きます。

database.yml

development:
  zaicodb:
    <<: *default
    database: zaicodb_development
  zaicodb_replica:
    <<: *default
    database: zaicodb_development
    replica: true

$ bundle exec rails db:create

データベース接続先の設定

app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :zaicodb, reading: :zaicodb_replica } # 追記
end

コネクションの自動切り替えを有効にする

config/application.rb

# 追記
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session

モデルやマイグレーションを用意(検証用)

$ bundle exec rails g model User name:string

$ bundle exec rails db:migrate

コントローラを用意(検証用)

class UsersController < ApplicationController
  def index
    render json: User.all
  end
  def create
    user = User.new
    user.name = params[:name]
    user.save!
    render json: user
  end
  # ...その他省略
end

primary/replicaどちらを参照しているかログで確認できるようにする

各種記事でも紹介がある、arproxyというgemを利用します。

Gemfile

gem 'arproxy' # 追記

config/initializers/arproxy.rb

class QueryTracer < Arproxy::Base
  def execute(sql, name=nil)
    role = ActiveRecord::Base.current_role
    name = "#{name} [#{role}]"
    super(sql, name)
  end
end

Arproxy.configure do |config|
  config.adapter = "mysql2" # A DB Apdapter name which is used in your database.yml
  config.use QueryTracer
end
Arproxy.enable!

動作確認

$ curl localhost:3000/users/

ちゃんとreadingと出ていて、replicaを読んでいますね。

$ curl -X POST -H ‘Content-Type: application/json’ -d ‘{“name”: “zaico taro”}’ localhost:3000/users

こちらはwriting、つまりprimary(zaicodb)を参照しています。

Railsガイドにはこのように記載されています。

アプリケーションがPOST、PUT、DELETE、PATCHのいずれかのリクエストを受け取ると、自動的にprimaryに書き込みます。書き込み後に指定の時間が経過するまでは、アプリケーションはprimaryから読み出します。アプリケーションがGETリクエストやHEADリクエストを受け取ると、直近の書き込みがなければreplicaから読み出します。

ということなので、これが本当にそうなのか確認してみます。

GETメソッドで呼ばれるアクションで無理やりINSERTを試す

app/controllers/users_controller.rb

# 追記
def save
    user = User.new
    user.name = params[:name]
    user.save!
    render json: user
end

config/routes.rb

Rails.application.routes.draw do
  resources :users do
    get :save, on: :collection # 追記
  end
end

動作確認

$ curl localhost:3000/users/save?name=yahoo

おー!ActiveRecord::ReadOnlyErrorということで、ちゃんと弾かれています。replicaをしっかり読もうとしているんですね。

Rails6.1ということで、水平シャーディング機能なども試したいですが、それは別の機会でやってみます。

以上、モデルでのコネクション自動切り替えを試した内容でした。

参考記事

Active Record で複数のデータベース利用 – Railsガイド

Rails6.0で複数のデータベースを利用する – youichiro – Zenn

ZAICOでは、新しいテクノロジーの力でモノの状態・流れを把握する仕組みに一緒に取り組む仲間を募集しております。
詳しくは、採用ページをご覧ください。

好きな場所で働こう