Picture of the author

GAURAV VARMA


Rails 6 brings first-class support for multiple databases, making it much easier to manage complex applications that need read/write separation, replica fallbacks, or domain-specific sharding — without relying on third-party gems.

Key Features

  • Connect to multiple primary and replica databases
  • Built-in connection switching middleware
  • Independent migrations and schema tracking
  • Out-of-the-box Rails tasks for each database

Setting Up database.yml

Here’s an example configuration with a primary DB and an animals DB with replicas:

1production:
2  primary:
3    database: my_primary_database
4    adapter: mysql
5    user: root
6  primary_replica:
7    database: my_primary_database
8    adapter: mysql
9    user: readonly_user
10    replica: true
11
12  animals:
13    database: animals_db
14    adapter: mysql
15    user: animals_user
16    migrations_paths: db/animals_migrate
17  animals_replica:
18    database: animals_db
19    adapter: mysql
20    user: animals_readonly
21    replica: true

A few important things to note:

  • Replicas must set replica: true
  • Use different users for primaries and replicas
  • Use migrations_paths to isolate migrations per DB

Connecting Models to Databases

Create an abstract base model for each custom DB:

1class AnimalsBase < ApplicationRecord
2  self.abstract_class = true
3
4  connects_to database: { writing: :animals, reading: :animals_replica }
5end

For your default models:

1class ApplicationRecord < ActiveRecord::Base
2  self.abstract_class = true
3
4  connects_to database: { writing: :primary, reading: :primary_replica }
5end

If you're using legacy roles like :readonly, you can override them in your config:

1config.active_record.writing_role = :default
2config.active_record.reading_role = :readonly

Rails Tasks for Multi-DB

Rails ships with namespaced commands for each database:

1rails db:create
2rails db:create:animals
3rails db:migrate
4rails db:migrate:animals
5rails db:migrate:status:animals

Each database uses its own migration directory. Use --database when generating migrations:

1rails g migration CreateDogs name:string --database animals

Automatic Connection Switching

Rails includes middleware that automatically routes reads/writes:

  • Writes (POST/PUT/DELETE) use the primary
  • Reads (GET/HEAD) use the replica, unless a recent write occurred

Enable it in application.rb:

1config.active_record.database_selector = { delay: 2.seconds }
2config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
3config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session

You can even write your own resolver if you want to control switching based on cookies, headers, etc.

Manual Connection Switching

Use connected_to if you want explicit control:

1ActiveRecord::Base.connected_to(role: :reading) do
2  # Read-only logic here
3end

Passing an unknown role will raise an error, so make sure it matches your config.

Caveats

  • ❌ No native sharding support yet
  • 🔀 No built-in replica load balancing
  • 🚫 No cross-database joins
  • 📦 You’ll need to manually load schema caches for each DB

Links

Summary

Rails 6 makes scaling your app’s data layer far more manageable with built-in multi-DB tools. Whether you're separating concerns across domains or using replicas to reduce load, Rails now gives you a clean and consistent interface to do it all natively.