CSV解析占用太多内存

时间:2016-12-09 19:07:16

标签: ruby-on-rails ruby csv activerecord activerecord-import

我试图读取一个5MM的行文件,现在它超过了我在heroku上的内存使用量。我的方法有点快〜约200插入/秒..我相信它在导入时崩溃..所以我的计划是批量导入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

3 个答案:

答案 0 :(得分:2)

第一个问题

只要将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

第3个问题?

上一个答案不应该占用太多内存,但导入所有内容仍然需要很长时间,对于远程服务器来说可能太多了。

使用枚举器的优点是可以轻松跳过批次,并获得您想要的批次。

假设您的导入时间太长,并且在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|

...

答案 1 :(得分:0)

CSV.parse非常有效,将一条已解析的CSV行传递给执行处理的块。 问题不是来自CSV解析器,而是来自内存中构建parts_db数组。我建议重写Part.import方法逐行导入数据,而不是一次导入整个数组。

答案 2 :(得分:-1)

尝试其他CSV。如果有一个大约30兆的内存使用了剩余的8个RAM,重新保存该文件似乎已解决了我的问题。