
Приступим
Для генерации 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(";") + "\n"
end
file.close
Но после того, как я увидел структуру файла .xlsx, у меня появились сомнения.

Эксперименты
Я изучил много инфы, Stack Overflow утверждал, что построчно записывать в файл нельзя, но решил, что решение должно быть, и я не один с этим столкнулся. Переключился на структуру файлов и поиск возможных решений на других языках. В итоге наткнулся на Libxlsxwriter на C, потом на статью и даже на гем.
Давайте посмотрим, что из этого вышло:
- 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,5 ГБ — тоже много. Решил посмотреть, что можно с этим сделать, и нашел опцию — 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 не имеет возможности считывать данные из текущего состояния и не имеет никакой интеграции с рельсами. Но я все равно считаю это отличным бустом производительности.