0

设想:

我有一份在生产(heroku)中运行进程(sidekiq)的工作。activerecord-import该过程使用gem将数据 (CSV) 从 S3 导入数据库模型。这个 gem 有助于批量插入数据。因此,变量在迭代 CSV 行时dbRows从存储的所有对象中设置了大量内存(一切都很好)。ActiveRecord一旦数据被导入(在db_model.import dbRows:)dbRows被清除(应该是!)并处理下一个对象。

如:(简化脚本以便更好理解)

def import
      ....
      s3_objects.contents.each do |obj|
          @cli.get_object({..., key: obj.key}, target: file) 
          dbRows = []
          csv = CSV.new(file, headers: false)
          while line = csv.shift
              # >> here dbRows grows and grows and never is freed!
              dbRows << db_model.new(
                field1: field1,
                field2: field2,
                fieldN: fieldN
              )
          end
          db_model.import dbRows
          dbRows = nil   # try 1 to freed array
          GC.start   # try 2 to freed memory
      end
      ....
end

问题:

作业内存在进程运行时增长,但一旦作业完成,内存不会下降。它永远存在!

调试我发现它dbRows看起来永远不会被垃圾收集,并且我了解了 RETAINED 对象以及内存在 rails 中的工作方式。虽然我还没有找到一种方法来应用它来解决我的问题。

我希望一旦工作完成,在 dbRows 上设置的所有引用都是 GC 并释放工作内存。

任何帮助表示赞赏。

更新:我读过weakref但我不知道是否有用。有什么见解吗?

4

1 回答 1

0

尝试从 CSV 批量导入行,例如一次将行导入 DB 1000 行,这样您就不会保留以前的行,GC 可以收集它们。无论如何,这对数据库都有好处(如果您CSV从 S3.

s3_io_object = s3_client.get_object(*s3_obj_params).body
csv = CSV.new(s3_io_object, headers: true, header_converters: :symbol)
csv.each_slice(1_000) do |row_batch|
  db_model.import ALLOWED_FIELDS, row_batch.map(&:to_h), validate: false
end

请注意,我不是为了节省内存而实例化 AR 模型,而是只传递哈希值并activerecord-import告诉validate: false.

另外,file参考从哪里来?它似乎是长寿的。

从您的示例中并不明显,但是对对象的引用是否仍可能由您的环境中的库或扩展全局保存?

有时这些事情很难追踪,因为来自任何地方的任何被调用的代码(包括外部库代码)都可能执行以下操作:

动态定义常量,因为它们永远不会被 GC 处理

Any::Module::Or:Class.const_set('NewConstantName', :foo)

或将数据添加到常量引用/拥有的任何内容

SomeConstant::Referenceable::Globally.array << foo # array will only get bigger and contents will never be GC'd

否则,您能做的最好的事情就是使用一些内存分析工具,无论是在 Ruby 内部(内存分析 gems)还是在 Ruby 之外(作业和系统日志)来尝试查找源。

于 2019-03-03T12:44:55.920 回答