Ruby不释放内存

时间:2016-04-21 12:18:02

标签: 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

我所看到的是RSS内存(htop)不断增长,并且一度达到10GB。我不确定为什么会发生这种情况,但Ruby似乎永远不会将内存释放回操作系统。

我知道有很多问题都与此相符。我甚至尝试通过代码更改看起来像这样(具体是最后3行).i.e手动运行GC.start结果仍然相同。

while true

....
...
...
users = nil
GC.start
break if break_condition
end

在Ruby版本2.2.22.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

1 个答案:

答案 0 :(得分:2)

我不能告诉你内存泄漏的来源,但是我确实间谍了一些低调的果实。

但首先,有两件事:

  1. 您确定ActiveRecord是将数据从一个数据库复制到另一个数据库的正确方法吗?我非常有信心,事实并非如此。每个主要数据库产品都具有强大的导出和导入功能,您将看到的性能将比在Ruby中执行的性能好很多倍,并且您始终可以在应用程序中调用这些工具。在继续沿着这条路走下去之前要认真思考。

  2. 10,000的数字来自哪里?您的代码表明您知道一次获取所有记录并不是一个好主意,但10,000仍然是很多记录。您可以通过简单地尝试不同的数字来看到一些收益:100或1,000,比方说。

  3. 那就是说,让我们深入研究这条线的作用:

    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执行此操作。

相关问题