Automatically switching database connections when writing

Hey, everyone!

Automatically switching database connections has been discussed before, including on @eileencodes’s PR that added the API for connection switching. That said, Rails’ multiple database logic has been thoroughly battle tested since then and we wanted to see if there’s a new appetite for this discussion.

At Zendesk we think there’s a use case for an opt-in option for Rails to automatically switch to the writing role when Rails knows the query is a write. Currently, the default role in ActiveRecord is the :writing role. This allows for you to read (Foo.first) and write (Foo.create!) outside of a connected_to(...) block.

However, Rails allows you to change the default role for a connection class and doing so can be quite beneficial for multiple database setups. Let’s say you have one Foo writer database instance and many Foo replica instances that are distributed closer to the end users. Defaulting to the writer database degrades performance: all traffic gets routed to the single database instance rather than distributed to the nearest replica; the request transit time usually takes longer because the end user typically has a shorter hop to a replica database, not the writer.

If we instead change the default_role we can still call Foo.first but Foo.create! will raise an error unless it’s inside a .connected_to(role: :writing) block. Here’s a gist that demonstrates this behavior.

Ideally, Rails would understand that Foo.create! is a write query and, because our default role is :reading, switch to the writer database for us and seamlessly create the record. This is something we’re thinking of implementing internally and, if it’s of interest, trying to upstream.

We’re unsure of the best approach. The one thing we’re certain of: due to the unexpected (and difficult to debug) issues that arise from replication lag, any automatic database switching should be opt-in.

In the gist above, Rails raises an ActiveRecord::ReadOnly error when you attempt to write to a replica database, implying that Rails already knows if a query is a write or not. Should we hook into that logic (#check_if_write_query) to switch connections if necessary? It’s quite far down in the request cycle though and may not be the ideal place to switch connections.

In the previous discussion, @madbomber used ActiveRecord callbacks to switch connections but what about queries that skip callbacks altogether?

The ActiveRecord::Middleware::DatabaseSelector::Resolver can switch connections based on the HTTP verb but some apps violate this by writing during GET requests.

We’re very interested in any thoughts folks might have. Thank you!

3 Likes

Yes totally this would be great.

We were previously using Makara until it broke with ActiveRecord 7.1 (and no longer supported). This is why we at Olio have created janus GitHub - OLIOEX/janus-ar: A read/write ActiveRecord database adapter. Its been in production for us for several months and working great!

If we could use built in Rails switching that would be less code to maintain for us :slight_smile:

1 Like