为啥 Rails 渲染视图如此缓慢?

Posted

技术标签:

【中文标题】为啥 Rails 渲染视图如此缓慢?【英文标题】:Why does Rails render the views so slowly?为什么 Rails 渲染视图如此缓慢? 【发布时间】:2014-03-25 16:24:23 【问题描述】:

使用 Rails 4 和 Ruby 1.9.3。 我在我正在渲染的 usuarios#show 中有这个视图。控制器需要大量数据,但我已经设法将使用 eager_load 和 akin 的查询从 300 毫秒(奇数)减少到 44 毫秒。

我的问题是数据库调用没问题,但是视图需要将近 30 秒才能呈现。

我已确保从控制器变量中获取我使用的所有内容。

编辑:我已经从控制器添加了查询本身

这是控制器方法,使用 eager_loading

#app/controllers/usuarios_controller.rb

before_action :show_usuario, only: [:show]

def show
  if !@usuario.country_id.blank?
    @country = @usuario.country
  end
  respond_to do |format
    format.html
    format.js
  end
end

private
def show_usuario
  if usuario_signed_in?
    id = current_usuario.id
    @usuario = Usuario.eager_load(:profile).find(id)
  else
    @usuario = Usuario.eager_load(:profile, textos: [:likes, :text_medals, :tags]).find(params[:id])
  end
  @textos = Texto.eager_load(:likes, :text_medals, :tags).where(:usuario_id => @usuario.id).order("textos.created_at DESC").paginate(:page => params[:page]).per_page(6)
end

这是我的日志

