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|
...
Но выше вы рекомендовали '.foreach' .. это не сработало бы в этом случае? – gemart
'CSV.foreach' не возвращает Enumerable, что нам нужно для' each_slice'. Пока вы не используете 'File.read()', это нормально использовать 'CSV.parse'. Во втором случае это всего лишь 2000 строк. –
Я использовал ваш пример, и я получил сообщение ETIMEDOUT: read ETIMEDOUT' из консоли heroku после 424 000 Импорт. Вы знаете, как я могу обойти это? – gemart