提问人:KaptajnKold 提问时间:4/20/2014 最后编辑:davidbKaptajnKold 更新时间:6/23/2020 访问量:1336
片段缓存和快速加载:如何两全其美?
Fragment caching and eager loading: How to get the best of both worlds?
问:
在我看来,片段缓存和急切加载 - 至少有时 - 有些不一致。假设我有一个用户,他有很多帖子,每个帖子都有很多评论,而这些评论又可以有很多评论,依此类推。
当我必须渲染页面时,我可以选择对用户、她的所有帖子、他们的所有评论等进行急切加载,以避免点击数据库 n-1 次。或我可以延迟加载每个对象,并依靠片段缓存来仅查询数据库中的新对象或已更改的对象。同时使用片段缓存和预先加载似乎是浪费,因为我可能会执行非常复杂的查询并实例化大量对象,只是为了使用其中的一小部分。
但是,如果我有一个应用程序,其中用户有许多 Foos,而 Foos 又有许多 Bar,依此类推,但其中每个 Foo 都是同时创建完整的,其中包含所有 Bar 及其相关对象,并且从那时起永远不会改变。在这种情况下,我想对已渲染的 Foos 使用片段缓存,但当我必须加载新的 Foo 及其所有关联对象时,请使用急切加载。毕竟,在更精细的级别上缓存片段不会带来任何好处。
在 Rails 中实现这一点的最佳方法是什么?我想我可以做一个查询来只获取 Foo 的 id,然后在我必须渲染每个 Foo 时通过急切加载进行显式查找。有没有更好/更优雅/更惯用的方法?
答:
您可以在控制器中使用该方法来防止在缓存中时预先加载所有对象。只有在第一次调用页面时,情况并非如此。fragment_exists?
喜欢这个:
if fragment_exists? "my_cache_key_#{id}"
# load your object without eager loading here
else
# eager load your objects here
end
然后在视图中使用片段查查:
<% cache("my_cache_key_#{@object.id}") do %>
...
...
...
<% end %>
这应该为你做!
评论
*** NoMethodError Exception: undefined method `fragment_exists?' for #<CustomerController:0x007f94a7149ae0>
fragment_exist?
views/response/mission-ab203634-3ce6-44f0-88a9-7e62b25d9637/1-20201009164529/js_init
views/welcome/dashboard:d450ca1537e85d680d530f7909373869/response/mission-ab203634-3ce6-44f0-88a9-7e62b25d9637/1-20201009164529//js_init
基于@daviddb Rails 4 的答案,我发现有必要在视图中进行比较,因为在控制器中为模板重新创建 MD5 哈希进行比较似乎有点多。skip_digest
此外,如果没有第一次找到对象,就很难获得具有上次修改时间戳的对象,因此我发现在没有.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
我使用 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
您可以使用俄罗斯娃娃缓存来解决此类问题,假设我们有带有评论和作者的帖子,您可以在主页中使用
// 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
评论