【问题标题】:Handling a massive query in Rails在 Rails 中处理大量查询
【发布时间】:2017-07-13 09:44:26
【问题描述】:

使用 Rails 和 Postgres 处理大型结果集的最佳方法是什么?直到今天我才遇到问题,但现在我正试图返回 @network_hosts 的 124,000 条记录对象,它有效地对我的开发服务器进行了 DoS。

我的 activerecord orm 不是最漂亮的,但我很确定清理它不会对性能有所帮助。

@network_hosts = []
@host_count = 0
@company.locations.each do |l|
  if  l.grace_enabled == nil || l.grace_enabled == false
    l.network_hosts.each do |h|
      @host_count += 1
      @network_hosts.push(h)
      @network_hosts.sort! { |x,y| x.ip_address <=> y.ip_address }
      @network_hosts = @network_hosts.first(5)
     end
  end
end

最后,我需要能够将@network_hosts返回给控制器,以便处理到视图中。

这是 Sidekiq 能够提供帮助的事情吗,还是会持续这么久?如果 Sidekiq 是要采用的路径,由于作业异步运行,我该如何处理在页面加载时没有 @network_hosts 对象?

【问题讨论】:

    标签: ruby-on-rails ruby postgresql sidekiq


    【解决方案1】:

    我相信您希望 (1) 摆脱所有循环(您有很多查询正在进行)和 (2) 使用 AR 查询而不是在数组中进行排序。

    可能是这样的:

    NetworkHost.
      where(location: Location.where.not(grace_enabed: true).where(company: @company)).
      order(ip_address: :asc).
      tap do |network_hosts|
        @network_hosts = network_hosts.limit(5)
        @host_count = network_hosts.count
      end
    

    这样的事情应该在单个数据库查询中完成。

    我不得不对您的关联是如何建立的以及您正在寻找grace_enabled 不为真(nil 或假)的位置做出一些假设。

    我还没有测试过这个,所以它很可能是错误的。但是,我认为方向是正确的。

    【讨论】:

    • 我应该提到,一旦我从@network_hosts = network_hosts.limit(5) 取出限制,问题就会出现
    • 根据this answer,limit 不是array 方法。我仍然认为您想摆脱所有循环(n+1?)和array 的排序。
    • 你是对的。我能够加快查询速度,并且服务器不会因为这个查询而挂起@network_hosts = NetworkHost.where(location_id: @company.locations.where.not(grace_enabled: true)).order(ip_address: :asc) ...现在我只需要弄清楚如何进一步减少查询时间,因为到目前为止它需要380511ms。谢谢你让我走上正轨。我的 AR 查询技能需要改进!
    • 出于好奇,在您的控制台中,由于此代码块,执行了多少查询?此外,只要NetworkHost belongs_to :location ,您应该能够使用location: 而不是location_id:
    • 数不胜数……数不胜数。有什么方法可以让我轻松数数吗?
    【解决方案2】:

    需要记住的是,Rails 在实际需要查询结果之前不会执行任何 SQL 查询。 (我将使用 User 而不是 NetworkHost,这样我可以随时向您显示控制台输出)

    @users = User.where(first_name: 'Random');nil # No query run
    => nil
    @users # query is now run because the results are needed (they are being output to the IRB window)
    #  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" = $1 LIMIT $2  [["first_name", "Random"], ["LIMIT", 11]]
    # => #<ActiveRecord::Relation [...]>
    @users = User.where(first_name: 'Random') # query will be run because the results are needed for the output into the IRB window   
    #  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" = $1 LIMIT $2  [["first_name", "Random"], ["LIMIT", 11]]
    # => #<ActiveRecord::Relation [...]>
    

    为什么这很重要?它允许您将要运行的查询存储在实例变量中,并且在您到达可以使用ActiveRecord::Batches 的一些不错方法的视图之前不执行它。特别是,如果您有一些视图(或导出功能等)正在迭代@network_hosts,则可以使用find_each

    # Controller
    @users = User.where(first_name: 'Random') # No query run
    
    # view
    @users.find_each(batch_size: 1) do |user|
      puts "User's ID is #{user.id}"         
    end
    #  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["first_name", "Random"], ["LIMIT", 1]]
    #  User's ID is 1
    #  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" = $1 AND ("users"."id" > 1) ORDER BY "users"."id" ASC LIMIT $2  [["first_name", "Random"], ["LIMIT", 1]]
    #  User's ID is 2
    #  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" = $1 AND ("users"."id" > 2) ORDER BY "users"."id" ASC LIMIT $2  [["first_name", "Random"], ["LIMIT", 1]]
    # => nil
    

    您的查询直到视图才会执行,此时它一次只会将 1,000 条记录(可配置)加载到内存中。一旦到达这 1,000 条记录的末尾,它将自动运行另一个查询以获取接下来的 1,000 条记录。所以你的记忆更加清醒,代价是额外的数据库查询(通常很快)

    【讨论】:

    • 这样做是否会阻止页面加载,直到所有记录都被批处理?
    • 据我所知,rails 在视图完成渲染之前不会开始发送任何 html,但我承认,我从未研究过它如何/何时开始向客户端发送内容.但是,根据我的经验,当您有大量记录返回的查询时,通常不需要运行一段时间的查询,而是尝试在内存中加载和存储这么多记录,而批处理通常可以为您提供更好的性能
    • 如果您知道您的服务器可以一次处理 10,000 条记录而不会减慢速度,那么通过可配置的批量大小,您可以减少需要运行的查询数量
    • 而不是@users,您将拥有@network_hosts,然后像@network_hosts.find_each { |host| # render/act on the host }一样做
    猜你喜欢
    • 1970-01-01
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    • 2013-07-03
    • 1970-01-01
    • 2020-09-30
    • 2018-01-04
    • 2013-12-14
    相关资源
    最近更新 更多