Rails Multibase and Models
Rails 6.1 предлагает возможность использования одновременно нескольких баз данных. Для чего? Во первых можно распределить нагрузку и использовать одну из баз только для чтения, другую только на запись, например для админки. Конечно можно использовать разные типы баз данных в одном приложении. Это открывает еще больше возможностей, например для объединения или миграции данных из разных версий приложения.
Первое с чего начинается настройка - config/database.yml
default: &default
adapter: sqlite3
pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
timeout: 5000
development:
primary:
adapter: postgresql
encoding: unicode
host: 127.0.0.1
port: 5432
username: postgres
password: po$tgr$s
pool: <%= ENV.fetch('RAILS_PG_MAX_THREADS') { 5 } %>
database: primary_database
primary_replica:
adapater: postgresql
encoding: unicode
host: 127.0.0.1
port: 5432
username: replica_readonly
password: p@$tgr$s
pool: 5
database: primary_database
replica: true
secondary:
adapter: mysql2
pool: <%= ENV.fetch('RAILS_MY_MAX_THREADS') { 5 } %>
encoding: utf8
username: root
password: r@@t
host: 127.0.0.1
port: 3306
database: secondary_database
migration_paths: db/secondary_migrate
lite:
adapter: sqlite3
pool: <%= ENV.fetch('RAILS_LT_MAX_THREADS') { 5 } %>
database: db/development-lite.sqlite3
migration_paths: db/lite_migrate
timeout: 5000
test:
<<: *default
database: db/test.sqlite3
Для каждого типа данных потребуется установить отдельные пакеты
bundle add sqlite3
bundle add pg
bundle add mysql2
gem 'sqlite3', '~> 1.4'
gem 'pg', '>= 0.18', '< 2.0'
gem 'mysql2', '~> 0.5.3'
Реплицированные базы данных, конечно, должны быть одинаковыми. Пользователь для записи и чтения должен быть разным. Для реплики надо установить replica: true
.
Для хранения миграций для каждой базы отдельно используется migration_paths
. Миграции для отдельных баз должны сохранятся в отдельных директориях с префиксом, соответствующим имени подключения. Необходимо указать в конфигурации путь к миграциям.
primary:
adapter: postgresql
database: primary_database
migration_path: db/primary_migration
Для использования разных баз требуется создать отдельный абстрактный класс для каждой базы, который будет основной моделей:
class PrimaryRecord < ActiveRecord::Base
self.abstract_class = true
connect_to database: { writing: :primary, reading: :primary }
end
Для ApplicationRecord
это может быть
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connect_to database: { writing: :primary, reading: :primary_replica}
end
Это важно, чтобы подключение к базе определено в одной модели и затем наследовалось остальными. Клиент баз данных имеют ограничения количество одновременных подключений и если подключение производить в каждой модели/классе, то лимит будет быстро исчерпан.
После создания конфигурации станут доступны дополнительные команды миграции:
# rails db:<cmd>:<database>
rails db:create:primary
rails db:migrate:primary
rails db:migrate:seconadry
rails db:migrate:lite
# ...
Для генератора миграций указывается --database=
rails g migration Post title:string text:text --database=primary
Если абстрактного класса нет, он также будет создан. Можно указать доп. --parent=
, если абстрактный класс уже существует и его имя не соответствует правилам именования.
Для использования реплики только для чтения надо включить автоматическое переключение. В зависимости от HTTP команды будет выбираться необходимая база. Когда приложение получает POST
, PUT
, DELETE
, PATCH
запрос то выбирается база для записи, а для GET
или HEAD
реплика.
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
Использование нескольких баз данных в приложение это очень мощная возможность, возможность взглянуть на Rails приложение по новому.