ankane / distribute_reads

Scale database reads to replicas in Rails

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

disable `Call 'to_a' inside block ...` warning

Yaroslav-F opened this issue · comments

Hello,

I'm using distribute_reads { ... } to force AR to use read replica.
I use .reload to ensure queries go to read replica and result is still an AR relation, not an Array. The issue is that I can't get rid of these warnings:

[distribute_reads] Call `to_a` inside block to execute query on replica

Would it make sense to suppress these warnings when .reload or .load is used?

Hope it makes sense.
Thanks

Hey @Yaroslav-F, not sure I follow what you're trying to do. Can you give an example?

Hi @ankane ! Sure, so here's an example query:

irb(main):008:0> admin_users = distribute_reads { AdminUser.where(email: 'test@test.com') }
[Readreplica] SQL (0.4ms)  SHOW SLAVE STATUS
[distribute_reads] Call `to_a` inside block to execute query on replica
[MasterNode] AdminUser Load (1.3ms)  SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`email` = 'test@test.com'

I can see a warning from the gem saying that I need to use to_a to avoid lazy loading. Fair enough!
Adding to_a obviously fixes the warning and makes the request go to read replica, perfect.

irb(main):009:0> admin_users = distribute_reads { AdminUser.where(email: 'test@test.com').to_a }
[Readreplica] SQL (0.3ms)  SHOW SLAVE STATUS
[Readreplica] AdminUser Load (0.4ms)  SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`email` = 'test@test.com'

However the problem is that now admin_users is an array object, not ActiveRecord::Relation. So, if I need to execute some queries on this object later - it will obviously break:

irb(main):007:0> admin_users.group(:email).count
NoMethodError: undefined method `group' for #<Array:0x007fbf4fdbdd80>

Fixing this issue is a piece of cake, I just need to call .load or .reload instead of .to_a on the query. It will do pretty much the same thing but return an ActiveRecord::Relation, which is perfect.
So here we go:

irb(main):008:0> admin_users = distribute_reads { AdminUser.where(email: 'test@test.com').load }
[Readreplica] SQL (0.3ms)  SHOW SLAVE STATUS
[Readreplica] AdminUser Load (0.4ms)  SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`email` = 'test@test.com'
[distribute_reads] Call `to_a` inside block to execute query on replica

irb(main):009:0> admin_users.group(:email).count
[2018-11-13 14:25:16]   [Readreplica] (0.5ms)  SELECT COUNT(*) AS count_all, `admin_users`.`email` AS admin_users_email FROM `admin_users` WHERE `admin_users`.`email` = 'test@test.com' GROUP BY `admin_users`.`email`
=> {"test@test.com"=>1}

Nice, it works, but there's this warning from distribute_reads telling me to call to_a even though .load did the job perfectly.
This gets worse when running test suite and getting tons of these warnings everywhere.

So the question is - can this warning be disabled when .load or .reload is used?

Hope it makes sense, but should you have any questions - please don't hesitate to ask.

Thanks!

Hey @Yaroslav-F, do you have DistributeReads.by_default = true or nested distribute_reads blocks in your last example? If not, the last query (count) should go to the primary.

The short answer is it can't be disabled by design. In your case, you'd want to do:

users_relation = AdminUser.where(email: "test@test.com")
admin_users = distribute_reads { users_relation.to_a }
count = distribute_reads { users_relation.group(:email).count }

or

users_relation = AdminUser.where(email: "test@test.com")
distribute_reads do
  admin_users = users_relation.to_a
  count = users_relation.group(:email).count
end

ah, the sneaky by_default, my bad, never mind

Thanks!