rails - Экспорт огромного CSV-файла занимает всю оперативную память в производстве - PullRequest
0 голосов
/ 16 октября 2018

Таким образом, мое приложение экспортирует файл CSV размером 11,5 МБ и использует практически всю оперативную память, которая никогда не освобождается.

Данные для CSV берутся из БД, и в вышеупомянутом случае все этоэкспортируется.

Я использую стандартную библиотеку CSV Ruby 2.4.1 следующим образом:

export_helper.rb:

CSV.open('full_report.csv', 'wb', encoding: UTF-8) do |file|
  data = Model.scope1(param).scope2(param).includes(:model1, :model2)
  data.each do |item|
    file << [
      item.method1,
      item.method2,
      item.methid3
    ]
  end
  # repeat for other models - approx. 5 other similar loops
end

и затем в контроллере:

generator = ExportHelper::ReportGenerator.new
generator.full_report
respond_to do |format|
  format.csv do
    send_file(
      "#{Rails.root}/full_report.csv",
      filename: 'full_report.csv',
      type: :csv,
      disposition: :attachment
    )
  end
end

После одного запроса процессы puma загружают 55% оперативной памяти всего сервера и остаются такими до тех пор, пока в конце концов не исчерпают память.

Например, в этой статье для создания файла CSV с размером строки в 75 миллионов строк требуется только 1 МБ ОЗУ.Но нет никаких запросов к БД.

Сервер имеет 1015 МБ ОЗУ + 400 МБ подкачки памяти.

Итак, мои вопросы:

  • Что именно потребляет столько памяти?Это генерация CSV или связь с БД?
  • Я делаю что-то не так и пропускаю утечку памяти?Или это просто как работает библиотека?
  • Есть ли способ освободить память без перезапуска работников puma?

Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 16 октября 2018

Помимо использования find_each , вы должны попробовать запустить код ReportGenerator в фоновом режиме с ActiveJob .Поскольку фоновые задания выполняются в отдельных процессах, при их уничтожении память возвращается в ОС.

Таким образом, вы можете попробовать что-то вроде этого:

  • Пользователь запрашивает некоторый отчет (CSV, PDF, Excel)
  • Некоторые контроллеры запрашивают ReportGeneratorJob, ипользователю отображается подтверждение
  • Задание выполнено и отправлено электронное письмо со ссылкой / файлом для загрузки.
0 голосов
/ 16 октября 2018

Вместо each вы должны использовать find_each, что специально для таких случаев, как это, потому что он будет создавать экземпляры моделей в пакетах и ​​впоследствии их выпускать, тогда как each будет создавать все экземплярыиз них сразу.

CSV.open('full_report.csv', 'wb', encoding: UTF-8) do |file|
  Model.scope1(param).find_each do |item|
    file << [
      item.method1
    ]
  end
end

Кроме того, перед записью в браузер вы должны передавать CSV-файл вместо записи в память или на диск:

format.csv do
  headers["Content-Type"] = "text/csv"
  headers["Content-disposition"] = "attachment; filename=\"full_report.csv\""

  # streaming_headers
  # nginx doc: Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications
  headers['X-Accel-Buffering'] = 'no'
  headers["Cache-Control"] ||= "no-cache"
  headers.delete("Content-Length")
  response.status = 200

  header = ['Method 1', 'Method 2']
  csv_options = { col_sep: ";" }

  csv_enumerator = Enumerator.new do |y|
    y << CSV::Row.new(header, header).to_s(csv_options)
    Model.scope1(param).find_each do |item|
      y << CSV::Row.new(header, [item.method1, item.method2]).to_s(csv_options)
    end
  end

  # setting the body to an enumerator, rails will iterate this enumerator
  self.response_body = csv_enumerator
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...