Last Updated: February 25, 2016
·
12.39K
· bashir

Bypassing ActiveRecord query cache

ActiveRecord by default caches queries for performance. You might see something like the following the logs:

User Load (84.6ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1

The first sql query actually executes against the db and is cached for the subsequent 4 calls so you only incur one db hit. However sometimes this may not be the results you want. In the example above each time you are expecting a different random user however because the query results were cached you will keep getting the same user (e.g. Joe), rather than (Joe, Jill, Edward, ...)
To get this right, we need to disable caching for this query and it can be done with the uncached scoping:

uncached do
  active.where("badges_count > 1").order("Random()").limit(limit)
end

this will result in the expected 5 individual queries with distinct results:

User Load (84.2ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
  User Load (83.0ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
  User Load (83.8ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
  User Load (83.3ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1
  User Load (83.2ms)  SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND (badges_count > 1) ORDER BY Random() LIMIT 1