join fetch вместе с постраничной выборкой
Чтобы убрать N+1 в списке заказов, разработчик добавил fetch-выборку коллекции прямо в постраничный запрос:
@Query("select o from Order o join fetch o.items where o.status = :status")
Page<Order> findByStatus(@Param("status") Status status, Pageable pageable);
На тестовом стенде с сотней заказов всё быстро. В проде, где под фильтр попадает несколько сотен тысяч заказов, эндпоинт начинает отвечать десятки секунд, растёт потребление памяти, а в логах Hibernate появляется предупреждение о применении пагинации в памяти. Что происходит?
- Пагинация корректно уходит в базу как LIMIT/OFFSET, а тормозит выгрузка позиций отдельными запросами — это остаточный N+1
- Page требует дополнительного count-запроса, и именно он тратит всё время; помогает замена Page на Slice
- Проблема в дубликатах заказов из-за join: достаточно добавить distinct, других последствий нет
- Из-за join fetch коллекции Hibernate не может применить LIMIT к строкам результата: он вычитывает всю выборку и режет страницу в памяти
