片段缓存和渴望加载:如何充分利用这两个世界?

时间:2014-04-20 08:28:08

标签: ruby-on-rails performance caching eager-loading fragment-caching

在我看来,片段缓存和急切加载 - 至少有时 - 彼此之间有些不一致。假设我有一个用户,他有很多帖子,每个帖子都有很多评论,反过来也可以有很多评论等等。

当我必须渲染页面时,我可以选择急切加载用户,所有帖子,所有评论等等,以避免数据库n-1次。 我可以懒惰加载每个对象并依赖片段缓存来仅查询数据库中是否有新的或已更改的对象。使用片段缓存和急切加载似乎都是浪费,因为我可能会做一个非常复杂的查询并实例化很多对象只是为了使用它们中的一小部分。

但是如果我有一个应用程序,其中一个用户有许多Foos,而这些Foos又有很多Bars等等,但是其中每个Foo都是同时创建完整的所有Bars及其相关对象的,然后从那时起永远不会改变。在这种情况下,我想对已经渲染的Foos使用片段缓存,但是当我必须加载一个包含其所有关联对象的新Foo时,使用预先加载。毕竟,在更细粒度的层次上缓存片段无法获得任何好处。

Rails实现这一目标的最佳方法是什么?我想我可以做一个查询来获取Foo的id,然后在我必须渲染每个Foo时用急切加载进行显式查找。这样做有更好/更优雅/更惯用的方法吗?

4 个答案:

答案 0 :(得分:0)

您可以在控制器中使用fragment_exists?方法,以防止在所有对象已经位于缓存中时急切加载。只有在第一次调用页面时才会出现这种情况。

像这样:

  if fragment_exists? "my_cache_key_#{id}" 
    # load your object without eager loading here        
  else
    # eager load your objects here        
  end

然后在视图中使用片段chaching:

<% cache("my_cache_key_#{@object.id}") do %>
...
...
...
<% end %>

那应该为你做到的!

答案 1 :(得分:0)

专门针对Rails 4的@daviddb答案构建,我发现在视图中skip_digest需要为控制器内的模板重新创建MD5 Hash以进行比较,这似乎有点多。

此外,如果没有第一次找到对象,将很难获得具有上次修改时间戳的对象,因此我发现在没有.includes(:object1, :object2)

的情况下进行初始查询会很有帮助

views/customers/show.html.slim(根据您首选的模板引擎进行调整)

- cache['customers/show', @customer], skip_digest: true do
  h1
    = @customer.account.name
  = render 'account_summary', account: @customer.account
  = render 'account_details', transactions: @customer.account.transactions
  ...

controllers/customers_controller.rb

  def show
    customer = Customer.find(params[:id])
    if fragment_exist?(['customers/show', customer])
      @customer = customer
    else
      @customer = Customer.includes(account: :transactions).find(params[:id])
    end
  end

注意:通过设置skip_digest: true,您需要在更改此视图及其所依赖的任何部分时清除部署缓存,以确保正确呈现新布局。

答案 2 :(得分:0)

我使用lambda做了类似的缓存。 下面你可以得到一个想法。它解决的问题是 - 让最受欢迎的用户操作繁重,需要> 5秒。但是使用lambda,您可以缓存用户列表。

controller:
def index
 @users = -> { User.by_rating }
end

view:
= cache "rating-list", expires_in: 1.day do
  - @users.call.each do |user|
     = render user

答案 3 :(得分:0)

您可以在此类主页上使用俄语娃娃缓存来解决此类问题,例如我们在主页上可以使用带有评论和作者的帖子

// HomeController

def index
  @posts = Post.includes(:author, :comments).limit(10)
end

视图

- cache ["v1#home", @posts.maximum(:updated_at).to_i] do
  - @post.each do |post|
    - cache ["v1#post_in_home", @post.id, @post.updated_at.to_i] do
      = post.title
      = post.author.name
      = post.content

      - cache ["v1#comments_in_post", @post.comments.maximum(:updated_at).to_i] do
        - @comments.each do |comment|
          = comment.author
          = comment.content