CSV 解析占用太多内存
CSV Parse Taking Too Much Memory
我正在尝试读取一个 5MM 行文件,现在它超出了我在 heroku 上分配的内存使用量。我的方法有点快~200 inserts/second..我相信它在导入时崩溃了..所以我的计划是分批导入 1,000 或 10,000。我的问题是如何告诉我我在文件的末尾,ruby 有一个 .eof
方法但它是一个 File
方法,我不知道如何调用它在我的循环中
def self.import_parts_db(file)
time = Benchmark.measure do
Part.transaction do
parts_db = []
CSV.parse(File.read(file), headers: true) do |row|
row_hash = row.to_hash
part = Part.new(
part_num: row_hash["part_num"],
description: row_hash["description"],
manufacturer: row_hash["manufacturer"],
model: row_hash["model"],
cage_code: row_hash["cage_code"],
nsn: row_hash["nsn"]
)
parts_db << part
end
Part.import parts_db
end
end
puts time
end
第一个问题
一旦您对一个大文件使用 File.read(file)
,您的脚本就会使用大量内存(可能太多)。您将整个文件读入 1 个巨大的字符串,即使 CSV
逐行读取它。
当您使用具有数千行的文件时,它可能工作正常。不过,您应该使用 CSV.foreach。
变化
CSV.parse(File.read(file), headers: true) do |row|
至
CSV.foreach(file, headers: true) do |row|
在 this 示例中,内存使用量从 1GB 变为 0.5MB。
第二题
parts_db
变成了一个巨大的零件数组,它一直在增长,直到 CSV 文件的最后。
您需要删除事务(导入会很慢,但不需要比 1 行更多的内存)或分批处理 CSV。
这是一种可行的方法。我们再次使用 CSV.parse
,但只使用 2000 行的批次:
def self.import_parts_db(filename)
time = Benchmark.measure do
File.open(filename) do |file|
headers = file.first
file.lazy.each_slice(2000) do |lines|
Part.transaction do
rows = CSV.parse(lines.join, write_headers: true, headers: headers)
parts_db = rows.map do |_row|
Part.new(
part_num: row_hash['part_num'],
description: row_hash['description'],
manufacturer: row_hash['manufacturer'],
model: row_hash['model'],
cage_code: row_hash['cage_code'],
nsn: row_hash['nsn']
)
end
Part.import parts_db
end
end
end
puts time
end
end
第三题?
之前的答案应该不会占用太多内存,但导入所有内容仍然需要很长时间,对于远程服务器来说可能太多了。
使用枚举器的优点是可以轻松跳过批次,只获取所需的批次。
假设您的导入时间太长,并且在 424000 次成功导入后由于某种原因停止。
你可以替换:
file.lazy.each_slice(2000) do |lines|
来自
file.lazy.drop(424_000).take(300_000).each_slice(2000) do |lines|
跳过前 424000 行 CSV 行,并解析接下来的 300000 行。
对于下一次导入,使用:
file.lazy.drop(424_000+300_000).take(300_000).each_slice(2000) do |lines|
然后:
file.lazy.drop(424_000+2*300_000).take(300_000).each_slice(2000) do |lines|
...
CSV.parse
非常高效,将一个解析的 CSV 行传递给执行处理的块。
问题不是来自 CSV 解析器,而是来自在内存中构建 parts_db
数组。我建议重写 Part.import
方法以逐行导入数据,而不是一次导入整个记录数组。
尝试不同的 CSV。有一个大约 30 兆的内存使用了剩余 8 GB 的 RAM,重新保存文件似乎解决了我的问题。
我正在尝试读取一个 5MM 行文件,现在它超出了我在 heroku 上分配的内存使用量。我的方法有点快~200 inserts/second..我相信它在导入时崩溃了..所以我的计划是分批导入 1,000 或 10,000。我的问题是如何告诉我我在文件的末尾,ruby 有一个 .eof
方法但它是一个 File
方法,我不知道如何调用它在我的循环中
def self.import_parts_db(file)
time = Benchmark.measure do
Part.transaction do
parts_db = []
CSV.parse(File.read(file), headers: true) do |row|
row_hash = row.to_hash
part = Part.new(
part_num: row_hash["part_num"],
description: row_hash["description"],
manufacturer: row_hash["manufacturer"],
model: row_hash["model"],
cage_code: row_hash["cage_code"],
nsn: row_hash["nsn"]
)
parts_db << part
end
Part.import parts_db
end
end
puts time
end
第一个问题
一旦您对一个大文件使用 File.read(file)
,您的脚本就会使用大量内存(可能太多)。您将整个文件读入 1 个巨大的字符串,即使 CSV
逐行读取它。
当您使用具有数千行的文件时,它可能工作正常。不过,您应该使用 CSV.foreach。 变化
CSV.parse(File.read(file), headers: true) do |row|
至
CSV.foreach(file, headers: true) do |row|
在 this 示例中,内存使用量从 1GB 变为 0.5MB。
第二题
parts_db
变成了一个巨大的零件数组,它一直在增长,直到 CSV 文件的最后。
您需要删除事务(导入会很慢,但不需要比 1 行更多的内存)或分批处理 CSV。
这是一种可行的方法。我们再次使用 CSV.parse
,但只使用 2000 行的批次:
def self.import_parts_db(filename)
time = Benchmark.measure do
File.open(filename) do |file|
headers = file.first
file.lazy.each_slice(2000) do |lines|
Part.transaction do
rows = CSV.parse(lines.join, write_headers: true, headers: headers)
parts_db = rows.map do |_row|
Part.new(
part_num: row_hash['part_num'],
description: row_hash['description'],
manufacturer: row_hash['manufacturer'],
model: row_hash['model'],
cage_code: row_hash['cage_code'],
nsn: row_hash['nsn']
)
end
Part.import parts_db
end
end
end
puts time
end
end
第三题?
之前的答案应该不会占用太多内存,但导入所有内容仍然需要很长时间,对于远程服务器来说可能太多了。
使用枚举器的优点是可以轻松跳过批次,只获取所需的批次。
假设您的导入时间太长,并且在 424000 次成功导入后由于某种原因停止。
你可以替换:
file.lazy.each_slice(2000) do |lines|
来自
file.lazy.drop(424_000).take(300_000).each_slice(2000) do |lines|
跳过前 424000 行 CSV 行,并解析接下来的 300000 行。
对于下一次导入,使用:
file.lazy.drop(424_000+300_000).take(300_000).each_slice(2000) do |lines|
然后:
file.lazy.drop(424_000+2*300_000).take(300_000).each_slice(2000) do |lines|
...
CSV.parse
非常高效,将一个解析的 CSV 行传递给执行处理的块。
问题不是来自 CSV 解析器,而是来自在内存中构建 parts_db
数组。我建议重写 Part.import
方法以逐行导入数据,而不是一次导入整个记录数组。
尝试不同的 CSV。有一个大约 30 兆的内存使用了剩余 8 GB 的 RAM,重新保存文件似乎解决了我的问题。