我正在构建一个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
我知道检查每个对象的缓存必须有一些开销。但我猜测有一种比我现在的方式更有效的方法,基本上是连续的,一个接一个。有关提高效率的任何指示吗?
答案 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
注意:如果您设置了许多项目,您可能需要考虑在某种后台工作程序中写入缓存。