在 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 条记录。所以你的记忆力更健全,代价是额外的数据库查询(通常很快)
使用 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 条记录。所以你的记忆力更健全,代价是额外的数据库查询(通常很快)