Выгрузка CSV на 2 м...
 
Уведомления
Очистить всё

Выгрузка CSV на 2 млн строк раздувает воркер до 3 ГБ

6 Сообщения
5 Пользователи
8 Реакции
275 Просмотры
georgiy_efi
Сообщения: 7
Автор темы
Разобрался с блоками middle-разработчик
Присоединился: 1 год назад
[#101]

Ruby 3.4, Rails, MySQL 8. Выгрузка заказов за год в CSV для бухгалтерии идёт фоновой джобой. Пока было 300 тысяч строк, жила спокойно, сейчас 2 млн, процесс воркера раздувается до 3 ГБ, и его убивает OOM. Декабрь, в четыре уже темно, все хотят годовые выгрузки, а у меня падает. Код:

csv = CSV.generate do |rows|
  rows << %w[id number total created_at]
  Order.where(created_at: Time.zone.local(2025).all_year).each do |o|
    rows << [o.id, o.number, o.total, o.created_at]
  end
end
File.write(path, csv)

Что всё в памяти, понимаю, но что из этого самое тяжёлое?


5 ответов
TimurK62
Сообщения: 31
Читает чужие PR бэкенд-разработчик
Присоединился: 3 года назад

Там три потребителя. .each без батчей грузит все 2 млн объектов Active Record разом, каждый объект это сотни байт плюс строки атрибутов. Потом вся CSV-строка целиком в памяти. Потом ещё копия при записи. Я бы писал в файл сразу построчно и брал из базы только нужные колонки пачками:

CSV.open(path, "w") do |csv|
  csv << %w[id number total created_at]
  Order.where(created_at: Time.zone.local(2025).all_year)
       .in_batches(of: 5_000) do |batch|
    batch.pluck(:id, :number, :total, :created_at).each { |row| csv << row }
  end
end

pluck вообще не создаёт объектов моделей, только массивы. До правки снимите memory_profiler на 50 тысячах строк, чтобы было с чем сравнить.


Ответ
Валерий40
Сообщения: 33
Читает чужие PR фулстек-разработчик
Присоединился: 4 года назад

В Laravel для такого был lazy() поверх чанков, тут in_batches по сути то же самое


Ответ
georgiy_efi
Сообщения: 7
Автор темы
Разобрался с блоками middle-разработчик
Присоединился: 1 год назад

Переписал по примеру Тимура. На 2 млн строк воркер держится в районе 180 МБ, выгрузка идёт 2 минуты 40 секунд, а раньше падала на девятой минуте. memory_profiler на 50 тысячах показал 410 МБ выделенной памяти до правки и 23 МБ после.


Ответ
kostya.sokurov
Сообщения: 73
Держит прод бэкенд-разработчик, senior
Присоединился: 4 года назад

Про in_batches одна мелочь: он ходит по первичному ключу, каждый раз WHERE id > последний, это быстро. А если добавить свой order, он его проигнорирует и только предупредит в логе. Если нужна сортировка по дате, проще выгружать помесячно.


Ответ
vladislav2000
Сообщения: 2
Только поставил Ruby data engineer
Присоединился: 1 год назад

а бухгалтерия файл на 2 млн строк чем открывает?) табличные редакторы обычно на миллионе с небольшим строк и заканчиваются


Ответ