Railsの複数DB機能で負荷を分散する

こんにちは。弥生で Misoca を開発している小坂と申します。インターネットには kosappi という名前で存在しています。

前回ご紹介した みんなのコンピュータサイエンス は読んでいただけたでしょうか?

9月末で事業年度が終わる会社は多いかと思います。みなさんは無事に10月を迎えることはできましたか?私は有給休暇の日数が付与されて、とても良い気分です 🏝

今回は、Rails の複数 DB 機能を利用して9月末の高負荷を乗り切った話を紹介いたします。

🔥 月末の高負荷

Misoca は請求書作成ソフトということもあり、月末にアクセスが増加します。

ユーザの増加や、機能が充実したことにより、DB への負荷も増加しています。8月末の負荷は DB の限界に近い値でした。 特に、文書の一覧や検索などの参照系のクエリの比重が高く、機能の充実によってクエリ自体も重いものになっており、問題になっています。

9月末は事業年度が終わるユーザも多く、8月末よりも負荷が高くなり、このままでは DB の処理能力の限界を超えてしまう事態が予想されました。

DB への負荷を改善する方法としては、下記のようなものがあると思います。

  • 重いクエリを見直して軽くする(SQLの改善)
  • DB インスタンスの強化(スケールアップ)
  • DB インスタンスを増やして負荷を分散させる(スケールアウト)

今回は参照系のクエリが問題になっていることもあるので、読み込み専用の DB インスタンス(リードレプリカ)を増やして、一部の重いクエリをそちらに向けることにしました。

🛠 Active Record による複数の DB 利用

Misoca は Ruby on Rails の上で開発されています。

Rails は 6 にバージョンアップした際に、複数の DB 利用をサポートしました。Rails 5 でも、追加の gem を使えば実現できたのですが、Rails 6 からは標準機能として提供されます。

参考 : Active Record で複数のデータベース利用

今回はこの機能を利用して、一部のクエリをリードレプリカに向けました。

一部のクエリをリードレプリカに向ける

まず DB を追加します。

事前に AWS の RDS で、普段使っているインスタンスをレプリケーションするインスタンスを作りました。 レプリケーションの遅延が発生しますが、これは 1ms 以内と示されていたので、今回は問題にならないと判断しています。

メインのDBを primary、リードレプリカを replica という名前にしています。 Rails が DB を判断するために、レプリカとして利用する DB には replica: true を書く必要があります。

production:
  primary: &primary
    <<: *default
    host: <%= ENV['PRIMARY_HOST']  %>
  replica:
    <<: *primary
    host: <%= ENV['REPLICA_HOST']  %>
    replica: true

そして全てのモデルで上記の DB が利用できるようにします。 今回はロールが writing の場合は primary を、 reading の場合は replica をそれぞれ利用します。

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

  connects_to database: { writing: :primary, reading: :replica }
end

最後に、向き先を変えたいクエリを実行している箇所を、下記のようにブロックに含めればOKです。このブロックの外では必ず role: :writing でクエリが実行されます。 role 以外にも database を直接指定することもできますが、こちらは非推奨となっているので気をつけてください。

ActiveRecord::Base.connected_to(role: :reading) do
    # リードレプリカに向けたいクエリを実行するコード
end

今回は手動でロールを変更しましたが、自動的に切り替えることも可能です。 書き込むクエリの場合は primary、読み込みクエリの場合は replica を使う、といった振り分けを書くことができます。

参考 : Active Record で複数のデータベース利用#コネクションの自動切り替えを有効にする

📉 結果

以上の対策を施した上で、無事に9月末を乗り越えることができました。

DB の負荷はどうなっていたんでしょうか?

primary DB の IOPS 1 を見てみましょう。オレンジが READ、ブルーが WRITE の IOPS です。

リードレプリカの利用開始時期(9/15ごろ)から、READ の IOPS が下がっていることがわかります。 8月末と9月末を比較すると、かなり負荷を減らすことができました。

今回は9月末の高負荷を、Rails の複数 DB 機能で乗り切ることができました。 今後は、2台に増えた DB のスペックを見直したり、他のクエリをリードレプリカに向けることを検討する予定です。

🎺 宣伝

Misoca 開発チームでは、Rails の新機能を使って課題を解決したいエンジニアを募集しています。


  1. Input/Output Per Second の略です。1秒あたりの入出力の多さを示しています。