Started GET "/" for 127.0.0.1 at 2014-03-25 12:55:39 +0100
  Usuario Load (1.4ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = 12 ORDER BY "usuarios"."id" ASC LIMIT 1
Processing by UsuariosController#show as HTML
  Notification Load (1.2ms)  SELECT "notifications".* FROM "notifications" INNER JOIN "textos" ON "textos"."id" = "notifications"."text" WHERE (textos.usuario_id = 12 AND notifications.user_related != 12) ORDER BY "notifications"."id" DESC LIMIT 10
  SQL (1.2ms)  SELECT "usuarios"."id" AS t0_r0, "usuarios"."email" AS t0_r1, "usuarios"."encrypted_password" AS t0_r2, "usuarios"."reset_password_token" AS t0_r3, "usuarios"."reset_password_sent_at" AS t0_r4, "usuarios"."remember_created_at" AS t0_r5, "usuarios"."sign_in_count" AS t0_r6, "usuarios"."current_sign_in_at" AS t0_r7, "usuarios"."last_sign_in_at" AS t0_r8, "usuarios"."current_sign_in_ip" AS t0_r9, "usuarios"."last_sign_in_ip" AS t0_r10, "usuarios"."created_at" AS t0_r11, "usuarios"."updated_at" AS t0_r12, "usuarios"."nombre" AS t0_r13, "usuarios"."fecha_nac" AS t0_r14, "usuarios"."country_id" AS t0_r15, "usuarios"."is_admin" AS t0_r16, "usuarios"."publish_fbviews" AS t0_r17, "usuarios"."accept_terms" AS t0_r18, "profiles"."id" AS t1_r0, "profiles"."usuario_id" AS t1_r1, "profiles"."quote" AS t1_r2, "profiles"."quote_author" AS t1_r3, "profiles"."fb_account" AS t1_r4, "profiles"."twt_account" AS t1_r5, "profiles"."gpls_account" AS t1_r6, "profiles"."biografia" AS t1_r7, "profiles"."created_at" AS t1_r8, "profiles"."updated_at" AS t1_r9, "profiles"."hide_email" AS t1_r10, "profiles"."pic_file_name" AS t1_r11, "profiles"."pic_content_type" AS t1_r12, "profiles"."pic_file_size" AS t1_r13, "profiles"."pic_updated_at" AS t1_r14 FROM "usuarios" LEFT OUTER JOIN "profiles" ON "profiles"."usuario_id" = "usuarios"."id" WHERE "usuarios"."id" = $1 LIMIT 1  [["id", 12]]
USER CARGADO
  Country Load (0.7ms)  SELECT "countries".* FROM "countries" WHERE "countries"."id" = $1 LIMIT 1  [["id", 246]]
TEXTOS CARGADOS
  Rendered usuarios/_main_frame.html.erb (12082.9ms)
   (1.6ms)  SELECT COUNT(DISTINCT "textos"."id") FROM "textos" LEFT OUTER JOIN "likes" ON "likes"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals_textos" ON "text_medals_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals" ON "text_medals"."id" = "text_medals_textos"."text_medal_id" LEFT OUTER JOIN "tags_textos" ON "tags_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "tags_textos"."tag_id" WHERE "textos"."usuario_id" = 12
  SQL (1.9ms)  SELECT DISTINCT "textos".id, textos.created_at AS alias_0 FROM "textos" LEFT OUTER JOIN "likes" ON "likes"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals_textos" ON "text_medals_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals" ON "text_medals"."id" = "text_medals_textos"."text_medal_id" LEFT OUTER JOIN "tags_textos" ON "tags_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "tags_textos"."tag_id" WHERE "textos"."usuario_id" = 12 ORDER BY textos.created_at DESC LIMIT 6 OFFSET 0
  SQL (5.6ms)  SELECT "textos"."id" AS t0_r0, "textos"."usuario_id" AS t0_r1, "textos"."titulo" AS t0_r2, "textos"."contenido" AS t0_r3, "textos"."idioma" AS t0_r4, "textos"."created_at" AS t0_r5, "textos"."updated_at" AS t0_r6, "textos"."is_borrador" AS t0_r7, "textos"."is_on_contest" AS t0_r8, "textos"."portada_file_name" AS t0_r9, "textos"."portada_content_type" AS t0_r10, "textos"."portada_file_size" AS t0_r11, "textos"."portada_updated_at" AS t0_r12, "textos"."contest_id" AS t0_r13, "textos"."views" AS t0_r14, "textos"."has_draft" AS t0_r15, "textos"."license_type" AS t0_r16, "likes"."id" AS t1_r0, "likes"."usuario_id" AS t1_r1, "likes"."texto_id" AS t1_r2, "likes"."created_at" AS t1_r3, "likes"."updated_at" AS t1_r4, "text_medals"."id" AS t2_r0, "text_medals"."name" AS t2_r1, "text_medals"."slogan" AS t2_r2, "text_medals"."created_at" AS t2_r3, "text_medals"."updated_at" AS t2_r4, "text_medals"."image_file_name" AS t2_r5, "text_medals"."image_content_type" AS t2_r6, "text_medals"."image_file_size" AS t2_r7, "text_medals"."image_updated_at" AS t2_r8, "tags"."id" AS t3_r0, "tags"."nombre" AS t3_r1, "tags"."created_at" AS t3_r2, "tags"."updated_at" AS t3_r3 FROM "textos" LEFT OUTER JOIN "likes" ON "likes"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals_textos" ON "text_medals_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals" ON "text_medals"."id" = "text_medals_textos"."text_medal_id" LEFT OUTER JOIN "tags_textos" ON "tags_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "tags_textos"."tag_id" WHERE "textos"."usuario_id" = 12 AND "textos"."id" IN (75, 74, 73, 70, 69, 68) ORDER BY textos.created_at DESC
  CACHE (0.0ms)  SELECT COUNT(DISTINCT "textos"."id") FROM "textos" LEFT OUTER JOIN "likes" ON "likes"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals_textos" ON "text_medals_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "text_medals" ON "text_medals"."id" = "text_medals_textos"."text_medal_id" LEFT OUTER JOIN "tags_textos" ON "tags_textos"."texto_id" = "textos"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "tags_textos"."tag_id" WHERE "textos"."usuario_id" = 12
  Usuario Load (0.8ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  Profile Load (0.7ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  CACHE (0.1ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  CACHE (0.0ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  CACHE (0.1ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  CACHE (0.0ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  CACHE (0.1ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  CACHE (0.1ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  CACHE (0.0ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  CACHE (0.0ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  CACHE (0.1ms)  SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."id" = $1 ORDER BY "usuarios"."id" ASC LIMIT 1  [["id", 12]]
  CACHE (0.1ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."usuario_id" = $1 ORDER BY "profiles"."id" ASC LIMIT 1  [["usuario_id", 12]]
  Rendered usuarios/_user_texts.html.erb (16760.5ms)
  Rendered usuarios/show.html.erb within layouts/application (28889.5ms)
Completed 200 OK in 29246ms (Views: 28898.8ms | ActiveRecord: 44.1ms)

如您所见,视图需要很长时间才能呈现。 这是“_user_texts”部分。

<div class="ui grid">
  <% @textos.each do |texto| %>
  <div class="text row" text-pages="<%= @textos.total_pages%>">
    <div class="five wide column">
      <%= image_tag texto.portada.url(:medium), :class => "ui rounded medium left floated image text-cover" %>
    </div>
    <div class="eleven wide column">
      <div class="row">
        <h1 class="ui header">
          <%= link_to text_name(texto), usuario_texto_path(texto.usuario.id, texto.id), :class => "title-format"%>
          <div class="sub header subheader-format">
            escrito por <%= text_author_image texto %><%= text_author_name texto %> el <%= humanize_creation_date(texto.created_at) %>
          </div>
        </h1>
      </div>
      <div class="tag-list row">
        <div class="ui horizontal list">
          <% if not texto.tags.empty? %>
            <% texto.tags.each do |tag| %>
            <div class="tag item">
              <%= link_to tag.nombre, tag_path(tag.id), :class => "ui red large label" %>
            </div>
            <% end %>
          <% end %>
        </div>
      </div>
    </div>
  </div>
  <% end %>
</div>

<% if @textos.total_pages > @textos.current_page %>
<a id="more_texts" href="javascript:void(0)">
  <span class="vertical"></span><span>Ver más</span>
</a>
<% end %>

编辑:我正在使用分页。特别是will_paginate gem。当我到达页面底部时,有一个用于获取更多结果的按钮。

有人可以告诉我如何让它更快吗?这让我发疯了。 谢谢!

【问题讨论】:

可能与this Carrierwave issue有关。你可以在没有图像的情况下尝试它,看看它是否恢复正常?可能值得与 Github 上的维护人员联系,以便他们可以重新打开问题(它看起来从未修复过)。 @textos 变量保存了多少条记录? texto.tags 平均保存多少条记录?我建议去掉 usuarios/_user_texts.html.erb 部分,直到你到达最慢的部分。 @ZachKemp 我正在使用回形针。 我相信你需要使用 kaminari 之类的东西来为你的文本分页,而不是在页面上加载所有这些东西。 我会对您的查询进行基准测试,对我来说似乎很重。我认为重点应该放在提高 sql 查询的性能上。我不知道确切的内容,但我怀疑您是否需要在一个查询中获得这么多的情报。 【参考方案1】:

我会对您的查询进行基准测试,这对我来说似乎很重。我认为重点应该放在提高 sql 查询的性能上。我不知道确切的内容,但我怀疑您是否需要在一个查询中获得这么多的情报。

例如,您可以在表中添加索引以提高性能。您还应该考虑您真正需要的属性并使用 select 来定义它们。查询中的列越少,结果就越快。

最后,你做了很多连接。看起来您几乎加入了所有模型。左连接真的很慢,因为你会遍历所有记录。如果您真的需要所有这些连接,那么如果您可以将其中一些左连接转换为内部连接,那就太好了。这将意味着巨大的改进,因为您只需检查相关记录。

【讨论】:

感谢您的见解。我将阅读有关内部连接的信息,听起来不错。 如果您在开篇文章中显示您的查询将会很有用。现在我们看到了所有的 sql 输出,阅读起来不太友好。尤其是在手机上。 :)【参考方案2】:

在您在视图中调用 .all 或 .each 之前,查询不会真正运行,即使您将结果存储在控制器中的实例变量中也是如此。这可能会使视图看起来很慢,而实际上它是数据库中的内容。

尝试调用一些调用来对视图中的不同位置进行基准测试,以查看时间在哪里使用。

http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html#method-i-benchmark

为了查看您正在查询的内容并发现缺失的索引等。解释很方便。

http://guides.rubyonrails.org/active_record_querying.html#running-explain

【讨论】:

不知道第一部分。我会调查的。谢谢!【参考方案3】:

解决了。在 Bullet gem 的帮助下,我重新制定了我渴望加载的方式。

事实证明,实际上所做的一切都做得很好。问题是我的搭档将回形针的图像存储在 Dropbox 中。 Dropbox 很可能不是针对此类请求的,所以延迟就是因为这个。我们将改用亚马逊的 S3。

对于未来的读者:请注意!并检查您将图像存储在哪里。

谢谢大家!

【讨论】:

以上是关于为啥 Rails 渲染视图如此缓慢?的主要内容,如果未能解决你的问题,请参考以下文章

诊断视图渲染缓慢的原因

渲染布局和视图,rails

Rails 如何转义和显示渲染视图的内容

Rails 3:我的视图没有被渲染

为啥 Rails 不自动生成视图文件?

Rails 5 - 使用多态关联 - 渲染视图