/rails6-multidb-sample

Rails 6.0 の DatabaseSelector middleware を理解する

Primary LanguageRuby

rails6-multidb-sample

次のことを確認するためのサンプルアプリケーションです。

  • PostgreSQL の非同期レプリケーション
  • Rails 6.0 の Multiple DBs 機能
  • DatabaseSelector を使った HTTP Method による接続先自動切り替え

試す

$ docker-compose up
$ docker-compose exec app bundle exec rails db:setup

Open http://localhost:3000/posts

PostgreSQL の非同期レプリケーション

Docker Compose を使って、2 つの PostgreSQL コンテナを立ち上げます。 一方をレプリケーションの parimary に、もう一方を readonly (Hot Standby) として使います。

Rails 6.0 の Multiple DBs 機能

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

次のようにレプリケーションの parimary (読み書き可能)と readonly (読み込み専用)を host で指定します。 YAML 中の primary primary_readonly は後で参照するのに使う識別子で任意に設定可能です。

development:
  primary:
    <<: *default
    database: MyApp_development
    host: pg_primary
  primary_readonly:
    <<: *default
    database: MyApp_development
    host: pg_readonly
    replica: true   # この接続はレプリカであることRailsに伝える
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  connects_to database: { writing: :primary, reading: :primary_readonly }
end
ActiveRecord::Base.connected_to(role: :writing) do
  # このブロック内のコードはすべて writing ロールで接続される
  ActiveRecord::Base.current_role #=> :writing
end

ActiveRecord::Base.connected_to(role: :reading) do
  # このブロック内のコードはすべて reading ロールで接続される
  ActiveRecord::Base.current_role #=> :reading
end

DatabaseSelector を使った HTTP Method による接続先自動切り替え

config/environments/development.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

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

試してみる

http://localhost:3000/posts で記事を登録したり、表示したりすると次のようなログが表示され、どちらのデータベースにクエリが送信されたか確認できます。

app_1          | Started PATCH "/posts/1" for 172.21.0.1 at 2019-10-03 18:37:22 +0000
app_1          | Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
pg_primary_1   | 2019-10-03 18:37:22.227 UTC [66] LOG:  statement: SELECT 1
app_1          | Processing by PostsController#update as HTML
app_1          |   Parameters: {"authenticity_token"=>"tJBhu5R3jC6vI+SubacKMK4bAyLUi62Tq5GkXpI/jhwPPLlpzC546Nwmaurjgp45kFieKwScCboGzkXgsC71cw==", "post"=>{"title"=>"Hello world!!", "body"=>"one two three"}, "commit"=>"Update Post", "id"=>"1"}
pg_primary_1   | 2019-10-03 18:37:22.229 UTC [66] LOG:  statement: SHOW search_path
pg_primary_1   | 2019-10-03 18:37:22.230 UTC [66] LOG:  execute a1: SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2
pg_primary_1   | 2019-10-03 18:37:22.230 UTC [66] DETAIL:  parameters: $1 = '1', $2 = '1'
app_1          |   Post Load (0.4ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
app_1          |   ↳ app/controllers/posts_controller.rb:67:in `set_post'
app_1          | Redirected to http://localhost:3000/posts/1
app_1          | Completed 302 Found in 4ms (ActiveRecord: 0.8ms | Allocations: 1373)
app_1          |
app_1          |
app_1          | Started GET "/posts/1" for 172.21.0.1 at 2019-10-03 18:37:22 +0000
app_1          | Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
pg_primary_1   | 2019-10-03 18:37:22.237 UTC [66] LOG:  statement: SELECT 1
app_1          | Processing by PostsController#show as HTML
app_1          |   Parameters: {"id"=>"1"}
pg_primary_1   | 2019-10-03 18:37:22.238 UTC [66] LOG:  execute a1: SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2
pg_primary_1   | 2019-10-03 18:37:22.238 UTC [66] DETAIL:  parameters: $1 = '1', $2 = '1'
app_1          |   Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
app_1          |   ↳ app/controllers/posts_controller.rb:67:in `set_post'
app_1          |   Rendering posts/show.html.erb within layouts/application
app_1          |   Rendered posts/show.html.erb within layouts/application (Duration: 0.2ms | Allocations: 95)
app_1          | Completed 200 OK in 6ms (Views: 4.7ms | ActiveRecord: 0.3ms | Allocations: 6369)
app_1          |
app_1          |
app_1          | Started GET "/posts/1" for 172.21.0.1 at 2019-10-03 18:37:36 +0000
app_1          | Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
pg_primary_1   | 2019-10-03 18:37:36.607 UTC [66] LOG:  statement: SELECT 1
app_1          | Processing by PostsController#show as HTML
app_1          |   Parameters: {"id"=>"1"}
pg_readonly_1  | 2019-10-03 18:37:36.609 UTC [30] LOG:  statement: SELECT 1
pg_readonly_1  | 2019-10-03 18:37:36.609 UTC [30] LOG:  execute a1: SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2
pg_readonly_1  | 2019-10-03 18:37:36.609 UTC [30] DETAIL:  parameters: $1 = '1', $2 = '1'
app_1          |   Post Load (0.5ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
app_1          |   ↳ app/controllers/posts_controller.rb:67:in `set_post'
app_1          |   Rendering posts/show.html.erb within layouts/application
app_1          |   Rendered posts/show.html.erb within layouts/application (Duration: 0.2ms | Allocations: 92)
app_1          | Completed 200 OK in 8ms (Views: 5.3ms | ActiveRecord: 0.5ms | Allocations: 6356)