LoginSignup
23
11

More than 5 years have passed since last update.

"Octopus"のクローン"Tako"を作ってRails5でshardingする話

Posted at

この記事は Ruby on Rails アドベントカレンダーの5日目です。

RailsでDBのshardingをする話

負荷分散のためにRailsでDBのシャーディングをしたいときに、いくつか利用できるGemがあります。
例を挙げると、

などなど…そこそこあるのですが、この中でこれ書いている現在Rails 5 readyなのがactiverecord-shardingswitch_pointだけのようでした。

で、自分たちはOctopusを使ってしまっていた

Rails 4.2を利用していて、Octopus使っていて、からのー、Rails 5にアップグレード、うっ、頭が…

OctopusはActiveRecordの標準の機能を拡張してシャーディングの機能を提供してくれるところが良いところなのですが、裏を返せばActiveRecordの実装に手を入れるということなのでRailsのバージョンアップコストがとても高いというデメリットがあります。

そこそこコードが成長してしまっていたので、今から他のGemに乗り換えるのは難しそうでした。(activerecord-shardingなどは、独自メソッドやDSLをActiveRecordに生やすように実装されているので、ActiveRecordのもとの実装に影響されない。かわりに、コードのマイグレーションが必要だった)

ぶっちゃけやりたいのはシャーディングだけで、そんなに多機能なGemじゃなくてよかった

Rails 5 readyなoctopusが欲しい…と思っていたところに本家issueにこんなコメントが

image

もうOctopusメンテされないらしい…チャンス…!?

※と…思っていたのだけれど、Gem作ってからこの記事書いてる間にこんなコメントが

image

ああっ…台無し…!? でも、ここまできたので続けていきます

Octopusはもうダメかもしれない :fearful: …Tako作ろ…

ということで、OctopusのクローンのTakoを作ってみました。
MITライセンスで公開しています。

Takoで実現したかったことの要件をまとめると以下のような感じです

  • Octopusの#usingメソッドのように、与えられたブロック内またはそのメソッドチェイン内でシャードを指定してSQLを発行したい。
  • この際なのでActiveRecordの実装への依存を可能な限り減らしたい。
  • Rails標準のマイグレーションも可能な限り利用できるようにしたい。
    • 完全水平分割だけでいいのでシャード指定のマイグレーションとか要らない
  • Railsのバージョンアップがあったら、可能な限り早く対応したい。
  • Railsの対応バージョンは<= 4.0で5系対応。 3系はもう良いかなって…

使い方

インストール

Gemfile
gem 'tako'
bundle install

or

gem install tako

で利用できます

DBのセットアップ

config/shards.ymlを作成します。読み込む際に、ERBで一度パースされます。
config/shards.ymlの内容はTako.configで取得することができます。

config/shards.yml
default: &default
  adapter: mysql2
  encoding: utf8
  charset: utf8
  collation: utf8_general_ci
  reconnect: false
  username: <%= ENV['MYSQL_USER_NAME'] %>
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  host: <%= ENV['MYSQL_HOST'] %>
  port: <%= ENV['MYSQL_PORT'] %>
  database: tako_<%= Rails.env %>

tako:
  <%= Rails.env %>:
    shard1:
      <<: *default
      host: <%= ENV['MYSQL_SHARD1_HOST'] %>
      database: tako_<%= Rails.env %>_shard1
    shard2:
      <<: *default
      host: <%= ENV['MYSQL_SHARD2_HOST'] %>
      database: tako_<%= Rails.env %>_shard1

以下のように rake db:tako:<command>でマイグレーションを実行できます。

$ bundle exec rake db:tako:create
$ bundle exec rake db:tako:migrate

shards.ymlから全シャード情報を読み込み、それぞれに対してdb:migrateを掛けるようになっています。
各shard間でマイグレーションのバージョンが違っていてもいい感じで処理してくれます。

アプリ内での利用

Octopus.usingに相当するメソッドが、Tako.shardになります。
usingにしなかった理由は、Module#usingと名前が重複するからです。

User.shard(:slave_one).where(:name => "Thiago").limit(3)
# => :slave_oneでクエリ実行

User.create(name: "Bob")
# => database.ymlに記載されているデフォルトのDBでクエリ実行

Tako.shard(:slave_two) do
  User.create(name: "Mike")
  # => :slave_twoでクエリ実行
end

User.shard(:slave_two) do
  User.shard(:slave_one).create(name: "Mike")
  # => :slave_oneでクエリ実行
end

アソシエーションでの対応

要するに、そのActiveRecord::Baseのインスタンスが作成されたスコープにおけるシャードが、保存先のシャードになる実装になっています。

user = User.shard(:shard01).create(name: "Jerry")

user.logs.create
# => この場合は、userと同じシャードである:shard01でlogが作成される

user.logs << Log.shard(:shard02).new
# => ただし、この場合は:shard02

life = user.build_life
life.save!
# => buildしてsaveの場合は、親?と同じ:shard01になる。

全シャードでの実行

Tako.with_all_shards(&block)で、ブロック内のコードが全シャードで実行されます

# 全シャードでMikeさんがお見えになる
Tako.with_all_shards do
  User.create(name: "Mike")
end

Octopusからマイグレーションしたい場合

まずはじめに、ありがとうございます!

以下の手順でマイグレーションできます。
migration等でOctopusの機能を利用している場合は、避けていただくか、TakoにPRを送ってください。

  1. Octopus.using またはModel.usingTako.shardまたはModel.shardに置換します
  2. config/shards.ymlをTako用に書き換えます

実装で苦労した所/妥協した所

prependが使えない

他にActiveRecordを拡張するgemを利用していた場合、例えばsave!メソッドをalias_methodまたはaliasするようなgemが入っていると、prependでsuperを呼び出そうとして、stack level too deepになってしまいます。なので、今回Takoの実装には同じくalias_methodが利用されています…

ActiveRecord::Migratorが結構魔界

Railsのソース読む上で一番苦労したのがMigratorのコードを追うところでした。
Rails内部から利用される想定で書いてあるので、外からの呼び出しが辛かった覚えがあります。

ConnectionPoolが利用できない

Thread使うことって、個人的にはあんまりないかなって思ったので、実装が楽な方を取りました。
Takoでは、connection_poolは利用できません。

まとめ

以上、シャーディングに関するつらみとTakoの紹介でした。

23
11
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
23
11