2

我正在尝试读取一个 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
4

3 回答 3

6

第一个问题

一旦您使用File.read(file)一个巨大的文件,您的脚本将使用大量内存(可能太多)。您将整个文件读入 1 个巨大的字符串,即使是CSV逐行读取。

当您使用具有数千行的文件时,它可能会正常工作。不过,您应该使用CSV.foreach。改变

 CSV.parse(File.read(file), headers: true) do |row|

CSV.foreach(file, headers: true) do |row|

示例中,内存使用量从 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

第三个问题?

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

使用 Enumerator 的优点是很容易跳过批次,只得到你想要的。

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

...

于 2016-12-09T20:34:08.300 回答
0

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

于 2016-12-09T20:34:33.247 回答
-3

尝试不同的 CSV。有一个大约 30 兆的内存,使用了 8 个剩余的内存,重新保存文件似乎已经纠正了我的问题。

于 2019-10-02T18:17:21.280 回答