作为ActiveRecord生成的Rails缓存键::关系

问题描述:

我正在尝试生成一个片段缓存(使用Dalli/Memcached存储),但是使用“#”作为键的一部分生成密钥,所以Rails似乎没有要认识到有一个缓存值并正在击中数据库。作为ActiveRecord生成的Rails缓存键::关系

在查看我的缓存键看起来是这样的:

cache([@jobs, "index"]) do 

该控制器具有:

@jobs = @current_tenant.active_jobs 

通过这样的实际活动记录查询:

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/#<ActiveRecord::Relation:0x007fbabef9cd58>/1-index 
Read fragment views/#<ActiveRecord::Relation:0x007fbabef9cd58>/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 ::关系,但我不知道如何。

我有类似的问题,我还没有能够成功地将关系传递给缓存函数,您的@jobs变量是一个关系。

我编写了一个缓存键的解决方案,处理这个问题以及我正在处理的其他一些问题。它基本上涉及通过迭代关系来生成缓存键。

一个完整的写在我的网站在这里。

http://mark.stratmann.me/content_items/rails-caching-strategy-using-key-based-approach

总之我添加了一个get_cache_keys功能的ActiveRecord :: Base的

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 
+3

该链接返回一个404 ... – 2014-02-16 13:29:33

创建缓存片段虽然我标志着@标记施特拉特曼我的答复是正确的,我实际上是通过简化实现来解决这个问题的。我加触摸:忠于我的模型关系声明:

belongs_to :tenant, touch: true 

,然后设置缓存键根据承租人(有需要的查询参数以及):

<% cache([@current_tenant, params[:query], "#{@current_tenant.id}-index"]) do %> 

这样,如果新作业已添加,它也触及Tenant缓存。不知道这是不是最好的路线,但它的工作原理似乎很简单。

背景:

问题是关系的字符串表示是不同的每个代码运行时间:

        |This changes| 
views/#<ActiveRecord::Relation:0x007fbabef9cd58>/... 

所以你每次不同的缓存键。

此外,它是而不是可能完全摆脱数据库查询。(您own answer是最好的,可以做)

解决方案:

生成有效的关键,而不是此

cache([@jobs, "index"]) 

做到这一点:

cache([@jobs.to_a, "index"]) 

此查询数据库并构建一个模型数组,从中检索cache_key

PS:我可以发誓使用关系Rails中的早期版本中的工作......

+0

我不太确定如果模型中的某个字段是十进制的,那么这将起作用 – bcackerman 2014-02-27 16:12:54

+0

@bcackerman:这没什么区别。默认情况下,Rails使用'updated_at'和'id' * only *来生成缓存键。 – 2015-06-15 10:48:21

我们一直在做你正在生产提什么了大约一年。我是解压到一个宝石在几个月前:

https://github.com/cmer/scope_cache_key

基本上,它可以让你用一个范围作为缓存键的一部分。这样做有显着的性能优势,因为现在可以将包含多个记录的页面缓存在单个缓存元素中,而不是遍历该作用域中的每个元素并分别检索缓存。我觉得把它与标准的“俄罗斯娃娃缓存”原则结合起来是最佳的。

也许这可以帮助你 https://github.com/casiodk/class_cacher,它产生从模型本身cache_key,但也许你可以在代码库使用此代码中使用的一些原则

林:

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 

我在ActiveRecord :: Relation上使用一个简单的补丁来为关系生成缓存键。

require "digest/md5" 

module RelationCacheKey 
    def cache_key 
    Digest::MD5.hexdigest to_sql.downcase 
    end 
end 

ActiveRecord::Relation.send :include, RelationCacheKey 
+0

由于SQL本身被用作缓存键,所以如果更新任何模型,这将永不过期。 – fivedigit 2014-07-13 20:05:39

我已经做了类似Hopsoft,但它使用的Rails Guide方法作为模板。我使用MD5摘要来区分关系(因此User.active.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 

以此为起点,你可以尝试这样的事:

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不支持“触摸”,所以如果我更新了街道,也不会被高速缓存,否则看到的):

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中使用触摸,假设由count和max updated_at组成的缓存键就足够了,那么应该没问题。