方案:
我在生产(Heroku)中运行流程(Sidekiq)的工作。该过程使用activerecord-import
GEM将数据(CSV)从S3导入到DB模型中。该宝石有助于大量插入数据。因此,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
看起来永远不会被收集我了解了保留的对象以及记忆如何在铁轨中工作。尽管我还没有找到一种应用它来解决我的问题的方法。
我希望一旦工作完成了DBROW上的所有参考,均为GC,并且释放了工作人员的内存。
任何帮助。
更新:我阅读了有关weakref
的信息,但我不知道是否有用。那里有任何见解吗?
尝试从批处理中导入CSV的行,例如一次将线路导入到DB 1000行中,因此您不会握住以前的行,而GC可以收集它们。无论如何,这对数据库都是有益的(对于从S3下载,如果您将 CSV
从s3中移交给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'D
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(内存分析的宝石)或Ruby(作业和系统日志)之外尝试找到源。