【问题标题】:How do I optimize ActiveRecord queries and speed up my HAML partial which is rendered 1000 times in Rails?如何优化 ActiveRecord 查询并加快在 Rails 中呈现 1000 次的 HAML 部分?
【发布时间】:2014-03-01 20:22:19
【问题描述】:

我有一个 HAML 部分呈现为集合的一部分,它本身呈现两个其他部分。集合可能有 1000 长。它使用 group by 和 #map#reject 的复杂查询。如何加快渲染速度? 100 已经花费了大约 6 秒。我已经尝试在顶部查询中使用 .includes(:relation) 但它没有帮助。尽管包括.includes(:votes, :comments, :taggings, :tags),并运行SELECT "votes".* FROM "votes" WHERE "votes"."post_id" IN (124, 123,...,它仍然尝试加载单个关系Vote Load (0.0ms) SELECT "votes".* FROM "votes" WHERE "votes"."post_id" = $1 AND "votes"."user_id" = 1。我想 ActiveRecord 不使用像 Coherence 这样的关系对象缓存(这很好,因为这很困难)。

(顺便说一句,我如何提取部分中几行的复杂查询代码?中间部分没有控制器。我担心如果我添加移动模板,我会有复制复杂的查询并保持同步,这无疑会失败。)

这是调用的布局

-- User
  -- SQL: @user.posts.order(:created_at => :desc).limit(1000)
  -- Post
    -- SQL, Votes, post.votes.where(user_id: current_user.id)
    -- SQL, Comments, post.comments.count
    -- SQL, Tags, post.taggings.select("tag_id, count(*) as count").group("tag_id").order(count: :desc).limit(3).includes(:tag)
  -- SQL, Comments... about the same as above

这个博客几乎完全描述了我遇到的同样的问题!但是,他们使用:finder_sql,当我尝试使用它时,Rails 抱怨说它已被弃用并查看范围。范围似乎没有任何帮助。它们很简洁,但它们只限制查询,而不是在一个查询中加载附加信息。 http://coldattic.info/shvedsky/pro/blogs/a-foo-walks-into-a-bar/posts/75

【问题讨论】:

  • 您是说您的复杂查询代码在部分内部吗?这不应该在模型中吗?速度问题与渲染有关还是与查询有关?需要看一个更具体的例子才能知道问题出在哪里。
  • 听起来您可能正在查询和实例化很多模型,这很昂贵,我建议您使用 SQL 即子查询来仅查找您想要的模型。
  • @riley 好的,我展开循环并将部分包含到主要的show.haml 中,并将查询移到模型中,但我认为这不是问题所在。它似乎没有帮助。我认为问题出在 SQL 上,它会为 1000 个帖子中的每一个获取附加信息。我会用布局更新问题。
  • @riley 出于某种原因,我认为 SQL 应该进入控制器。通常我喜欢我的模型非常简单。不知道为什么我没有想到将 SQL 放入模型中。
  • 从控制器粘贴此操作的代码。

标签: ruby-on-rails performance ruby-on-rails-4 haml


【解决方案1】:

