Запутывание кэширования запросов Active Record с помощью Rails.cache.fetch

Моя версия:

  • Rails: 3.2.6
  • dalli: 2.1.0

Моя env:

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

Когда я пишу:

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

Пользовательскую модель нельзя кэшировать. Если я использую

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

он может быть кэширован. Как кэшировать результат запроса?

Ответ 1

Причина в том, что

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

возвращает ActiveRecord::Relation, который на самом деле является областью, а не запросом. Рельсы кэшируют область действия.

Если вы хотите кэшировать запрос, вам нужно использовать метод запроса в конце, например #all.

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

Обратите внимание, что никогда не рекомендуется кэшировать объекты ActiveRecord. Кэширование объекта может привести к несогласованным состояниям и значениям. Вы всегда должны кэшировать примитивные объекты, когда это применимо. В этом случае рассмотрим кэширование идентификаторов.

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

Вы можете утверждать, что в этом случае вызов User.find всегда выполняется. Это правда, но запрос с использованием первичного ключа выполняется быстро, и вы обошли проблему, о которой я говорил ранее. Более того, кэширование активных объектов записи может быть дорогостоящим, и вы можете быстро завершить заполнение всей памяти Memcached только одной записью одного кэша. Кэширование ids также предотвратит эту проблему.

Ответ 2

В дополнение к выбранному ответу: для Rails 4+ вы должны использовать load вместо all для получения результата области.

Ответ 3

Rails.cache.fetch кэширует то, что блок оценивает.

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

Является просто областью, поэтому кэширование - это только объект ActiveRecord:: Relation, то есть запрос, но не его результаты (потому что запрос еще не выполнен).

Если вам нужно что-то полезное для кэширования, вам нужно принудительно выполнить выполнение запроса внутри блока, например, выполнив

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

Обратите внимание, что на рельсах 4 all не принудительно загружает отношение - используйте to_a вместо

Ответ 4

Использование

User.where("status = 1").limit(1000).all

должен работать.