2016-12-09 6 views
1

Ich versuche, eine 5MM-Zeile Datei zu lesen, und jetzt ist es überschritten meine gespeicherte Speicherbelegung auf Heroku. Meine Methode ist etwas schnell ~ 200 Einsätze/Sekunde .. Ich glaube, es stürzt auf den Import .. Also war mein Plan, in Chargen von 1.000 oder 10.000 zu importieren. Meine Frage ist, wie kann ich sagen, dass ich am Ende meiner Datei bin, hat Rubin eine .eof Methode, aber es ist eine File Methode, und ich bin nicht sicher, wie esCSV-Analyse zu viel Speicher

in meiner Schleife nennen
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 

Antwort

2

ersten Problem

Sobald Sie File.read(file) mit einer großen Datei verwenden, wird Ihr Skript eine Menge Speicher (möglicherweise zu viel) verwenden. Sie lesen die gesamte Datei in eine riesige Zeichenfolge, obwohl CSV Zeile für Zeile gelesen wird.

Es kann gut funktionieren, wenn Sie Dateien mit Tausenden von Zeilen verwenden. Trotzdem sollten Sie CSV.foreach verwenden.

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

zu

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

In this Beispiel ging die Speicherauslastung zu 0,5 MB von 1 GB.

zweites Problem

parts_db wird zu einem großen Array von Teilen, die bis zum Ende der CSV-Datei wachsen. Sie müssen entweder die Transaktion entfernen (der Import ist langsam, erfordert aber nicht mehr Speicher als für 1 Zeile) oder die CSV-Verarbeitung in Stapeln durchführen.

Hier ist eine Möglichkeit, es zu tun. Wir verwenden CSV.parse wieder, aber nur mit Chargen von 2000 Zeilen:

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. Problem?

Die vorherige Antwort sollte nicht viel Speicher verwenden, aber es könnte noch lange dauern, alles zu importieren, möglicherweise zu viel für einen Remote-Server.

Der Vorteil der Verwendung eines Enumerators besteht darin, dass es einfach ist, Stapel zu überspringen und nur die gewünschten zu erhalten.

Angenommen, Ihr Import dauert zu lange und wird aus irgendeinem Grund nach 424000 erfolgreichen Importen beendet.

Sie können ersetzen:

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

von

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

die ersten 424.000 CSV Zeilen zu überspringen, und die nächsten 300000 diejenigen analysieren.

Für den nächsten Import verwenden:

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

und dann:

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

...

+0

Aber oben wurde empfohlen '.foreach' .. würde es in diesem Fall nicht funktionieren? – gemart

+0

'CSV.foreach' gibt kein Enumerable zurück, was wir für' each_slice' benötigen. Solange Sie 'File.read()' nicht verwenden, können Sie 'CSV.parse' verwenden. Im zweiten Fall ist es nur für 2000 Zeilen. –

+0

Ich habe dein Beispiel benutzt und nach 424.000 Importen habe ich einen 'ETIMEDOUT: read ETIMEDOUT' Fehler von der heroku Konsole bekommen. Weißt du, wie ich das umgehen kann? – gemart

0

CSV.parse ist ziemlich effizient und übergibt eine geparste CSV-Zeile an den Block, der die Verarbeitung durchführt. Das Problem kommt nicht aus dem CSV-Parser, es stammt aus dem Aufbau des parts_db Array im Speicher. Ich schlage vor, die Part.import Methode neu zu schreiben, um die Daten Zeile für Zeile statt einer ganzen Reihe von Datensätzen gleichzeitig zu importieren.

Verwandte Themen