概要
Rails6からマルチデータベースを利用できるようになりました
リードレプリカ構成で負荷をうまく分散させたり、取得するモデル単位でデータベースのコネクションを切り替えることができるようになりました
同一のモデルを取得するけれど、パスによって取得するDBを切り替えることを試したので書き残します
今回試した内容の結論から書くと「concernで無理やり解決した」となります
どうやってDBを切り替えるのか
切り替えたいモデルが継承している抽象クラスにconnects_to database
を記述すれば切り替えができます
下記のような切り替えたいモデルとしてCard
があったとき、それが継承しているApplicationRecord
に対して
writingのDBとreadingのDBを指定すると、必要に応じて切り替わります
class Card < ApplicationRecord
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :default, reading: :default }
end
もう少し手順を1つ1つ確認していくと下記のようなことが必要です
- database.ymlの設定
- 該当のモデル・テーブル
- 該当のモデルが継承している抽象クラスでのDB接続設定(↑で書いてある内容)
database.ymlの設定
database.ymlの設定としては下記の通りです(一部省略)
紛らわしいですが、default配下のdefaultの設定を利用するので今回はkanban_development
のDBへデータの変更削除などを行いに行きます
default:
default:
port: 3306
database: kanban_development
renewal:
port: 3306
database: board_development
migrations_paths: db/board_migrate
該当のモデル・テーブル
これはそのままですが下記のモデルの作成とテーブルを作成する必要があります
class Card < ApplicationRecord
end
ActiveRecord::Schema[7.1].define(version: 2023_11_29_171139) do
create_table "cards", charset: "utf8mb4", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
該当のモデルが継承している抽象クラスでのDB接続設定
↑でも書いていた通り、connects_to database:...
の設定が必要です
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :default, reading: :default }
end
writing: :default
をwriting: :renewal
と変更すると
接続先のdatabaseがkanban_development
→board_development
となります
default:
default:
port: 3306
database: kanban_development
renewal:
port: 3306
database: board_development
migrations_paths: db/board_migrate
実際にパスで切り替えて行くにはどうしたか
- database.ymlの設定と各DBに対して同一のmigrationを作成する
- 異なる抽象クラスを用意する
- 動的にクラスを変更するためcontrollerに対してmoduleを定義
大きく分けてこの3ステップくらいです
サンプルコードはこちらに上げています
database.ymlの設定と各DBに対して同一のmigrationを作成する
database.ymlについては↑でも載せているものを利用します
kanban_developmentのmigraitonはdb/migrate
配下に作成していき
board_developmentのmaigrationはmigrations_pathsで指定してる db/board_migrate
配下へ作成します
default:
default:
port: 3306
database: kanban_development
renewal:
port: 3306
database: board_development
migrations_paths: db/board_migrate
異なる抽象クラスを用意する
接続先の切り替えはモデルが継承している抽象クラスで定義するので異なる抽象クラスとそれぞれを継承したモデルが必要です
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :default, reading: :default }
end
class Card::Content < ApplicationRecord
self.table_name = 'contents'
end
class RenewalRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :renewal }
end
class Post::Content < RenewalRecord
self.table_name = 'contents'
end
動的にクラスを変更するためcontrollerに対してmoduleを定義
moduleとしてモジュールを利用した側での名前を利用して動的にデータを取得するモジュールを作成しました
module ContentsControllerble
extend ActiveSupport::Concern
included do
def index
@contents = content_model.all
end
private
def content_model
namespace = self.class.name.deconstantize
"#{namespace}::Content".constantize
end
end
end
実際にこのモジュールを利用する側はincludeしているだけです
class Post::ContentsController < ApplicationController
include ContentsControllerble
end
class Card::ContentsController < ApplicationController
include ContentsControllerble
end
処理の流れ
-
/post/contents
にアクセスがあった場合- Post::ContentsControllerのindexアクションが実行
- 実態はモジュールにあり、Post::Contentモデルのデータを取得する
- Post::Contentモデルは
RenewalRecord
を継承している-
RenewalRecord
では接続先をrenewal_development
としている
-
-
renewal_development
のcontentsテーブルのデータを取得してくる
-
/card/contents
にアクセスがあった場合- Card::ContentsControllerのindexアクションが実行
- 実態はモジュールにあり、Card::Contentモデルのデータを取得する
- Card::Contentモデルは
ApplicationRecord
を継承している-
ApplicationRecord
では接続先をkanban_development
としている
-
-
kanban_development
のcontentsテーブルのデータを取得してくる
まとめ
サンプルコードを見てもらうとわかりますが、結構モジュールを読み込むだけのファイルができます
冒頭に書いた通りconcernで解決したのであまり良い結果とは言えません...
結局同一のテーブル構造を実現させるなら水平シャーディングを利用すると複数のDBに対して同一のデータが保存できそうです
ただ、その場合は複数DBでテーブル構造を共有するので各DBごとのカスタマイズ性が落ちそう
水平シャーディングについても聞きかじった程度なので見ていきます
参照・引用