次のことを確認するためのサンプルアプリケーションです。
- PostgreSQL の非同期レプリケーション
- Rails 6.0 の Multiple DBs 機能
- DatabaseSelector を使った HTTP Method による接続先自動切り替え
$ docker-compose up$ docker-compose exec app bundle exec rails db:setupOpen http://localhost:3000/posts
Docker Compose を使って、2 つの PostgreSQL コンテナを立ち上げます。 一方をレプリケーションの parimary に、もう一方を readonly (Hot Standby) として使います。
次のようにレプリケーションの 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 }
endActiveRecord::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
endconfig/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)