【问题标题】:CSV Parse Taking Too Much MemoryCSV 解析占用太多内存
【发布时间】:2017-04-25 08:05:41
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

      【解决方案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
      

      第三个问题?

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

      使用 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|
      

      ...

      【讨论】:

      • 但是上面你推荐了.foreach..在这种情况下它不起作用吗?
      • CSV.foreach 不返回 Enumerable,这是我们对 each_slice 所需要的。只要不使用File.read(),使用CSV.parse就可以。在第二种情况下,它仅适用于 2000 行。
      • 我使用了您的示例,在 424,000 次导入后,我从 heroku 控制台收到了 ETIMEDOUT: read ETIMEDOUT 错误。你知道我该如何解决吗?
      • 我没有使用 Heroku 的经验。这对stackoverflow.com/questions/15992689/…有帮助吗?
      • 我看到了,但我不认为是这样。我认为这是完成任务所需的时间。不过谢谢,你帮了大忙!
      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-10-14
        • 2013-07-18
        • 2013-07-11
        • 2020-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-26
        相关资源
        最近更新 更多