4

我正在尝试生成一个片段缓存(使用 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/#<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::Relation,但我不确定如何。

4

9 回答 9

8

背景:

问题是每次运行代码时关系的字符串表示形式都不同:

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

所以你每次都会得到一个不同的缓存键。

除此之外,不可能完全摆脱数据库查询。(你自己的答案是最好的)

解决方案:

生成一个有效的密钥,而不是这个

cache([@jobs, "index"])

做这个:

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

这将查询数据库并构建模型数组,从中cache_key检索 。

PS:我可以发誓使用以前版本的 Rails 中的关系......

于 2013-04-18T12:24:41.130 回答
3

大约一年来,我们一直在做您在生产中提到的事情。几个月前我把它提取到了一个宝石中:

https://github.com/cmer/scope_cache_key

基本上,它允许您使用范围作为缓存键的一部分。这样做有显着的性能优势,因为您现在可以在单个缓存元素中缓存包含多条记录的页面,而不是循环范围内的每个元素并单独检索缓存。我觉得将此与标准的“俄罗斯娃娃缓存”原则相结合是最佳的。

于 2013-06-22T11:08:01.087 回答
2

我遇到过类似的问题,我无法成功地将关系传递给缓存函数,而您的 @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
于 2013-03-04T16:54:59.383 回答
2

我做过类似 Hopsoft 的东西,但它使用Rails Guide中的方法作为模板。我使用 MD5 摘要来区分关系(因此User.active.cache_key可以User.deactivated.cache_keyupdated_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
于 2015-06-13T20:49:38.620 回答
1

虽然我将@mark-stratmann 的响应标记为正确,但实际上我通过简化实现解决了这个问题。我在我的模型关系声明中添加了 touch: true :

belongs_to :tenant, touch: true

然后根据租户设置缓存键(也需要查询参数):

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

这样,如果添加了新作业,它也会触及租户缓存。不确定这是否是最好的路线,但它有效并且看起来很简单。

于 2013-03-05T19:36:05.617 回答
1

我使用这段代码:

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
于 2013-09-19T17:33:27.240 回答
0

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

于 2013-07-10T01:16:06.697 回答
0

作为一个起点,你可以尝试这样的事情:

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 中使用 touch,您应该可以假设由 count 和 max updated_at 组成的缓存键就足够了。

于 2015-07-08T16:25:49.693 回答
-1

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

require "digest/md5"

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

ActiveRecord::Relation.send :include, RelationCacheKey
于 2013-12-10T22:18:25.917 回答