场景:
我有一个在生产(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
问题:
进程运行时作业内存增加但是一旦作业完成,内存不会下降。它永远永远存在!
调试我发现dbRows
看起来不会被垃圾收集,我学会了RETAINED对象以及内存如何在rails中运行。虽然我还没有找到一种方法来应用它来解决我的问题。
我希望一旦作业完成,dbRows上设置的所有引用都是GC并释放工作者内存。
任何帮助赞赏。
更新:我读到了关于weakref
但我不知道是否有用。那里有什么见解?
尝试批量导入CSV中的行,例如一次将行导入DB 1000行,这样您就不会保留以前的行,GC可以收集它们。无论如何,这对数据库都有好处(如果你从S3手中获取CSV
IO对象,则从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外部(作业和系统日志),都可以尝试查找源代码。