Rails 6.1 adds the ability to switch a role or shard for an application with multiple databases. This means it is possible to switch connections for one database instead of all databases globally.
To use this feature, we need to set the below config in our application.
config.active_record.legacy_connection_handling = falseLet’s say we have two databases, primary and vehicles.
And we have shards and replica configured for each of them in database.yml as below:
production:
primary:
database: primary_database
adapter: mysql
primary_replica:
database: primary_database
adapter: mysql
replica: true
primary_shard_one:
database: primary_shard_one
adapter: mysql
primary_shard_one_replica:
database: primary_shard_one
adapter: mysql
replica: true
vehicles:
database: vehicles_database
adapter: mysql
vehicles_replica:
database: vehicles_database
adapter: mysql
replica: true
vehicles_shard_one:
database: vehicles_shard_one
adapter: mysql
vehicles_shard_one_replica:
database: vehicles_shard_one
adapter: mysql
replica: trueWe have two models User and Car. User is stored in the primary database and
Car in the vehicles database.
The corresponding model and abstract classes will look as below:
# primary database
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
default: { writing: :primary, reading: :primary_replica },
shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica }
}
end
# User class
class User < ApplicationRecord
end
# vehicles database
class VehiclesRecord < ApplicationRecord
self.abstract_class = true
connects_to shards: {
default: { writing: :vehicles, reading: :vehicles_replica },
shard_one: { writing: :vehicles_shard_one, reading: :vehicles_shard_one_replica }
}
end
# Car class
class Car < VehiclesRecord
endWhen legacy_connection_handling is set to false, any abstract connection class will
be able to switch connections without affecting other connections.
Before
ActiveRecord::Base.connected_to(role: :reading) do
User.first # Reads from primary replica
Car.first # Reads from vehicles replica
VehiclesRecord.connected_to(role: :reading, shard: :shard_one) do
User.first # Reads from shard_one_replica of primary database
Car.first # Reads from shard_one_replica of vehicles database
end
ApplicationRecord.connected_to(role: :reading, shard: :shard_one) do
User.first # Reads from shard_one_replica of primary database
Car.first # Reads from shard_one_replica of vehicles database
end
endIn both the above VehiclesRecord.connected_to and ApplicationRecord.connected_to
blocks the connection was changed to shard_one of the corresponding database.
After
ActiveRecord::Base.connected_to(role: :reading) do
User.first # Reads from primary replica
Car.first # Reads from vehicles replica
VehiclesRecord.connected_to(role: :reading, shard: :shard_one) do
User.first # Reads from primary replica
Car.first # Reads from shard_one_replica of vehicles database
end
ApplicationRecord.connected_to(role: :reading, shard: :shard_one) do
User.first # Reads from primary_shard_one_replica
Car.first # Reads from vehicles_primary
end
endAs seen above, when we connect VehiclesRecord to shard_one only Car queries
are executed on shard_one whereas User queries are executed on primary
database.
Summary
The addition of this granular switching is useful in cases like:
- Suppose
replicaorshard_oneis not configured forprimarydatabase. A connection is made toshard_oneandUserqueries are executed under theshard_oneblock, then an error would be thrown. This is because the connection was changed globally across all databases. - If two shards
shard_oneandshard_twoare configured for both databases,user_aexists onshard_oneanduser_bonshard_two. IfVehiclesRecordconnection is changed toshard_twoand we query foruser_aunder that block it will return incorrect result.
