Rails Cache Key生成为ActiveRecord :: Relation
我正在尝试生成一个片段缓存(使用Dalli / Memcached存储)但是密钥是使用“#”生成的,因为Rails似乎没有认识到存在缓存值并且正在命中数据库。
我在视图中的缓存键如下所示:
cache([@jobs, "index"]) do
控制器有:
@jobs = @current_tenant.active_jobs
使用这样的实际Active Record查询:
def active_jobs self.jobs.where("published = ? and expiration_date >= ?", true, Date.today).order("(featured and created_at > now() - interval '" + self.pinned_time_limit.to_s + " days') desc nulls last, created_at desc") end
查看rails服务器,我看到缓存读取,但SQL查询仍然运行:
Cache read: views/#/1-index Read fragment views/#/1-index (1.0ms) (0.6ms) SELECT COUNT(*) FROM "jobs" WHERE "jobs"."tenant_id" = 1 AND (published = 't' and expiration_date >= '2013-03-03') Job Load (1.2ms) SELECT "jobs".* FROM "jobs" WHERE "jobs"."tenant_id" = 1 AND (published = 't' and expiration_date >= '2013-03-03') ORDER BY (featured and created_at > now() - interval '7 days') desc nulls last, created_at desc
关于我可能做错什么的任何想法? 我确定它必须与密钥生成和ActiveRecord :: Relation进行,但我不确定如何。
我遇到过类似的问题,我无法成功地将关系传递给缓存函数,而@jobs变量是一种关系。
我编写了一个解决缓存密钥的解决方案来处理这个问题以及我正在处理的其他问题。 它主要涉及通过迭代关系生成缓存键。
我的网站上有完整的文章。
http://mark.stratmann.me/content_items/rails-caching-strategy-using-key-based-approach
总之,我在ActiveRecord :: Base中添加了一个get_cache_keys函数
module CacheKeys extend ActiveSupport::Concern # Instance Methods def get_cache_key(prefix=nil) cache_key = [] cache_key << prefix if prefix cache_key << self self.class.get_cache_key_children.each do |child| if child.macro == :has_many self.send(child.name).all.each do |child_record| cache_key << child_record.get_cache_key end end if child.macro == :belongs_to cache_key << self.send(child.name).get_cache_key end end return cache_key.flatten end # Class Methods module ClassMethods def cache_key_children(*args) @v_cache_key_children = [] # validate the children args.each do |child| #is it an association association = reflect_on_association(child) if association == nil raise "#{child} is not an association!" end @v_cache_key_children << association end end def get_cache_key_children return @v_cache_key_children ||= [] end end end # include the extension ActiveRecord::Base.send(:include, CacheKeys)
我现在可以通过这样做来创建缓存片段
cache(@model.get_cache_key(['textlabel'])) do
背景:
问题是每次运行代码时关系的字符串表示forms都不同:
|This changes| views/#/...
因此,每次都会获得不同的缓存密钥。
除此之外,不可能完全摆脱数据库查询。 (你自己的答案是最好的答案 )
解:
生成有效密钥,而不是此
cache([@jobs, "index"])
做这个:
cache([@jobs.to_a, "index"])
这将查询数据库并构建模型数组,从中检索cache_key
。
PS:我可以发誓使用以前版本的Rails中的关系……
我们一直在做你在生产中提到的大约一年。 几个月前我把它提取成了一颗gem:
https://github.com/cmer/scope_cache_key
基本上,它允许您将范围用作缓存键的一部分。 这样做有很大的性能优势,因为您现在可以将包含多个记录的页面缓存在单个缓存元素中,而不是循环范围中的每个元素并单独检索缓存。 我认为将此与标准的“俄罗斯娃娃缓存”原则相结合是最佳选择。
虽然我将@ mark-stratmann的回答标记为正确,但我实际上是通过简化实现来解决这个问题。 我添加了touch:true给我的模型关系声明:
belongs_to :tenant, touch: true
然后根据租户设置缓存密钥(还有一个必需的查询参数):
<% cache([@current_tenant, params[:query], "#{@current_tenant.id}-index"]) do %>
这样,如果添加新作业,它也会触及租户缓存。 不确定这是否是最好的路线,但它的工作原理似乎非常简单。
我用这个代码:
class ActiveRecord::Base def self.cache_key pluck("concat_ws('/', '#{table_name}', group_concat(#{table_name}.id), date_format(max(#{table_name}.updated_at), '%Y%m%d%H%i%s'))").first end def self.updated_at maximum(:updated_at) end end
我做过类似Hopsoft的事情,但它使用Rails指南中的方法作为模板。 我已经使用MD5摘要来区分关系(因此可以将User.deactivated.cache_key
与User.deactivated.cache_key
),并使用count和max updated_at
在关系更新时自动使缓存失效。
require "digest/md5" module RelationCacheKey def cache_key model_identifier = name.underscore.pluralize relation_identifier = Digest::MD5.hexdigest(to_sql.downcase) max_updated_at = maximum(:updated_at).try(:utc).try(:to_s, :number) "#{model_identifier}/#{relation_identifier}-#{count}-#{max_updated_at}" end end ActiveRecord::Relation.send :include, RelationCacheKey
也许这可以帮助你解决https://github.com/casiodk/class_cacher ,它从模型本身生成一个cache_key,但也许你可以使用代码库中的一些原则
作为起点,您可以尝试这样的事情:
def self.cache_key ["#{model_name.cache_key}-all", "#{count}-#{updated_at.utc.to_s(cache_timestamp_format) rescue 'empty'}" ] * '/' end def self.updated_at maximum :updated_at end
我有一个规范化的数据库,其中多个模型与同一个其他模型相关,想到客户端,位置等都通过street_id拥有地址。
使用此解决方案,您可以根据范围生成cache_keys,例如
cache [@client, @client.locations] do # ... end cache [@client, @client.locations.active, 'active'] do # ... end
我可以简单地从上面修改self.updated
以包含相关对象(因为has_many
不支持“touch”,所以如果我更新了街道,否则缓存不会看到它):
belongs_to :street def cache_key [street.cache_key, super] * '/' end # ... def self.updated_at [maximum(:updated_at), joins(:street).maximum('streets.updated_at') ].max end
只要你没有“取消删除”记录并在belongs_to中使用touch,你就可以假设由count和max updated_at组成的缓存键就足够了。
我在ActiveRecord :: Relation上使用一个简单的补丁来生成关系的缓存键。
require "digest/md5" module RelationCacheKey def cache_key Digest::MD5.hexdigest to_sql.downcase end end ActiveRecord::Relation.send :include, RelationCacheKey