作为加速解决方案之一——您可以尝试去掉多余的部分,并将它们全部与第一个主要部分结合起来。调用`render 非常“昂贵”,因此您可能需要支付一些代码“去组织”来加快速度。

【讨论】:

  • 好的,我展开循环并将部分 HAML 直接插入到 show.haml,但这并没有太大帮助。我认为问题是SQL。使用 PHP 或 Perl,我可以创建一个巨大的 JOIN 查询并在循环中加载所有内容,创建我自己的对象。不知道如何优化 ActiveRecord。
  • 您也可以在 Rails 中使用纯 SQL 请求(在您的控制器中使用 connection.execute(sql_query)
  • 好的,我优化了展开和重构的 HAML,两者之间没有额外的 SQL,并且在数组上使用 render 速度慢两倍!
【解决方案2】:

好的,我从我提到的博客中得到了一个想法。我把它降低到毫秒!他们的解决方案已被弃用,但他们也提到了find_by_sql,这是我使用的。我可以创建我的定制查询,它会向模型中的 select 子句中添加额外的字段。我很沮丧,因为我不得不取消我的 HAML 模板并专门为此页面自定义它们,从而导致重复。 :( 我的模板非常漂亮,我很惊讶整个网站可以容纳这么几行。

sql = <<-EOL
  select posts.id, posts.user_id, posts.title, ...
    users.username,
    (select vote from votes ...) as user_votes,
    coalesce((select sum(vote) from votes ...),0) as votes_sum,
    (select count(*) from comments ...) as comments_count
    from posts inner join users on posts.user_id = users.id
    where users.id = ?
    order by created_at desc
    limit 1000 
EOL
@posts = Post.find_by_sql [sql, (current_user ? current_user.id : -1), params[:id]]
postIds = @posts.map { |p| p.id }
sql = <<-EOL
  select tags.id, tags.tag, taggings.post_id, count(*) as count 
    from tags inner join taggings on tags.id = taggings.tag_id 
    where taggings.post_id in (?)
    group by tags.id, tags.tag, taggings.post_id
    order by count desc
EOL
@tags = Tag.find_by_sql [sql, postIds]
sql = <<-EOL
  select comments.id, comments.user_id, ...
    (select vote from votes where ...) as user_votes,
    coalesce((select sum(vote) from votes ...),0) as votes_sum,
    (select count(*) from comments ...) as has_children
  from comments
  where comments.user_id = ?
  order by created_at desc
  limit 1000 
EOL
@comments = Comment.find_by_sql [sql, (current_user ? current_user.id : -1), params[:id] ]   

这些标签特别具有挑战性,因为我需要数千个中的前 3 个。如果没有针对 tag1、tag2、tag3 的 3 个子选择,在 SQL 中没有简单的方法可以做到这一点,但我认为这比额外的查询还要慢。但是标签现在与帖子无关,所以我必须使用纯 Ruby 和 selectfirst 来获得正确的标签。

另一条评论提到connnection.execute(sql),我可能会尝试让它更快,完全绕过模型。如果是这样,我可能会对 Rails 失去更多的信心...... :(

我发现image_tag 占用了很大一部分时间!转换为纯 HTML 大大加快了速度!

我在“网络”选项卡上转到 Chrome 调试器并右键单击请求并选择“复制 CURL”以获取请求,然后我可以从命令行运行并对其进行基准测试。

$ time for i in {1..10}; do curl -I "http://127.0.0.1:3000/users/1" -H "If-None-Match: ""37d7cd0d7d37f5af51168561c2ef04e2""" -H "Accept-Encoding: gzip,deflate,sdch" -H "Accept-Language: en-US,en;q=0.8" -H "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" -H "Cache-Control: max-age=0" -H "Cookie: _Tyger_session=Y3oydVZ5OE1KMXl6cHNWL0dwNnU3bTQzNlpuaERtemRSZ3g5T3FoV2pPdEErejllVDRmME9EVGVuTkZVa1l3eDhRMmx3QXplTCtmT0xQTlFYNGhvbzJlUXVGRFc1a3BsWG1ZWThIRDE5WmVnZ2czU2NnMkRPQ0xIc1RJd3gxMm5wMUxWUEMyQUQxa1EzYzl5b3lMSDhsckd6WWJnYTJnUm96NEl4b3Q3MmdmSE1pbzE2NjF5b040SnB6TEk1WjZoSU5jYjI5V0xDSWwvMlJ3c2llOHBISmp0YlpCRGdxcnhNTWtNMXRyT3piYz0tLXdFNkpTVjFHeThTckJNNk1HU3N2NWc9PQ"%"3D"%"3D--21e668fceab201c65260d8afebce1b24728d6055" -H "Connection: keep-alive" --compressed -s | grep X-Runtime; done
X-Runtime: 0.307203
X-Runtime: 0.305202
X-Runtime: 0.313207
X-Runtime: 0.310206
X-Runtime: 0.303202
X-Runtime: 0.332220
X-Runtime: 0.312222
X-Runtime: 0.304202
X-Runtime: 0.308239
X-Runtime: 0.315209

real    0m3.449s
user    0m0.060s
sys     0m0.258s

【讨论】:

    【解决方案3】:

    “我如何加速 X?”没有可供审查的代码是无法回答的。不过,作为一般说明,您可以使用fragment caching 来提高检索部分的速度。

    此外,您可以使用“模型缓存”作为described in this railscast 来缓存查询结果。

    对于问题的第二部分,您可以将查询提取到查询对象中,如 this codeclimate blog post 所述

    【讨论】:

    • 我可以自己加速代码,我只是不知道如何使用 HAML 和 partials 和 Rails 来做。我已经写了让陀思妥耶夫斯基晕倒的怪物 SQL 查询!片段缓存很有趣,但每个部分都是动态的,具有不同的标题。每个用户都可以看到它,我不担心重新加载,只是初始加载。所以它是小批量但高成本和私人的。我现在会阅读其他链接,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2020-02-14
    • 2016-08-06
    • 2017-11-15
    • 2012-05-05
    • 1970-01-01
    • 2011-09-02
    • 2015-05-23
    相关资源
    最近更新 更多