2016-12-09 3 views
1

Я пытаюсь прочитать файл строки 5MM, и сейчас он превышает мое использование памяти alotted на heroku. Мой метод несколько быстрый ~ 200 вставок в секунду. Я считаю, что он рушится на импорт. Поэтому мой план заключался в том, чтобы импортировать партии в 1000 или 10000. Мой вопрос, как я могу сказать, что я в конце моего файла, рубин имеет метод .eof, но его метод File, и я не знаю, как назвать это в моем циклеCSV Parse Слишком много памяти

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

1 проблема

Как только вы используете File.read(file) с огромным файлом, ваш скрипт будет использовать много памяти (возможно, слишком много). Вы читаете весь файл в одну огромную строку, хотя CSV читает ее по строкам.

Это может сработать при использовании файлов с тысячами строк. Тем не менее, вы должны использовать CSV.foreach. Изменение

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

в

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

В this Например, использование памяти пошли от 1 Гб до 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 

3 проблемы?

В предыдущем ответе не должно использоваться много памяти, но все еще может потребоваться много времени, чтобы импортировать все, возможно, слишком много для удаленного сервера.

Преимущество использования 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| 

...

+0

Но выше вы рекомендовали '.foreach' .. это не сработало бы в этом случае? – gemart

+0

'CSV.foreach' не возвращает Enumerable, что нам нужно для' each_slice'. Пока вы не используете 'File.read()', это нормально использовать 'CSV.parse'. Во втором случае это всего лишь 2000 строк. –

+0

Я использовал ваш пример, и я получил сообщение ETIMEDOUT: read ETIMEDOUT' из консоли heroku после 424 000 Импорт. Вы знаете, как я могу обойти это? – gemart

0

CSV.parse довольно эффективен, передавая одну анализируемую строку CSV в блок, который обрабатывает. Проблема возникает не из анализатора CSV, а из массива parts_db в памяти. Я предлагаю переписать метод Part.import для импорта данных по строкам, а не сразу всего массива записей.

Смежные вопросы