在 Rails 中处理大量查询

Handling a massive query in Rails

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

我的 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

最终,我需要能够return@network_hosts到controller进行处理进入view

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

我相信您想要 (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 或 false)的位置做出一些假设。

我还没有测试过这个,所以它很可能是错误的。但是,我觉得方向是对的。

需要记住的一点是,在实际需要查询结果之前,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" =  LIMIT   [["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" =  LIMIT   [["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" =  ORDER BY "users"."id" ASC LIMIT   [["first_name", "Random"], ["LIMIT", 1]]
#  User's ID is 1
#  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" =  AND ("users"."id" > 1) ORDER BY "users"."id" ASC LIMIT   [["first_name", "Random"], ["LIMIT", 1]]
#  User's ID is 2
#  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."first_name" =  AND ("users"."id" > 2) ORDER BY "users"."id" ASC LIMIT   [["first_name", "Random"], ["LIMIT", 1]]
# => nil

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