본문 바로가기
JPA

JPA - N+1 문제와 해결방법 정리

by devLog by Ronnie's 2022. 8. 9.

들어가며


JPA를 사용하다 보면 N+1 문제를 한번씩은 겪어봤을것이다.

하지만 콘솔창을 자세히 보지 않았다면 그냥 지나쳤을 수도 있다.

N+1의 문제가 발생한다고 해서 결과에 문제가 생기는 것은 아니지만 성능에는 문제가 생길 수 있기 때문에 확실히 인지해서 문제를 해결하는 것이 좋다.

 

 

N+1 문제란 무엇인가?


연관관계가 설정된 엔티티를 조회할 때 조회 된 데이터의 갯수만큼 조회 쿼리가 추가로 발생하는 경우는 말한다. 이해를 돕기 위해 예를 들면 팀과 회원의 연관관계에서 팀을 조회했을때 팀에 속한 회원이 10명 있다고 가정할때 팀을 조회할때 연관관계를 가지고 있는 회원을 조회하는 쿼리가 10번(n번) 발생하는 것을 의미한다.

 

N+1 == 10번(회원) + 1번(팀)

 

 

Fetch 모드에 따른 결과 (즉시 로딩과 지연 로딩)


연관관계 설정 시 즉시 로딩과 지연 로딩 설정에 따른 차이점이 있을까?

 

먼저 즉시 로딩에 경우를 살펴보자.

 

즉시로딩

  • 즉시 로딩 (EAGER) 으로 설정한 경우 findAll() 호출 시 N+1 문제가 발생하여 연관된 회원이 10명이면 10번의 회원 조회 쿼리가 발생한다.

 

지연 로딩

  • 지연 로딩 (LAZY)로 설정한 경우 findAll()을 호출 시 N+1 문제가 발생하지 않는 것처럼 보인다.
  • 이유는 지연 로딩의 특성으로 회원에 대한 접근이 없으면 프록시 객체를 통하여 조회를 미루기 때문
  • 이후 회원 객체에 접근하게 되면 N+1 문제가 발생되게 된다.

 

즉시로딩의 경우 흐름 (지연 로딩도 회원에 사용 시점만 차이있을뿐 흐름 같음)

  1. findAll() 호출한 순간 select 문을 JPQL 구문이 생성되고 해당 구문을 하이버네이트가 분석하여 SQL문을 생성하여 실행된다.
  2. DB 결과를 받아 엔티티의 인스턴스들을 생성한다.
  3. 팀과 연관된 회원 엔티티도 로딩을 한다.
  4. 영속성 컨텍스트에서 연관된 회원을 있는지 확인
  5. 없다면 2번에서 만들어진 팀 인스턴스들 개수에 맞게 회원을 찾는 SQL 문이 생성된다 -> N+1 문제 발생 시점

 

 

N+1 문제의 발생 이유


JPA가 JPQL을 분석해서 SQL을 생성할 떄는 글로벌 Fetch 전략을 참고하지 않고 오직 JPQL 자체만을 사용하기 때문이다. N+1 문제가 별거 아닐거 같지만 만약 관련 데이터의 개수가 10개가 아닌 몇십만 몇백만개라고 가정하면 서버 성능에 따라 크게 부하를 줄 수 있다

 

 

해결 방법


Fetch Join, EntityGraph 어노테이션, Batch Size 등의 방법이 존재한다.

 

Fetch Join

Fetch Join 방법은 JPQL을 사용하여 디비에서 데이터를 가져올 때 처음부터 연관된 데이터까지 같이 가져오게 하는 방법이다. 그러니 N+1의 문제가 발생하지 않는다. SQL의 Join을 생각하면 이해하기 쉽다. (SQL문의 inner join 구문으로 실행됨)

 

EntityGraph

EntityGraph 어노테이션 방식도 해당 어노테이션으로 관계를 정의해서 fetch 조인을 하는 것, 해당 방법 사용시 간단한 관계에서는 상관없지만 관계가 복잡해질수록 설계가 어렵다는 단점이 존재한다.

 

BatchSize

해당 방식은 쿼리문을 수정하는 것은 아니고 설정한 사이즈 값만큼 쿼리를 in절로 실행하는 방식이다.

 

 

 

댓글