如何处理 Ruby/Rails 中的内存泄漏
How to deal with memory leak in Ruby/Rails
我正在开发一个 Rails 应用程序来处理大量的
数据并停止,因为它使用了我计算机的所有内存,原因是
内存泄漏(未释放的已分配对象)。
在我的应用程序中,数据以分层方式组织,就像一棵树,
其中级别“X”的每个节点包含级别数据的总和
“X+1”。例如,如果级别“X+1”的数据包含数量
城市中的人口,“X”层包含城市中的人口数量
状态。这样,层级“X”的数据就是通过对
“X+1”级别的数据量(在本例中为人员)。
为了这个问题,考虑一棵有四层的树:
国家、州、城市和社区,每个级别都已映射
进入 Activerecords tables(国家、州、城市、社区)。
从填充树叶的csv文件中读取数据,即,
社区 table.
之后,数据按以下顺序从底部(社区)流向顶部(国家):
1) Neighbourhoods data is summed to Cities;
2) after step 1 is completed, Cities data is summed to States;
3) after step 2 is completed, States data is summed to Country;
我使用的原理图代码如下:
1 cities = City.all
2 cities.each do |city|
3 city.data = 0
4 city.neighbourhoods.each do |neighbourhood|
5 city.data = city.data + neighbourhood.data
6 end
7 city.save
8 end
树的最低层包含 380 万条记录。每次线
执行2-8,汇总一个城市,执行第8行后,
该子树不再需要,但它永远不会被释放(内存
泄露)。总结了50%的城市后,我所有的8Gbytes RAM
消失。
我的问题是我能做什么。买更好的硬件不会
因为我正在使用“小”原型。
我知道一个让它工作的方法:为每个城市重新启动应用程序,
但我希望有人有更好的主意。 “最简单的”是强制
垃圾收集器释放特定对象,但似乎不是一种方法
去做吧
(https://www.ruby-forum.com/t/how-do-i-force-ruby-to-release-memory/195515).
从以下文章中我了解到开发人员应该
以“建议”垃圾收集器什么的方式组织数据
应该被释放。也许另一种方法可以解决问题,但唯一的
我看到的替代方法是深度优先搜索方法而不是
我正在使用反向广度优先搜索,但我不明白为什么它应该起作用。
到目前为止我读到的内容:
https://stackify.com/how-does-ruby-garbage-collection-work-a-simple-tutorial/
https://www.toptal.com/ruby/hunting-ruby-memory-issues
https://scoutapm.com/blog/ruby-garbage-collection
https://scoutapm.com/blog/manage-ruby-memory-usage
谢谢
这并不是真正的内存泄漏。您只是不加掩饰地从 table 加载数据,这会耗尽可用内存。
解决办法是从数据库中加载数据in batches:
City.find_each do |city|
city.update(data: city.neighbourhoods.sum(&:data))
end
如果 neighbourhoods.data
是一个简单的整数,您不需要首先获取记录:
City.update_all(
'data = (SELECT SUM(neighbourhoods.data) FROM neighbourhoods WHERE neighbourhoods.city_id = cities.id)'
)
这将快一个数量级,并且内存消耗很小,因为所有工作都在数据库中完成。
如果您真的想将一堆记录加载到 rails 中,请确保 select 聚合而不是实例化所有这些嵌套记录:
City.left_joins(:neighbourhoods)
.group(:id)
.select(:id, 'SUM(neighbourhoods.data) AS n_data')
.find_each { |c| city.update(data: n_data) }
根据您的模型关联设置方式,应该能够利用预加载。
例如:
class City < ApplicationRecord
has_many :neighborhoods
class Neighborhood < ApplicationRecord
belongs_to :city
belongs_to :state
class State < ApplicationRecord
belongs_to :country
has_many :neighborhoods
class Country < ApplicationRecord
has_many :states
cities = City.all.includes(neighborhoods: { state: :country })
cities.each do |city|
...
end
你根本不需要 rails,纯 SQL 应该足以完成你正在尝试的事情:
City.connection.execute(<<-SQL.squish)
UPDATE cities SET cities.data = (
SELECT SUM("neighbourhoods.data")
FROM neighbourhoods
WHERE neighbourhoods.city_id = cities.id
)
SQL
我正在开发一个 Rails 应用程序来处理大量的 数据并停止,因为它使用了我计算机的所有内存,原因是 内存泄漏(未释放的已分配对象)。
在我的应用程序中,数据以分层方式组织,就像一棵树, 其中级别“X”的每个节点包含级别数据的总和 “X+1”。例如,如果级别“X+1”的数据包含数量 城市中的人口,“X”层包含城市中的人口数量 状态。这样,层级“X”的数据就是通过对 “X+1”级别的数据量(在本例中为人员)。
为了这个问题,考虑一棵有四层的树: 国家、州、城市和社区,每个级别都已映射 进入 Activerecords tables(国家、州、城市、社区)。
从填充树叶的csv文件中读取数据,即, 社区 table.
之后,数据按以下顺序从底部(社区)流向顶部(国家):
1) Neighbourhoods data is summed to Cities;
2) after step 1 is completed, Cities data is summed to States;
3) after step 2 is completed, States data is summed to Country;
我使用的原理图代码如下:
1 cities = City.all
2 cities.each do |city|
3 city.data = 0
4 city.neighbourhoods.each do |neighbourhood|
5 city.data = city.data + neighbourhood.data
6 end
7 city.save
8 end
树的最低层包含 380 万条记录。每次线 执行2-8,汇总一个城市,执行第8行后, 该子树不再需要,但它永远不会被释放(内存 泄露)。总结了50%的城市后,我所有的8Gbytes RAM 消失。
我的问题是我能做什么。买更好的硬件不会 因为我正在使用“小”原型。
我知道一个让它工作的方法:为每个城市重新启动应用程序, 但我希望有人有更好的主意。 “最简单的”是强制 垃圾收集器释放特定对象,但似乎不是一种方法 去做吧 (https://www.ruby-forum.com/t/how-do-i-force-ruby-to-release-memory/195515).
从以下文章中我了解到开发人员应该 以“建议”垃圾收集器什么的方式组织数据 应该被释放。也许另一种方法可以解决问题,但唯一的 我看到的替代方法是深度优先搜索方法而不是 我正在使用反向广度优先搜索,但我不明白为什么它应该起作用。
到目前为止我读到的内容:
https://stackify.com/how-does-ruby-garbage-collection-work-a-simple-tutorial/
https://www.toptal.com/ruby/hunting-ruby-memory-issues
https://scoutapm.com/blog/ruby-garbage-collection
https://scoutapm.com/blog/manage-ruby-memory-usage
谢谢
这并不是真正的内存泄漏。您只是不加掩饰地从 table 加载数据,这会耗尽可用内存。
解决办法是从数据库中加载数据in batches:
City.find_each do |city|
city.update(data: city.neighbourhoods.sum(&:data))
end
如果 neighbourhoods.data
是一个简单的整数,您不需要首先获取记录:
City.update_all(
'data = (SELECT SUM(neighbourhoods.data) FROM neighbourhoods WHERE neighbourhoods.city_id = cities.id)'
)
这将快一个数量级,并且内存消耗很小,因为所有工作都在数据库中完成。
如果您真的想将一堆记录加载到 rails 中,请确保 select 聚合而不是实例化所有这些嵌套记录:
City.left_joins(:neighbourhoods)
.group(:id)
.select(:id, 'SUM(neighbourhoods.data) AS n_data')
.find_each { |c| city.update(data: n_data) }
根据您的模型关联设置方式,应该能够利用预加载。
例如:
class City < ApplicationRecord
has_many :neighborhoods
class Neighborhood < ApplicationRecord
belongs_to :city
belongs_to :state
class State < ApplicationRecord
belongs_to :country
has_many :neighborhoods
class Country < ApplicationRecord
has_many :states
cities = City.all.includes(neighborhoods: { state: :country })
cities.each do |city|
...
end
你根本不需要 rails,纯 SQL 应该足以完成你正在尝试的事情:
City.connection.execute(<<-SQL.squish)
UPDATE cities SET cities.data = (
SELECT SUM("neighbourhoods.data")
FROM neighbourhoods
WHERE neighbourhoods.city_id = cities.id
)
SQL