Rails& Memcached:优化多个提取

时间:2013-01-17 22:02:53

标签: ruby-on-rails json caching memcached dalli

我正在构建一个iPhone应用程序的Rails后端。

在对我的应用程序进行概要分析后,我发现以下调用在性能方面特别昂贵:

@messages.as_json

此调用返回大约30个消息对象,每个消息对象包含许多子记录。如您所见,单个消息json响应可能会组成许多DB调用:

  def as_json(options={})

     super(:only => [...],
      :include => {
        :user => {...},
        :checkin => {...}
                     }},
        :likes => {:only => [...],
                      :include => { :user => {...] }}},
        :comments => {:only => [...],
                                    :include => { :user => {:only => [...] }}}
                   },
      :methods => :top_highlight)
  end

平均@messages.as_json次呼叫(所有30个对象)需要近1100毫秒。

想要优化我已经使用了memcached。使用下面的解决方案,当我的所有消息对象都在缓存中时,平均响应现在是200-300ms。我对此很满意,但我遇到的问题是,这使缓存未命中的情况更加缓慢。如果缓存中没有任何内容,则现在计算时间超过2000毫秒。

   # Note: @messages has the 30 message objects in it, but none of the child records have been grabbed

    @messages.each_with_index do |m, i|
      @messages[i] = Rails.cache.fetch("message/#{m.id}/#{m.updated_at.to_i}") do
        m.as_json
      end
    end

我知道检查每个对象的缓存必须有一些开销。但我猜测有一种比我现在的方式更有效的方法,基本上是连续的,一个接一个。有关提高效率的任何指示吗?

2 个答案:

答案 0 :(得分:8)

我相信Rails.cache使用ActiveSupport::Cache::Store接口,该接口具有read_multi方法用于此目的。 [1]

我认为为fetch换取read_multi会提高您的效果,因为ActiveSupport::Cache::MemCacheStore已优化read_multi的实施。 [2]

<强>代码

以下是更新后的实施:

keys = @messages.collect { |m| "message/#{m.id}/#{m.updated_at.to_i}" }
hits = Rails.cache.read_multi(*keys)
keys.each_with_index do |key, i|
  if hits.include?(key)
    @messages[i] = hits[key]
  else
    Rails.cache.write(key, @messages[i] = @messages[i].as_json)
  end
end

对于每次未命中,高速缓存写入仍然与高速缓存的一次往返同步执行。如果您想减少开销,请查看与workling之类的异步运行后台代码。

在开始扩展体系结构之前,请注意启动异步作业的开销实际上小于Rails.cache.write的开销。

Memcached Multi-Set

看起来Memcached团队至少考虑过提供Multi-Set(批量写入)命令,但目前还没有任何ActiveSupport接口,并且不清楚实现提供了什么级别的支持。 [3]

答案 1 :(得分:3)

从Rails 4.1开始,您现在可以执行fetch_multi并传入一个块。

http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch_multi

keys = @messages.collect { |m| "message/#{m.id}/#{m.updated_at.to_i}" }
hits = Rails.cache.fetch_multi(*keys) do |key|
  @messages[i] = @messages[i].as_json
end

注意:如果您设置了许多项目,您可能需要考虑在某种后台工作程序中写入缓存。