현재 코드는 프론트엔드에서 모임 목록 페이지에 접근하면 1번 페이지 열리면서 가장 최근의 Room 데이터 10개 호출해서 렌더링한다.
Room 테이블은 HostUser, Category, RoomImage 테이블과 관계 맺고 있는 상태다. Dto 변환시 각 테이블 데이터가 필요하기에 10개 데이터 호출되면 각 테이블데이터까지 포함해서 약 40번 쿼리 발생했다. 문제다.
1개의 데이터 호출시 Fetch Join 사용하면 연관된 테이블 쿼리를 1번으로 압축 시킬 수 있었다. findAll도 가능할 것이라 예상했다. 아래 처럼 서비스와 레포지토리 코드를 수정 후 실행했다. 하지만 에러 발생했다.
Fetch Join는 4개의 연관 엔티티 데이터를 가져오고, Pagenation은 10개의 로우 데이터를 Limit 등 제한 조건적용해서 데이터를 가져온다. 하지만 위 두 기능에서 데이터 로딩때 데이터 중복 및 예상되지 않은 데이터 꼬임 및 성능저하 발생할 수 있다고 한다.
그런 이유로 Jpa에서는 두 기능 함께 사용을 미지원 및 상황에 따라서 제한한다고 한다. 그럼 어떻게 하면 좋을까?
//service
@Override
public List<RoomDto> getRoomList(Integer page, Integer size) {
PageRequest pageRequest = PageRequest.of(page-1, size, Sort.by("id").descending());
Page<Room> roomsPage = roomRepository.findAllWithCategoryAndHostUsersAndImage(pageRequest);
List<Room> rooms = roomsPage.getContent();
if (rooms.isEmpty()) {
return new ArrayList<>();
}
List<RoomDto> roomDtos = rooms.stream().map(r -> roomMapper.toRoomDto(r)).collect(Collectors.toList());
return roomDtos;
}
//repository
@Query("SELECT DISTINCT r FROM Room r " +
"LEFT JOIN FETCH r.category " +
"LEFT JOIN FETCH r.hostUserList " +
"LEFT JOIN FETCH r.roomImage"
)
Page<Room> findAllWithCategoryAndHostUsersAndImage(Pageable pageable);
먼저 떠오른 생각은 모든 데이터 다 호출 후 Page구분 하려고 했다. 문제는 데이터가 쌓일수록 문제도 쌓일 것이라 판단했다.
결국 사용한 방법은 Fetch Join을 빼고 application-dev.yml에 Batch 값을 10으로 셋팅했다. 최대 여러 번 쿼리를 한 번에 묶어서 요청하는 방식인데 한 페이지에 10개씩 데이터 필요해서 10으로 할당했다.
이 후 Room 목록 10개 조회하니 40번의 쿼리는 4번으로 줄었다.(page로 인해 생긴 Count 쿼리 추가하면 5번)
임시적으로 이전보다는 쿼리 최적화 시켰다.
할 수 있으면 1번의 쿼리로도 가능할까 싶은데 아직은 잘 모르겠다. 나중에 알게되면 그 방법 사용할 계획이다.
큰 도움이 되었습니다, 감사합니다.