ruby-on-rails - CSV 解析占用太多内存

标签 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

最佳答案

第一个问题

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

当您使用具有数千行的文件时,它可能会正常工作。不过,您应该使用 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|

...

关于ruby-on-rails - CSV 解析占用太多内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41067281/

相关文章:

ruby-on-rails - 更新时唯一性的 rails 验证

ruby-on-rails - docker sh : 1: yarn: not found

node.js - 在 node.js 中读取 csv 文件的内容

ruby-on-rails - 从多个不同的 Rails 模型创建 "feeds"

ruby-on-rails - 在Rails中记录默认路由的使用

Ruby 类、包含和作用域

ruby - 有没有办法设置$的值?在 Ruby 的模拟中?

ruby - 如何在 Ruby 中对数组中的对象的属性求和

shell - 减去相应的行

python - 使用 4 个参数对数据集进行聚类和标记