본문 바로가기
프로젝트

JPA n+1 문제

by backwards 2023. 6. 21.

 

문제점

모든 대화방 조회, 검색 요청 시 n+1문제가 발생했다.

 

n+1 문제에 대해 내 서비스를 예로들어 설명해보겠다.

 

 

위는 현재 개선중인 서비스의 테이블 구조의 일부이다.

여러 QnA가 모여 하나의 대화방(Conversaiton)을 이루는 구조이고 대화방은 카테고리와 태그를 갖는다.

 

검색 기능의 흐름은 다음과 같다.

  1. QnA의 question 또는 answer의 키워드를 검색하여 해당될 경우 conversationId를 List로 저장한다.
  2. ID 리스트로 대화방 리스트를 조회한다.
  3. 최종응답 시 category와 tag 정보가 필요하기 때문에 category, tag 리스트도 조회한다.

 

여기서 데이터의 개수가 많아질수록 쿼리문도 많아진다.

  1. QnA select문 1개
  2. Conversation select문 n개
  3. category, tag select문 각각 n개씩

대화방 10개 조회 시 쿼리문

대화방 10개 조회 시 위와 같은 쿼리문이 발생한다.

이처럼 연관관계가 설정된 엔티티를 조회할 경우 데이터의 개수만큼 쿼리문이 추가로 발생하는 문제를 JPA n+1문제라고 한다.

 


해결

찾아본 해결방법은

  1. Join Fetch
  2. @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문으로 한번에 불러온 결과 쿼리문은 다음과 같다.

n+1 문제 해결된 쿼리문


 

더 알아볼 것

  • 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