Кейс: экспорт отчёта за год клал приложение по памяти на 200 строках
Эндпоинт отдавал годовой отчёт в Excel. На тесте с сотней строк работал за секунды. В проде на клиенте с высокой активностью — OutOfMemoryError на отчёте из двухсот тысяч строк, хотя heap был выставлен щедро.
Причина была не в объёме данных как таком, а в том, как их собирали перед отдачей. Сервис грузил весь результат запроса в List, затем строил из него Apache POI Workbook целиком в памяти, и только потом писал его в OutputStream ответа.
List rows = orderRepository.findAllForYear(year);
Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet();
for (OrderRow row : rows) { writeRow(sheet, row); }
workbook.write(response.getOutputStream());
Проблема тройная. Во-первых, весь результат запроса материализуется в List перед обработкой — вместо потоковой выборки из БД через курсор. Во-вторых, стандартный XSSFWorkbook держит всё дерево XML-объектов строки в памяти одновременно. В-третьих, ответ клиенту начинает писаться только после того, как весь Workbook готов — то есть пиковое потребление памяти складывается из всех трёх слоёв разом.
Решение — заменить XSSFWorkbook на SXSSFWorkbook, который держит в памяти окно из настраиваемого числа последних строк и сбрасывает остальные на диск, и заменить findAllForYear на потоковую выборку через Stream с курсором на уровне JDBC, чтобы не грузить весь список сразу.
Что спросят следом на разборе такого инцидента: «а почему просто не увеличили heap, если экспорт — редкая операция раз в год?» Ответ: увеличение heap решает конкретный случай с двумя сотнями тысяч строк, но не убирает архитектурную проблему — рост объёма данных клиента снова упрётся в лимит, только позже. Потоковая обработка не имеет верхней границы по объёму входных данных вообще.
OutOfMemoryError на экспорте почти никогда не про размер heap — почти всегда про то, что весь набор данных пытаются держать в памяти одновременно вместо потоковой обработки.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки