Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@tak1827

[Rails]DBのReadを、既存コード変更なしでレプリカへ向けるGemをMakaraにした話

Makaraを選んだ経緯

DBアクセスのRead/Writeを分けるGemとしてswitch_pointが有名です。しかし、既存コードを変更しなければならず、導入コストの観点から、私たちのプロジェクトでは使えませんでした。

他に有名なものとてOctopusがあります。Partial Replicationの設定をすることでデフォルトの接続先をマスターに向けることができ、既存コード変更なしで導入できます。使い方もModelにusingを付けるだけと快適です。ただ、2点問題がありました。

  • rake db:setupでコケる。どうやらdb:createdb:schema:loadを同時に行うと上手く動作しない
  • rake db:migraterake db:rollbackでレプリカにar_internal_metadataschema_migrationsのテーブルが作成される

2点目の、レプリカなのに書き込まれてしまう点が致命的で、私たちのプロジェクトでは使えませんでした。

困っていた折、Scaling Readsという記事に出会い救われました。Makaraの使い方を共有します。

Makaraとは

開発元ブログより

MakaraはRead/Writeを分けるActive Recordのアダプタだ

こんな機能があります。

  • 複数DBへのRead/Write分離
  • スレーブ障害時のフェイルオーバー
  • スレーブ接続断時の自動再接続
  • マスターないしスレーブへの接続を分離しないオプション(sticky)
  • Mysqlやpostgresに対応
  • Middlewareを提供 for releasing stuck connections
  • スレーブDBへの荷重接続(Weighted connection pooling)

以前はOctopus使ってた。エラーハンドリングやログ出力がイケてないから改善したぜ。
また、マスターの変更がすぐにレプリカに反映されないとき'RecordNotFound'エラーが出てしまう。マスターに書き込んだら読み込みもマスターからの方が安全なんだ。だから俺たちは'sticky'という概念を導入したぜ。

使い方

distribute_readsブロックで囲むとレプリカへ接続されます。囲わないとマスターへ接続されます。

distribute_reads { User.last }   # replica
User.first                       # primary

Makara公式のReadmeを読むと、本来の使い方は自分でProxyを作るようです。しかし、参考にしたブログではProxyクラスをprependで拡張しています。

distribute_reads実行時に:distribute_readsフラグをON。これがONならレプリカへ接続。OFFならマスターへ接続。ということをやっています。

注意すべきは、トランザクションを貼ると強制的にマスター接続になる点です。
/makara/blob/master/lib/makara/proxy.rb

def _appropriate_pool(method_name, args)
...
  elsif in_transaction?
    @master_pool
  # yay! use a slave
  else
    @slave_pool
  end
end

導入方法

GemfileにMakaraを追加

gem 'makara', '~> 0.4'

config/database.ymlをMakara設定に変更。マスターとレプリカの設定を記載。

production:
  adapter: 'mysql2_makara'
  database: 'MyAppProduction'
  makara:
    blacklist_duration: 5
    master_ttl: 5
    master_strategy: round_robin
    sticky: true
    connections:
      - role: master
        name: primary
        host: master.sql.host
      - role: slave
        name: replica
        host: slave.sql.host

config/initializers/makara.rbを新規作成。Makara::Context.generateはv0.4以降廃止されたので、テストケースを参考にseedを手動作成。

Makara::Cache.store = :noop

module DefaultToPrimary
  def _appropriate_pool(*args)
    return @master_pool unless Thread.current[:distribute_reads]
    super
  end
end

Makara::Proxy.send :prepend, DefaultToPrimary

module DistributeReads
  def distribute_reads
    previous_value = Thread.current[:distribute_reads]
    begin
      Thread.current[:distribute_reads] = true
    seed = Time.now.to_f
      Makara::Context.set_current(mysql: seed)
      yield
    ensure
      Thread.current[:distribute_reads] = previous_value
    end
  end
end

Object.send :include, DistributeReads
4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What is going on with this article?