Что делать если количество возвращаемых записей слишком велико и не помещается в оперативную память
Что проверяют
Понимаете ли вы, что результат не надо «уместить» в память, его надо не материализовать целиком. Знаете ли, что JDBC-драйвер по умолчанию вычитывает весь ответ в память, а Hibernate дополнительно держит все сущности в контексте персистентности.
Как отвечать
Сначала уточнить задачу: отдать данные наружу постранично или обработать пачкой на бэкенде. Решения разные.
- Обработка: курсорный обход. В JDBC это
setFetchSizeплюс однонаправленный курсор, причём в PostgreSQL серверный курсор включается только при выключенном автокоммите, иначе драйвер всё равно буферизует ответ целиком. - Spring Data умеет возвращать
Stream, но только внутри транзакции и с обязательным закрытием через try-with-resources. - Hibernate:
StatelessSessionлибоflush()иclear()каждые N записей, иначе сессия держит ссылки на всё загруженное и куча кончится независимо от размера выборки. - API: пагинация по ключу (
WHERE id > :last ORDER BY id LIMIT :n) вместоOFFSET. Большой офсет заставляет базу прочитать и выбросить всё, что до него, а при параллельных вставках строки дублируются между страницами. - Тянуть проекцию, а не сущность: только нужные колонки в DTO.
- Результат писать сразу в поток ответа или в файл, не собирая промежуточный список.
Где спотыкаются
Предлагают кэш. Положить огромную выборку в кэш значит перенести ту же память из временной в долгоживущую и получить нехватку позже, но надёжнее.
Выставляют размер выборки, а память всё равно кончается. Забыли про автокоммит, не учли, что драйверу MySQL нужен особый режим потоковой выдачи, или в конце цепочки всё равно стоит сбор в список.
Называют асинхронность способом сэкономить память. Вынос запроса в отдельный поток не уменьшает объём данных ни на байт, а несколько параллельных выгрузок умножают расход.