2024 年 Rails 片段缓存与急切加载

问题描述 投票:0回答:2

我正在开发一个成熟的 Rails 应用程序来加速我们的主索引页面,该页面的渲染时间为 5-6 秒。我最近注意到一些 N+1 没有被 Bullet 拾取,因此我在查询上实现了新的

strict_loading
方法,并相应地包含了关联。它现在做了相当多的急切加载:

[{ thumb_image: [:image_votes, :license, :projects, :user] }
 :location, :name,
 { titles: :votes },
 :projects, :logs, :user]

这对页面加载时间有所帮助。但最大的改进是当我开始在每个对象的部分上实现片段缓存时。这将加载时间缩短至 3-4 秒。像这样的东西:

Views: 62.4ms | ActiveRecord: 3356.4ms | Allocations: 592258

但是页面还是很慢。被查询的对象不仅关联性很强,而且对象也会不断地添加或更新。而且用户访问频率很高。

因此,即使大多数访问者获得 90% 的缓存命中率,我们也无法缓存索引查询以保持页面新鲜。现在我们遇到的情况是,查询时间占页面平均渲染时间的 95% 以上。这是因为它会急切加载所有这些对象和关联,即使它不需要其中的 90%。

所以我正在重新考虑急切加载。我想象更好的是先进行一个轻查询来获取最新的对象 ID,然后检查每个对象部分缓存键上的

fragment_exist?
。然后,无论缺少什么,batch加载缓存尚不存在的所有对象的关联。像这样的东西:

objects = Object.where(id: ids.reject { |id| fragment_exist?(cache_key_for_obj_id) }).
          includes(blah blah blah)

问题是缓存键。我已经查看了有关此问题的旧问答,但他们假设手动缓存键,手动无效。使用 Rails 5 中缓存片段键的“较新”自动生成的摘要(我使用 Rails 7):

  • 控制器似乎无法知道在视图模板中生成的缓存键
  • 如果我
    skip_digest
    在生成缓存密钥时,似乎我必须手动使缓存过期 - 不,谢谢,这看起来太脆弱了

这里要走什么路?我可以以某种方式从控制器检查

fragment_exist?
,包括正确的摘要,还是应该恢复到延迟加载关联?

如果延迟加载,我是否应该调用一个方法来从对象部分模板本身预先加载它们(每个对象)?这不仅没有将它们批处理在一起,而且似乎严重违反了模板和控制器之间的关注点。但部分模板似乎是唯一单独处理每个对象的地方。

正如一位之前的提问者所说,“片段缓存和急切加载似乎有些矛盾”。

ruby-on-rails caching eager-loading
2个回答
1
投票

可以考虑使用这种缓存模式:Rails 缓存:俄罗斯娃娃

您还需要设置自己的缓存键。您可以使用 Updated_at 属性作为其一部分,以确保对象没有更改。

请提供有关您正在处理的问题的更多信息。


0
投票

有一个中间地带称为批量加载。预加载仅在缓存失效时发生,但它有点像设置:

https://github.com/exAspArk/batch-loader

$ bundle add batch-loader
# app/models/post.rb

class Post < ApplicationRecord
  belongs_to :user

  def user_lazy
    # i'm not even gonna try to explain this, you'll have to really read the readme
    BatchLoader.for(user_id).batch do |user_ids, loader|
      User.where(id: user_ids).each { |user| loader.call(user.id, user) }
    end
  end
end
# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  def index
    # this one is for example purposes, there is a middleware for this
    BatchLoader::Executor.clear_current

    @posts = Post.all
    # no db query is executed yet
    @users = @posts.each_with_object({}) do |post, h|
      h[post] = post.user_lazy
    end
  end
end
# app/views/posts/index.html.erb

# collection cache also works, this is just to see it easier in the log
<% @posts.each do |post| %>
  <% cache post do %>
    <%= render post %>
  <% end %>
<% end %>
# app/views/posts/_post.html.erb

<%= tag.div id: dom_id(post) do %>
  # when any of the `cache post` is invalidated, this line will execute
  # and eager load all of the users. when all posts are cached there is
  # no eager loading of users
  <%= @users[post] %>
<% end %>

第一次加载时,帖子会被加载,用户会立即加载并且帖子会被缓存,第二次访问时只需要帖子:

Processing by PostsController#index as HTML
  Post Load (0.1ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:10:in `each_with_object'
  Rendering layout layouts/application.html.erb
  Rendering posts/index.html.erb within layouts/application
Read fragment views/posts/index:54ef1a9698061f894cb00c459440f8a6/posts/2-20240122190140985467 (0.1ms)
Read fragment views/posts/index:54ef1a9698061f894cb00c459440f8a6/posts/3-20240122185810608401 (0.1ms)
Read fragment views/posts/index:54ef1a9698061f894cb00c459440f8a6/posts/4-20240122185810608401 (0.1ms)
Read fragment views/posts/index:54ef1a9698061f894cb00c459440f8a6/posts/5-20240122185810608401 (0.1ms)

如果一个缓存失效

Post.first.touch
,用户会再次急切加载:

Processing by PostsController#index as HTML
  Post Load (0.1ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:10:in `each_with_object'
  Rendering layout layouts/application.html.erb
  Rendering posts/index.html.erb within layouts/application
Read fragment views/posts/index:1687a2170041b25d00fcb7bd15638f9e/posts/2-20240122192532103648 (0.2ms)
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (?, ?, ?) OR "users"."id" IS NULL)  [["id", 17], ["id", 19], ["id", 55]]
  ↳ app/models/post.rb:6:in `block in user_lazy'
  Rendered posts/_post.html.erb (Duration: 2.7ms | Allocations: 2761)
Write fragment views/posts/index:1687a2170041b25d00fcb7bd15638f9e/posts/2-20240122192532103648 (0.2ms)
Read fragment views/posts/index:1687a2170041b25d00fcb7bd15638f9e/posts/3-20240122185810608401 (0.1ms)
Read fragment views/posts/index:1687a2170041b25d00fcb7bd15638f9e/posts/4-20240122185810608401 (0.1ms)
Read fragment views/posts/index:1687a2170041b25d00fcb7bd15638f9e/posts/5-20240122185810608401 (0.1ms)
© www.soinside.com 2019 - 2024. All rights reserved.