Генерация больших excel файлов

Генерация больших excel файлов

Приступим

Для генерации excel файлов мы чаще всего используем roo или axlsx/caxlsx. Они прекрасно работают, имеют огромный функционал и активное комьюнити. Но иногда для счастья этого оказывается мало.

В качестве источника данных возьмем готовые таблицы:

1) users - 38832 записей
2) comments - 2728260 записей

Попробуем сгенерировать файл при помощи caxlsx:

p = Axlsx::Package.new
wb = p.workbook
wb.add_worksheet do |sheet|
  User.dataset.paged_each do |row|
    sheet.add_row row.values.values
  end
end
stream = p.to_stream
File.open('users.xlsx', 'wb') { |f| f.write(stream.read) }
  • Файл успешно сгенерировался, потребление оперативной памяти составило ~100 MB.
  • Теперь попробуем прогнать большую таблицу comments. К сожалению, нам не хватило ОЗУ, и OOM Killer прибил нас.

Первое, что приходит на ум - решить проблему инкрементальной записью чанков в файл, как мы это сделали бы в случае с csv:

file = File.open('users.csv', 'w')
User.dataset.paged_each do |row|
  file.write row.values.values.join(";")
end
file.close

Но после того, как я увидел структуру файла .xlsx, у меня появились сомнения.

Генерация больших excel файлов

Эксперименты

Я изучил много инфы, stackoverflow утверждал, что нельзя построчно записывать в файл, но решил, что решение должно быть и я не один с этим столкнулся. Переключился на структуру файлов и поиск возможных решений на других языках. В итоге наткнулся на Libxlsxwriter на С, потом на статью и даже на гем.

Давайте посмотрим, что из этого вышло:

  • caxlsx
users - 91.05 MB
RAM USAGE (start): 446696 K
RAM USAGE (end): 539936 K

comments - ??? MB
RAM USAGE (start): 444460 K
RAM USAGE (end): Killed (кончилась память)
  • xlsxwriter:
users - 38.42 MB
RAM USAGE (start): 444292 K
RAM USAGE (end): 483636 K

comments - 1597.96 MB
RAM USAGE (start): 443776 K
RAM USAGE (end): 2080096 K

Уже лучше, но 1.5GB тоже много. Решил посмотреть, что можно с этим сделать и нашел опцию - constant_memory.

  • xlsxwriter with constant_memory:
users - 8.26 MB
RAM USAGE (start): 445720 K
RAM USAGE (end): 454184 K

comments - 8.28 MB
RAM USAGE (start): 445780 K
RAM USAGE (end): 454260 K

Результат налицо: теперь генерация файла любого объема будет потреблять ~8 MB ОЗУ. Если сравнивать функциональность этого гема с axlsx, то xlsxwriter не имеет возможности считывать данные из текущего состояния и не имеет никакой интеграции с рельсами. Но я все равно считаю это отличным бустом производительности.

Полезные ссылки