0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RailsでRDSリードレプリカを活用した読み書き分離とスケールイン対応

Last updated at Posted at 2025-10-28

Rails で RDS リードレプリカを活用した読み書き分離とスケールイン対応

概要

Rails アプリケーションで RDS のリードレプリカを活用し、重い SQL クエリをリードレプリカに流すことで、メインのデータベースの負荷を軽減する方法と、リードレプリカを安全にスケールインできるようにする。

1. データベース設定

まず、config/database.ymlで読み書き用のデータベース接続を分離します。

default: &default
  adapter: mysql2
  charset: utf8mb4
  encoding: utf8mb4
  collation: utf8mb4_general_ci
  database: <%= ENV["DATABASE_NAME"] %>
  username: <%= ENV["DATABASE_USERNAME"] %>
  password: <%= ENV["DATABASE_PASSWORD"] %>
  host: <%= ENV["DATABASE_HOST"] %>
  port: <%= ENV["DATABASE_PORT"] %>
  pool: <%= ENV["RAILS_MAX_THREADS"] || 5 %>

development:
  primary:
    <<: *default
    database: app_development
  primary_replica:
    <<: *default
    database: app_development
    replica: true

production:
  primary:
    <<: *default
  primary_replica:
    <<: *default
    host: <%= ENV["DATABASE_READER_HOST"] %>
    replica: true

2. 読み書き分離モジュール

DbReadWriteSplitableモジュールを作成して、読み書きを分離する機能を提供します。

# app/models/concerns/db_read_write_splitable.rb
module DbReadWriteSplitable
  extend ActiveSupport::Concern

  class_methods do
    def write(retry_count: 0, &block)
      ActiveRecord::Base.connected_to(role: :writing, &block)
    end

    def readonly(retry_count: 0, &block)
      ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true, &block)
    rescue ActiveRecord::StatementInvalid => e
      # リーダーのスケールインによって接続エラーが発生した場合にコネクションをクリアして再リトライする
      raise e if retry_count >= 5
      if e.message =~ /MySQL server has gone away/i
        ActiveRecord::Base.clear_active_connections!
        readonly(retry_count: retry_count + 1, &block)
      else
        raise e
      end
    end
  end

  def write(...)
    self.class.write(...)
  end

  def readonly(...)
    self.class.readonly(...)
  end
end

3. ApplicationRecord での利用

ApplicationRecordでこのモジュールを include し、データベース接続を設定します。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  include DbReadWriteSplitable

  self.abstract_class = true

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

4. 使用方法

読み取り専用クエリ(リードレプリカ使用)

# 重い集計クエリをリードレプリカで実行
User.readonly do
  User.joins(:orders)
      .group(:id)
      .select('users.*, COUNT(orders.id) as order_count')
      .order('order_count DESC')
      .limit(100)
end

# インスタンスメソッドからも利用可能
user.readonly do
  user.orders.sum(:amount)
end

書き込みクエリ(プライマリ DB 使用)

# データの更新はプライマリDBで実行
User.write do
  user.update!(name: 'New Name')
end

5. スケールイン対応の仕組み

問題の背景

AWS RDS のリードレプリカは負荷に応じて自動的にスケールイン/アウトします。スケールイン時に既存の接続が切断され、MySQL server has gone awayエラーが発生します。

解決方法

DbReadWriteSplitableモジュールでは以下の仕組みでスケールインに対応しています:

  1. エラーハンドリング: ActiveRecord::StatementInvalidをキャッチ
  2. エラー判定: エラーメッセージにMySQL server has gone awayが含まれているかチェック
  3. 接続クリア: ActiveRecord::Base.clear_active_connections!で接続をクリア
  4. 再試行: 最大 5 回まで自動的に再試行
rescue ActiveRecord::StatementInvalid => e
  raise e if retry_count >= 5
  if e.message =~ /MySQL server has gone away/i
    ActiveRecord::Base.clear_active_connections!
    readonly(retry_count: retry_count + 1, &block)
  else
    raise e
  end

6. 環境変数の設定

本番環境では以下の環境変数を設定します:

# プライマリDB
DATABASE_HOST=your-primary-db-host
DATABASE_NAME=your_database_name
DATABASE_USERNAME=your_username
DATABASE_PASSWORD=your_password

# リードレプリカ
DATABASE_READER_HOST=your-reader-db-host

7. 注意点

データの整合性

  • リードレプリカは非同期でデータを複製するため、最新のデータが反映されていない可能性があります
  • リアルタイム性が重要な処理では、プライマリ DB を使用してください

接続プール

  • 読み書きで別々の接続プールを使用するため、接続数の設定に注意してください
  • pool設定を適切に調整して、リソースの無駄遣いを避けてください

エラーハンドリング

  • スケールイン時の再試行回数(デフォルト 5 回)は、アプリケーションの要件に応じて調整してください
  • 他のデータベースエラーと区別するため、エラーメッセージの判定条件を適切に設定してください

まとめ

この実装により、以下のメリットが得られます:

  1. パフォーマンス向上: 重い読み取りクエリをリードレプリカに分散
  2. 可用性向上: スケールイン時の自動再接続でサービス継続
  3. 運用負荷軽減: 手動での接続管理が不要

RDS のリードレプリカを活用することで、データベースの負荷分散と可用性の向上を実現できます。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?