【问题标题】:Rails & Memcached: Optimizing multiple fetchesRails 和 Memcached:优化多个提取
【发布时间】:2013-01-01 13:46:47
【问题描述】:

我正在为 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_jsoncall(所有 30 个对象)大约需要 1100 毫秒。

想要优化我使用了 memcached。使用下面的解决方案,当我所有的消息对象都在缓存中时,平均响应现在是 200-300 毫秒。我对此很满意,但我遇到的问题是这使缓存未命中情况变得更慢。在缓存中没有任何内容的情况下,现在需要超过 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

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

【问题讨论】:

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


    【解决方案1】:

    我相信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 多集

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

    【讨论】:

    • 这看起来很棒,并且会减少到缓存的往返次数。只是为了进一步教育我,为什么将键作为“*键”传递给 read_multi。我从来没有在 ruby​​/rails 中使用过这个约定?看起来像是指向我的指针。
    • 刚刚意识到这缺少关于 fetch 的一件好事,那就是它使用 m.as_json 值设置错过的读取。有什么想法解决这个问题吗?想到的一种方法是将@messages 传递给后台作业并异步写入丢失的对象。
    • 关于'*keys'语法here的信息
    • 是的,我的意思是,通过“获取”,丢失的对象被写入缓存,这样在下一次读取时就会成功。我对上面代码的理解是,它确实会得到完整的消息集,但错过的对象下次仍然会错过。
    • 哦,我明白你在说什么,你是对的。我不确定如何在 Ruby 中异步执行此操作。我将修改我的解决方案以同步填充缓存,然后研究异步。
    【解决方案2】:

    从 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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-28
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多