Rails Multibase and Models

Rails 6.1 предлагает возможность использования одновременно нескольких баз данных. Для чего?

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 приложение по новому.

Официальная документация