Vir*_*ren 5 ruby memory ruby-2.3
我有或多或少看起来像这样的Ruby代码
offset = 0
index = 1
User.establish_connection(..) # db1
class Member < ActiveRecord::Base
self.table_name = 'users'
end
Member.establish_connection(..) #db2
while true
users = User.limit(10000).offset(offset).as_json ## for a Database 1
offset = limit * index
index += 1
users.each do |u|
member = Member.find_by(name: u[:name])
if member.nil?
Member.create(u)
elsif member.updated_at < u[:updated_at]
member.update_attributes(u)
end
end
break if break_condition
end
Run Code Online (Sandbox Code Playgroud)
我所看到的是RSS内存(htop)不断增长,并且一度达到10GB.我不确定为什么会发生这种情况但是Ruby似乎永远不会将内存释放回操作系统.
我知道有很多问题都与此相符.我甚至尝试通过代码更改看起来像这样(具体是最后3行).ie GC.start手动运行结果仍然相同.
while true
....
...
...
users = nil
GC.start
break if break_condition
end
Run Code Online (Sandbox Code Playgroud)
关于Ruby版本测试这2.2.2和2.3.0
编辑:其他细节
1)操作系统.
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=15.04
DISTRIB_CODENAME=vivid
DISTRIB_DESCRIPTION="Ubuntu 15.04"
Run Code Online (Sandbox Code Playgroud)
2)通过rvm安装和编译ruby.
3)ActiveRecord版本 4.2.6
我无法告诉你内存泄漏的根源,但我确实发现了一些容易实现的目标。
\n\n但首先,有两件事:
\n\n您确定 ActiveRecord 是将数据从一个数据库复制到另一个数据库的正确方法吗?我非常确信事实并非如此。每个主要数据库产品都具有强大的导出和导入功能,您将看到的性能将比在 Ruby 中执行的性能好很多很多倍,并且您始终可以从应用程序内调用这些工具。在继续沿着这条路走之前,请仔细考虑一下。
10,000 这个数字从何而来?您的代码表明您知道一次获取所有记录不是一个好主意,但 10,000 条记录仍然很多。只需尝试不同的数字,例如 100 或 1,000,您就可能会看到一些收获。
也就是说,让我们深入研究一下这条线在做什么:
\n\nusers = User.limit(10000).offset(offset).as_json\nRun Code Online (Sandbox Code Playgroud)\n\n第一部分User.limit(10000).offset(offset)创建一个代表您的查询的 ActiveRecord::Relation 对象。当您调用as_json它时,会执行查询,实例化 10,000 个 User 模型对象并将它们放入一个数组中,然后根据每个 User 对象的属性构造一个哈希。(查看ActiveRecord::Relation#as_json 此处的来源。)
换句话说,您实例化了 10,000 个 User 对象,只是在获得它们的属性后将它们丢弃。
\n\n因此,快速获胜的方法是完全跳过这一部分。只需选择原始数据:
\n\nuser_keys = User.attribute_names\n\nuntil break_condition\n # ...\n users_values = User.limit(10000).offset(offset).pluck(user_keys)\n\n users_values.each do |vals|\n user_attrs = user_keys.zip(vals).to_h\n member = Member.find_by(name: user_attrs["name"])\n member.update_attributes(user_attrs) \n end\nend\nRun Code Online (Sandbox Code Playgroud)\n\nActiveRecord::Calculations#pluck返回一个数组数组,其中包含每个记录的值。在循环内user_values.each,我们将该值数组转换为哈希。无需实例化任何 User 对象。
现在让我们看一下:
\n\nmember = Member.find_by(name: user_attrs["name"])\nmember.update_attributes(user_attrs)\nRun Code Online (Sandbox Code Playgroud)\n\n这会从数据库中选择一条记录,实例化一个 Member 对象,然后在循环的每次迭代中更新数据库\xe2\x80\x9410,000 次记录while。如果您需要在更新记录时运行验证,这是正确的方法。不过,如果您不需要运行验证,则可以通过不实例化任何对象来节省时间和内存:
Member.where(name: user_attrs["name"]).update_all(user_attrs)\nRun Code Online (Sandbox Code Playgroud)\n\n不同之处在于,ActiveRecord::Relation#update_all它不会从数据库中选择记录或实例化 Member 对象,它只是更新它。您在上面的评论中说您对该name列有唯一的约束,因此我们知道这只会更新一条记录。
进行这些更改后,您仍然必须面对这样一个事实:您必须在循环的每次迭代中执行 10,000 个 UPDATE 查询while。再次考虑使用数据库的内置导出和导入功能,而不是尝试让 Rails 执行此操作。