Rails 5.2:在加载多个带有调整大小的附件的模型时避免 N+1
Rails5.2: avoid N+1 when loading multiples models with resized attachments
我有 4 个模型(Album、Samplepack、Demo 和 Ghostproduction)我想在一个视图中显示。其中两个(Album 和 Samplepack)附有封面图片。
这是生成的流程:
- StaticController => 加载 4 个模型到@resources
- index.html.erb(视图)=> 显示@resources
- application_helper (#display_mosaic) => 找到资源对应的partial
- hoverable_
=> 显示部分资源 (
- <资源>.large => 调整附加图片的大小
控制器加载4个模型:
# app/controllers/static_controller
class StaticController < ApplicationController
def index
@resources = [
Album.all.with_attached_album_artwork,
Samplepack.all.with_attached_album_artwork,
Demo.all,
Ghostproduction.all
].flatten
end
end
显示@resources的视图:
# app/views/static/index.html.erb
<% @resources.sort_by(&:created_at).each do |resources| %>
<%= display_mosaic(resource) %>
<% end %>
对应帮手:
# app/helpers/application_helper.rb
def display_mosaic(item)
if item.class == Demo
return render('demos/hoverable_demo', demo: item)
elsif item.class == Samplepack
return render('samplepacks/hoverable_samplepack', samplepack: item)
# ... and so on
end
部分示例:
# app/views/albums/_hoverable_album.html.erb
<div>
<%= image_tag album.large if album.album_artwork.attached? %>
</div>
以及 ActiveStorage 变体:
# app/models/concerns/artworkable_concern.rb
def large
return self.album_artwork.variant(resize: '600x600').processed
end
使用此解决方案,会产生 14 个查询:
# logs
# Album.all.with_attached_album_artwork
Album Load (0.7ms) SELECT "albums".* FROM "albums"
# Album attachments
ActiveStorage::Attachment Load (0.9ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = AND "active_storage_attachments"."name" = AND "active_storage_attachments"."record_id" IN (, , , ) [["record_type", "Album"], ["name", "album_artwork"], ["record_id", 1], ["record_id", 2], ["record_id", 3], ["record_id", 4]]
ActiveStorage::Blob Load (0.5ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" IN (, , , ) [["id", 1], ["id", 2], ["id", 3], ["id", 6]]
# Samplepack.all.with_attached_album_artwork
Samplepack Load (0.4ms) SELECT "samplepacks".* FROM "samplepacks"
# Samplepack attachments
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = AND "active_storage_attachments"."name" = AND "active_storage_attachments"."record_id" = [["record_type", "Samplepack"], ["name", "album_artwork"], ["record_id", 1]]
ActiveStorage::Blob Load (0.1ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = [["id", 5]]
# Demo.all
Demo Load (0.3ms) SELECT "demos".* FROM "demos"
# Ghostproduction.all
Ghostproduction Load (0.5ms) SELECT "ghostproductions".* FROM "ghostproductions"
# User, because of some logic in my _navbar partial
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = ORDER BY "users"."id" ASC LIMIT
# Variants (.large), I believe it's a N+1 issue (I currently have 4 Album + 1 Samplepack attachments to resize)
ActiveStorage::Blob Load (2.9ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 3], ["LIMIT", 1]]
ActiveStorage::Blob Load (0.4ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 1], ["LIMIT", 1]]
ActiveStorage::Blob Load (21.0ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 5], ["LIMIT", 1]]
ActiveStorage::Blob Load (1.7ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 2], ["LIMIT", 1]]
ActiveStorage::Blob Load (0.5ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 6], ["LIMIT", 1]]
有什么方法可以减少查询量并提高此视图的性能吗?我读过有关 Eager Loading 的内容,但它似乎不合适(这些模型之间没有关系)。
作为初学者,将不胜感激。
祝你有美好的一天,
克莱门特
编辑: 我找到了这个 great guide from EvilMartian
EDIT2: Another resource(更详尽)
如果所有东西都有图像附件,那么您可以彻底改变您的想法,直接加载附件,并包含多态记录。附件也是 ActiveRecord 模型。你可能有:
@resources =
ActiveStorage::Attachment.
where(record_type: %w[Album Samplepack Demo Production]).
includes(:record).
map(&:record)
用于主要数据获取。它不会是一个查询,但 Rails 会将其优化为最小查询数(一个用于所有附件,一个用于加载多态集合中的每种类型)。
对于变体,如果不对 Rails 打补丁,blob 查询可能无法停止。将自己束缚在 N+1 查询中的另一种方法是缓存,实际上 DHH even recommends it. I'd wrap an appropriate cache
around it (documentation here)。天真地说,那就是:
<% @resources.sort_by(&:created_at).each do |resource| %>
<% cache resource do %>
<%= display_mosaic(resource) %>
<% end %>
<% end %>
但是,也请仔细查看您的 display_mosaic
方法,因为在我看来它正在重新实现内置的多态部分渲染逻辑。周围有一些不稳定的文件名,您可以将其替换为:
<%= render partial: @resources.sort_by(&:created_at), cached: true %>
超高效渲染。
或者,这是一种相当常见的首页模式,使用 materialized view that's a union query and has its own ActiveRecord model. You can manage views in Rails with the Scenic gem.
我有 4 个模型(Album、Samplepack、Demo 和 Ghostproduction)我想在一个视图中显示。其中两个(Album 和 Samplepack)附有封面图片。
这是生成的流程:
- StaticController => 加载 4 个模型到@resources
- index.html.erb(视图)=> 显示@resources
- application_helper (#display_mosaic) => 找到资源对应的partial
- hoverable_
=> 显示部分资源 ( - <资源>.large => 调整附加图片的大小
控制器加载4个模型:
# app/controllers/static_controller
class StaticController < ApplicationController
def index
@resources = [
Album.all.with_attached_album_artwork,
Samplepack.all.with_attached_album_artwork,
Demo.all,
Ghostproduction.all
].flatten
end
end
显示@resources的视图:
# app/views/static/index.html.erb
<% @resources.sort_by(&:created_at).each do |resources| %>
<%= display_mosaic(resource) %>
<% end %>
对应帮手:
# app/helpers/application_helper.rb
def display_mosaic(item)
if item.class == Demo
return render('demos/hoverable_demo', demo: item)
elsif item.class == Samplepack
return render('samplepacks/hoverable_samplepack', samplepack: item)
# ... and so on
end
部分示例:
# app/views/albums/_hoverable_album.html.erb
<div>
<%= image_tag album.large if album.album_artwork.attached? %>
</div>
以及 ActiveStorage 变体:
# app/models/concerns/artworkable_concern.rb
def large
return self.album_artwork.variant(resize: '600x600').processed
end
使用此解决方案,会产生 14 个查询:
# logs
# Album.all.with_attached_album_artwork
Album Load (0.7ms) SELECT "albums".* FROM "albums"
# Album attachments
ActiveStorage::Attachment Load (0.9ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = AND "active_storage_attachments"."name" = AND "active_storage_attachments"."record_id" IN (, , , ) [["record_type", "Album"], ["name", "album_artwork"], ["record_id", 1], ["record_id", 2], ["record_id", 3], ["record_id", 4]]
ActiveStorage::Blob Load (0.5ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" IN (, , , ) [["id", 1], ["id", 2], ["id", 3], ["id", 6]]
# Samplepack.all.with_attached_album_artwork
Samplepack Load (0.4ms) SELECT "samplepacks".* FROM "samplepacks"
# Samplepack attachments
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = AND "active_storage_attachments"."name" = AND "active_storage_attachments"."record_id" = [["record_type", "Samplepack"], ["name", "album_artwork"], ["record_id", 1]]
ActiveStorage::Blob Load (0.1ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = [["id", 5]]
# Demo.all
Demo Load (0.3ms) SELECT "demos".* FROM "demos"
# Ghostproduction.all
Ghostproduction Load (0.5ms) SELECT "ghostproductions".* FROM "ghostproductions"
# User, because of some logic in my _navbar partial
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = ORDER BY "users"."id" ASC LIMIT
# Variants (.large), I believe it's a N+1 issue (I currently have 4 Album + 1 Samplepack attachments to resize)
ActiveStorage::Blob Load (2.9ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 3], ["LIMIT", 1]]
ActiveStorage::Blob Load (0.4ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 1], ["LIMIT", 1]]
ActiveStorage::Blob Load (21.0ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 5], ["LIMIT", 1]]
ActiveStorage::Blob Load (1.7ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 2], ["LIMIT", 1]]
ActiveStorage::Blob Load (0.5ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = LIMIT [["id", 6], ["LIMIT", 1]]
有什么方法可以减少查询量并提高此视图的性能吗?我读过有关 Eager Loading 的内容,但它似乎不合适(这些模型之间没有关系)。 作为初学者,将不胜感激。
祝你有美好的一天, 克莱门特
编辑: 我找到了这个 great guide from EvilMartian
EDIT2: Another resource(更详尽)
如果所有东西都有图像附件,那么您可以彻底改变您的想法,直接加载附件,并包含多态记录。附件也是 ActiveRecord 模型。你可能有:
@resources =
ActiveStorage::Attachment.
where(record_type: %w[Album Samplepack Demo Production]).
includes(:record).
map(&:record)
用于主要数据获取。它不会是一个查询,但 Rails 会将其优化为最小查询数(一个用于所有附件,一个用于加载多态集合中的每种类型)。
对于变体,如果不对 Rails 打补丁,blob 查询可能无法停止。将自己束缚在 N+1 查询中的另一种方法是缓存,实际上 DHH even recommends it. I'd wrap an appropriate cache
around it (documentation here)。天真地说,那就是:
<% @resources.sort_by(&:created_at).each do |resource| %>
<% cache resource do %>
<%= display_mosaic(resource) %>
<% end %>
<% end %>
但是,也请仔细查看您的 display_mosaic
方法,因为在我看来它正在重新实现内置的多态部分渲染逻辑。周围有一些不稳定的文件名,您可以将其替换为:
<%= render partial: @resources.sort_by(&:created_at), cached: true %>
超高效渲染。
或者,这是一种相当常见的首页模式,使用 materialized view that's a union query and has its own ActiveRecord model. You can manage views in Rails with the Scenic gem.