Open dtd1614 opened 1 year ago
정리를 해보자면, @EntityGraph를 활용한 fetch join을 사용할 수 있는 경우는 반환 형태가 단일, List 등이어야 합니다.
하지만, Page를 반환할 경우에 조심해야 할 점이 컬렉션 fetch join과 함께 사용되면 컬렉션을 기준으로 페이징을 하는 것을 방지하기 위해 스프링에서 쿼리에 limit을 포함시키지 않고 메모리 상에 리스트 전체를 올려버리고 애플리케이션 level에서 페이징을 진행합니다.
이로써 저희는 성능을 위한 우선 순위를 정할 수 있게 되었습니다.
더 좋은 의견이 있으시다면 코멘트 달아주세욥
Post 목록 불러오기 기능은 페이징 처리와 함께 각 Post마다 PostFile의 존재 여부를 표시해야 합니다. 그리고 해당 Post를 작성한 Member의 이메일도 표시해야 합니다.
그런데 Post 목록을 모두 불러와서 하나 하나 PostFile 존재 여부와 Member의 이메일을 조회한다면, 하나의 Post마다 2개의 쿼리가 추가로 발생할 것입니다. Post가 10개가 있으면 PostFile을 조회하는 10개의 쿼리와 Member를 조회하는 10개의 쿼리가 발생하여 총 20개의 쿼리가 추가로 발생하는 것입니다.
그래서 fetch join을 적용하여 쿼리가 한번만 나가도록 하였습니다.
Post와 Member는 다대일 관계고, Post와 PostFile은 일대다 관계입니다. 둘 다 lazy 로딩으로 설정되어 있습니다.
EntityGraph를 사용해 findAll할 때 Member와 PostFile을 fetch join하도록 설정하였습니다. 게시글 목록을 불러올 때 작성자의 이메일도 표시해야 하기 때문에 Member도 fetch join 하도록 하였습니다.
다음과 같은 테스트 코드를 통해서 fetch join이 잘 되는지 확인해보겠습니다. DB에는 20개의 Post가 저장되어 있는 상태입니다.
fetch join이 정상적으로 잘되어 PostFile이나 Member를 조회할 때 추가 쿼리가 발생하지 않는 것을 알 수 있습니다. 마지막 쿼리는 페이징을 위한 count 쿼리입니다.
그런데 맨 위에 HHH000104 경고가 떴습니다. 찾아보니 컬렉션 fetch join을 할 때, 데이터를 모두 가져와서 인메모리에 저장하고, 그 다음에 페이징 처리를 수행하기 때문에 발생하는 경고였습니다. 첫번째 쿼리를 보면 limit이 설정되어 있지 않습니다. 페이징 처리를 안하고 데이터를 모두 가져왔다는 의미입니다. 컬렉션 join을 하면 일대다에서 다를 기준으로 row가 생성되기 때문에 데이터가 예측할 수 없이 증가합니다. 그런데 그 데이터를 페이징하면 다를 기준으로 페이징이 되기 때문에 제대로 페이징이 될 수 없습니다. 그래서 일단 페이징을 하지 않고 모든 데이터를 메모리에 올린 다음 페이징을 시도합니다. 그런데 이 데이터의 크기가 크면 out of memory가 발생할 수 있습니다.
참고 : https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85#1-pagination https://choiyeonho903.tistory.com/155
참고 자료를 통해 컬렉션은 fetch join을 하지 않고 lazy 로딩으로 설정하되, batch fetch size를 설정하여 해결할 수 있다는 사실을 알았습니다.
fetch join에 컬렉션인 PostFile은 적용하지 않습니다. 이렇게 해서 PostFile은 lazy 로딩으로 불러옵니다.
yml 파일에 다음과 같이 batch fetch size를 설정합니다. 이렇게 하면 lazy로딩인 컬렉션을 조회할 때 IN 쿼리로 100개씩 한번에 조회하게 됩니다.
이제 다시 테스트 코드를 실행시키고 쿼리를 확인해보겠습니다.
인메모리로 저장한다는 경고는 뜨지 않습니다. 첫번째 쿼리문을 보면 limit이 설정되어 모든 데이터를 불러오지 않고 페이징을 적용하여 데이터를 불러온 것을 볼 수 있습니다. 그리고 두번째 쿼리문을 보면 IN 쿼리를 통해 한번에 PostFile을 조회하는 것을 볼 수 있습니다. 설정한 batch fetch size보다 Post가 많으면 쿼리문이 더 발생할 것입니다.
fetch join보다는 쿼리문이 더 나가긴 하지만, out of memory의 위험성을 줄일 수 있는 해결방안이었습니다.
참고하셔 나중에 페이징과 컬렉션 fetch join을 같이 해야 할 경우가 있다면 도움이 되셨으면 좋겠습니다. 더 좋은 방법이 있다면 알려주세요.
참고 : https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85#1-pagination https://choiyeonho903.tistory.com/155