场景:
我有一份工作正在生产(heroku)中运行流程(sidekiq)。该过程使用activerecord-import
gem将数据(CSV)从S3导入数据库模型。该gem帮助批量插入数据。因此,dbRows
变量在迭代CSV行时从所有存储的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
问题:
作业完成后,进程运行BUT时,作业内存会增加,而内存不会下降。它永远存在!
调试中,我发现dbRows
看起来永远不会被垃圾回收
我了解了其中的RETAINED对象以及内存在rails中的工作方式。尽管我还没有找到一种方法来解决我的问题。
我希望作业完成后,所有在dbRows上设置的引用都是GC,并且释放了工作人员内存。
任何帮助表示赞赏。
更新:我读到有关weakref
的信息,但我不知道这是否有用。有什么见识吗?
答案 0 :(得分:0)
尝试从CSV批量导入行,例如一次将行导入到DB 1000行中,这样就不必保留前几行,GC可以收集它们。在任何情况下,这对数据库都是有好处的(对于从s3下载,如果您从S3移交CSV
IO对象,也是如此。
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内部(内存分析gem)还是在Ruby外部(作业和系统日志)来尝试查找源。