我的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
我所看到的是RSS内存(htop)不断增长,并且一度达到10GB。我不确定为什么会发生这种情况,但Ruby似乎永远不会将内存释放回操作系统。
我知道有很多问题都与此相符。我甚至尝试通过代码更改看起来像这样(具体是最后3行).i.e手动运行GC.start
结果仍然相同。
while true
....
...
...
users = nil
GC.start
break if break_condition
end
在Ruby版本2.2.2
和2.3.0
编辑:其他细节
1)OS。
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=15.04
DISTRIB_CODENAME=vivid
DISTRIB_DESCRIPTION="Ubuntu 15.04"
2)通过rvm安装和编译ruby。
3)ActiveRecord版本4.2.6
答案 0 :(得分:2)
我不能告诉你内存泄漏的来源,但是我确实间谍了一些低调的果实。
但首先,有两件事:
您确定ActiveRecord是将数据从一个数据库复制到另一个数据库的正确方法吗?我非常有信心,事实并非如此。每个主要数据库产品都具有强大的导出和导入功能,您将看到的性能将比在Ruby中执行的性能好很多倍,并且您始终可以在应用程序中调用这些工具。在继续沿着这条路走下去之前要认真思考。
10,000的数字来自哪里?您的代码表明您知道一次获取所有记录并不是一个好主意,但10,000仍然是很多记录。您可以通过简单地尝试不同的数字来看到一些收益:100或1,000,比方说。
那就是说,让我们深入研究这条线的作用:
users = User.limit(10000).offset(offset).as_json
第一部分User.limit(10000).offset(offset)
创建一个表示查询的ActiveRecord :: Relation对象。当您在其上调用as_json
时,将执行查询,该查询将实例化10,000个用户模型对象并将它们放入数组中,然后从这些用户对象的每个属性构造一个哈希。 (请查看ActiveRecord::Relation#as_json
here的来源。)
换句话说,你实例化了10,000个User对象,只有在获得属性后才将它们抛弃。
因此,快速取胜就是完全跳过这部分。只需选择原始数据:
user_keys = User.attribute_names
until break_condition
# ...
users_values = User.limit(10000).offset(offset).pluck(user_keys)
users_values.each do |vals|
user_attrs = user_keys.zip(vals).to_h
member = Member.find_by(name: user_attrs["name"])
member.update_attributes(user_attrs)
end
end
ActiveRecord::Calculations#pluck
返回一个数组数组,其中包含每条记录的值。在user_values.each
循环内部,我们将该值数组转换为哈希。无需实例化任何User对象。
现在让我们来看看:
member = Member.find_by(name: user_attrs["name"])
member.update_attributes(user_attrs)
这将从数据库中选择一条记录,实例化一个Member对象,然后在while
循环的每次迭代中更新数据库中的记录10,000次。这是正确的方法如果您需要在更新该记录时运行验证。但是,如果您不需要运行验证,则可以通过再次实例化任何对象来节省时间和内存:
Member.where(name: user_attrs["name"]).update_all(user_attrs)
不同之处在于ActiveRecord::Relation#update_all
没有从数据库中选择记录或实例化一个Member对象,它只是更新它。您在上面的评论中说,您对name
列有一个唯一约束,因此我们知道这只会更新一条记录。
完成这些更改后,您仍然必须应对while
循环的每次迭代中必须执行10,000次UPDATE查询的事实。再次,考虑使用数据库的内置导出和导入功能,而不是尝试让Rails执行此操作。