Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion caching Active Record queries with Rails.cache.fetch

My version is:

  • Rails: 3.2.6
  • dalli: 2.1.0

My env is:

  • config.action_controller.perform_caching = true
  • config.cache_store = :dalli_store, 'localhost:11211', {:namespace => 'MyNameSpace'}

When I write:

 Rails.cache.fetch(key) do
     User.where('status = 1').limit(1000)
 end

The user model can't be cached. If I use

 Rails.cache.fetch(key) do
     User.all
 end

it can be cached. How to cache query result?

like image 897
tinylian Avatar asked Jun 27 '12 03:06

tinylian


People also ask

How does Rails query cache work?

Query caching is a Rails feature that caches the result set returned by each query. If Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.

Does Rails cache use Redis?

Rails 5.2 introduced built-in Redis cache store, which allows you to store cache entries in Redis.

What is Rails schema cache?

To avoid it, Rails provides with the schema cache feature. The idea is: Serialize data about the schema (tables, columns, and types) into a file. Distribute that file over all application servers. Load the data from file to avoid hitting the database.

What is caching and how it works?

In computing, a cache is a high-speed data storage layer which stores a subset of data, typically transient in nature, so that future requests for that data are served up faster than is possible by accessing the data's primary storage location.


2 Answers

The reason is because

User.where('status = 1').limit(1000)

returns an ActiveRecord::Relation which is actually a scope, not a query. Rails caches the scope.

If you want to cache the query, you need to use a query method at the end, such as #all.

Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).all
end

Please note that it's never a good idea to cache ActiveRecord objects. Caching an object may result in inconsistent states and values. You should always cache primitive objects, when applicable. In this case, consider to cache the ids.

ids = Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).pluck(:id)
end
User.find(ids)

You may argue that in this case a call to User.find it's always executed. It's true, but the query using primary key is fast and you get around the problem I described before. Moreover, caching active record objects can be expensive and you might quickly end up filling all Memcached memory with just one single cache entry. Caching ids will prevent this problem as well.

like image 135
Simone Carletti Avatar answered Oct 05 '22 23:10

Simone Carletti


In addition to selected answer: for Rails 4+ you should use load instead of all for getting the result of scope.

like image 26
Alexander Zinchuk Avatar answered Oct 05 '22 23:10

Alexander Zinchuk