Rails ActiveRecord model 里你们是如何处理耗时远程属性的 cache 的?

est · January 04, 2015 · Last by ruby_sky replied at January 05, 2015 · 2382 hits

比如

class ItemLog < ActiveRecord::Base
  def get_address
    http_get('http://x.com/get_address_by_item_type?id=' + self.type_id).to_json['address']
  end
end

那么比如遍历 500 个 ItemLog 输出 json,包括 get_address 字段,那么就得对应 500 次 http 请求。

其实 type_id 是有限的几种,可以搞个缓存

address_cache = Hash[ItemLog.where(...).limit(500).pluck(:type_id).uniq.map {|type_id| [type_id, get_address(type_id)] }]
ItemLog.where(...).limit(500).each { |item| item.address = address_cache[item.type_id }

显示 cache 明显的缺点就是需要两次遍历数据,一次得到所有唯一的 type_id,一次去处理最终 json 输出。

我的问题是,可否避免在代码上下文显示搞缓存,可否在 model 里直接搞个类似 scope cache 的东东?

class ItemLog < ActiveRecord::Base
  def address
    address_cache[self.type_id] || http_get('http://x.com/get_address_by_item_type?id=' + self.type_id).to_json['address']
  end
end

大家一般是怎么处理的呢?

Rails.cache 本身就有 fetch 的方法,可以读取现有缓存,如果不存在,取得后再写入。

def address
  # 假设type_id unique
  Rails.cache.fetch("itemlog:#{type_id}") do
    http_get("http://x.com/get_address_by_item_type?id=#{type_id}")
  end
end

另外有两点需要考虑的:

  1. 如果使用 Memcache 作为 cache store 要考虑数据会否太多,因内存贵。
  2. 如果需要输出 list, 那么一次前端请求就可能要发出 N 个后端请求,既耗时失败率也高。可考虑后端任务写入批量数据。

#1 楼 @billy 好办法,顶一个。不过这个 cache 是全局的。。。TTL 设置什么的也麻烦。

有没有办法在一个 model 对象生存周期里 cache?

btw 我用的是 sinatra 和 grape

#3 楼 @hooopo 又发现好东西了收藏一个。

问题是 memoize 有效是对于单个 model 实例的。但是对于不同实例,只要 type_id 一样,那么都应该避免重复 http 请求。memoize 应该没法做到。

也就是说缓存应该建立在一个 ActiveRecord::Relation 上面,而不是 ActiveRecord::Base 上。

5 Floor has deleted

#4 楼 @est 这样?

class XX
  class << self
    extend ActiveSupport::Memoizable

      def addresses
        # some code
      end
      memoize :addresses
   end

   def address
     self.class.addresses[type_id]
   end
end

#2 楼 @est

class ItemLog
  def address
    return @address if defined?(@address)
    @address = http_get('http://x.com/get_address_by_item_type?id=' + self.type_id).to_json['address']
  end
end

哦,我上面的是错误的代码,有 type_id 没处理

#6 楼 @hooopo 我感觉还是有问题,class 初始化之后,type_id 对应的缓存会一直不刷新。当服务器进程一直跑的话,那么缓存是永久不过期的。

所以我在想,有没有跟 scope 生命周期一样的缓存。。。

You need to Sign in before reply, if you don't have an account, please Sign up first.