문제점
모든 대화방 조회, 검색 요청 시 n+1문제가 발생했다.
n+1 문제에 대해 내 서비스를 예로들어 설명해보겠다.

위는 현재 개선중인 서비스의 테이블 구조의 일부이다.
여러 QnA가 모여 하나의 대화방(Conversaiton)을 이루는 구조이고 대화방은 카테고리와 태그를 갖는다.
검색 기능의 흐름은 다음과 같다.
- QnA의 question 또는 answer의 키워드를 검색하여 해당될 경우 conversationId를 List로 저장한다.
- ID 리스트로 대화방 리스트를 조회한다.
- 최종응답 시 category와 tag 정보가 필요하기 때문에 category, tag 리스트도 조회한다.
여기서 데이터의 개수가 많아질수록 쿼리문도 많아진다.
- QnA select문 1개
- Conversation select문 n개
- category, tag select문 각각 n개씩

대화방 10개 조회 시 위와 같은 쿼리문이 발생한다.
이처럼 연관관계가 설정된 엔티티를 조회할 경우 데이터의 개수만큼 쿼리문이 추가로 발생하는 문제를 JPA n+1문제라고 한다.
해결
찾아본 해결방법은
- Join Fetch
- @EntityGraph
이렇게 두 가지였는데 conversation 엔티티는 category, tag 엔티티와 다대다 연관관계를 맺고 있기 때문에 conversation_category, conversation_tag 두 개의 중간 테이블과 OneToMany관계를 가진다.
여기서 문제는 위의 해결방법은 자식엔티티가 하나일 때만 적용가능하다는 것이다.
두 개 이상의 ToMany 관계의 엔티티를 불러오면 다음과 같이 MultipleBagFetchException 이 발생한다.

따라서 2개 이상의 자식엔티티를 조회하기 위해서는 1개의 자식엔티티는 join fetch 또는 @EntityGraph로 조회하고, 나머지 엔티티들은 hibernate.default_batch_fetch_size를 조절하여 조회해야한다.
Hibernate default_batch_fetch_size
BatchSize (Hibernate JavaDocs) (jboss.org)
Hibernate User Guide를 보면 @BatchSize를 통해 batch_size를 조절할 수 있다.

위처럼 size를 1000으로 설정하면 IN 문을 사용해 최대 1000개의 엔티티를 한번에 불러올 수 있다.
conversation과 category는 @EntityGraph로 즉시로딩하여 불러오고 tag는 hibernate.default_batch_fetch_size를 통해 IN문으로 한번에 불러온 결과 쿼리문은 다음과 같다.

더 알아볼 것
- hibernate.default_batch_fetch_size의 값이 매우 클 경우, 어떤 문제가 발생하는가
- 쿼리문의 개수는 확실히 줄어들었는데, 시간적 성능을 얼마나 향상되었는가
- join fetch는 inner join, @EntityGraph 는 outer join이라는 차이점이 존재
참고 :
티스토리, “JPA N+1 문제 및 해결방안”, https://jojoldu.tistory.com/165
Hibernate ORM User Guide, https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html
'프로젝트' 카테고리의 다른 글
| 지연 로딩(Lazy loading), 영속성 컨텍스트 (0) | 2023.06.18 |
|---|---|
| 대화방 제목/pin 수정 - fetchType과 DTO (0) | 2023.06.14 |
| 대화방 생성 - 중복된 sql 쿼리문 (0) | 2023.06.12 |
| 프로젝트 개선 (0) | 2023.06.09 |