2016-12-09 2 views
1

私は5MMラインファイルを読み込もうとしていますが、今はherokuで私のメモリ使用量を超えています。私の方法は〜200人/秒というやや速いです。私はそれがインポートでクラッシュしていると信じています。私の計画は1,000または10,000のバッチでインポートすることでした。私は私が私のファイルの末尾だと言うのですか私の質問があり、ルビーは.eof方法が、そのFile方法を持っている、と私はCSV解析が多すぎるメモリを取る

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 

答えて

2

第一私のループ内でそれを呼び出すするかどうかはわかりません問題

巨大なファイルでFile.read(file)を使用すると、スクリプトは多量のメモリを使用します(多すぎる可能性があります)。 CSVが行単位で読み込んでも、ファイル全体を1つの巨大な文字列に読み込む。

何千もの行のファイルを使用すると正常に動作することがあります。それでも、CSV.foreachを使用してください。 this例で

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

へ 変更

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

、メモリ使用量が1ギガバイトから0.5メガバイトに行ってきました。

第二の問題は、

parts_dbは、CSVファイルの非常に最後まで成長し続けている部品の巨大な配列、になります。 トランザクションを削除する必要があります(読み込み速度は遅くなりますが、1行分以上のメモリは必要ありません)。または、CSVをバッチ処理します。

これは1つの可能性があります。

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 

第三の問題:我々は唯一の2000行のバッチで、再びCSV.parseを使うのか?

前回の回答ではメモリをあまり使用しないでください。ただし、すべてをインポートするには時間がかかりすぎる可能性があります。

列挙子を使用する利点は、バッチをスキップして必要なものだけを簡単に取得できることです。

インポートが長時間かかるとしたら、424000回のインポートが成功した後に何らかの理由で停止するとします。

あなたは置き換えることができます:

file.lazy.each_slice(2000) do |lines| 

file.lazy.drop(424_000).take(300_000).each_slice(2000) do |lines| 

することにより、第1 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| 

...

+0

しかし、あなたは上記の例では '.foreach'を推奨しています。 – gemart

+0

'CSV.foreach'は' each_slice'に必要なEnumerableを返しません。 'File.read()'を使わない限り、 'CSV.parse'を使っても構いません。 2番目のケースでは、2000行のみです。 –

+0

私はあなたの例を使用しました。私は、424,000回のインポート後に英雄コンソールから 'ETIMEDOUT:read ETIMEDOUT'エラーを受け取りました。私はそれをどのように乗り越えることができるか知っていますか? – gemart

0

CSV.parseは、処理するブロックに1つの解析済みCSVラインを渡すと、かなり効率的です。 この問題は、CSVパーサーからのものではなく、メモリ内にparts_dbアレイを構築することから発生します。私はPart.importメソッドを書き直して、一度にレコードの配列全体ではなく、行ごとにデータをインポートすることをお勧めします。

関連する問題