代码之家  ›  专栏  ›  技术社区  ›  cman77

生成为ActiveRecord::Relation的Rails缓存键

  •  4
  • cman77  · 技术社区  · 12 年前

    我正试图生成一个片段缓存(使用Dalli/Memcached存储),但密钥是用“#”作为密钥的一部分生成的,所以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::Relation一起进行,但我不确定如何进行。

    9 回复  |  直到 12 年前
        1
  •  8
  •   Community CDub    8 年前

    背景:

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

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

    所以每次都会得到不同的缓存密钥。

    除此之外 可以完全消除数据库查询。(你的 own answer 是最好的)

    解决方案:

    要生成有效的密钥,而不是

    cache([@jobs, "index"])
    

    这样做:

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

    这将查询数据库并构建一个模型数组 cache_key 检索到。

    附言:我可以发誓使用以前版本的Rails中的关系。。。

        2
  •  3
  •   Carl Mercier    12 年前

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

    https://github.com/cmer/scope_cache_key

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

        3
  •  2
  •   Mark Stratmann    12 年前

    我也遇到过类似的问题,我无法成功地将关系传递到缓存函数,而您的@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
    
        4
  •  2
  •   dignoe    10 年前

    我做过类似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
    
        5
  •  1
  •   cman77    12 年前

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

    belongs_to :tenant, touch: true
    

    然后基于租户设置缓存密钥(还需要一个所需的查询参数):

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

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

        6
  •  1
  •   Diego Carrion    11 年前

    我正在使用此代码:

    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
    
        7
  •  0
  •   Nicolai    12 年前

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

        8
  •  0
  •   hurikhan77    10 年前

    作为起点,您可以尝试以下操作:

    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和maxupdated_at组成的缓存密钥就足够了。

        9
  •  -1
  •   Hopsoft    11 年前

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

    require "digest/md5"
    
    module RelationCacheKey
      def cache_key
        Digest::MD5.hexdigest to_sql.downcase
      end
    end
    
    ActiveRecord::Relation.send :include, RelationCacheKey