JPA N+1 문제와 해결 방법
08 Jun 2024N+1 문제란?
- ORM 기술에서 특정 객체를 대상으로 수행한 쿼리가 해당 객체가 가지고 있는 연관관계 또한 조회하게 되면서 N번의 추가적인 쿼리가 발생하는 문제를 말한다.
- 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상
- 연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다.
- 즉, 1번의 쿼리를 날렸을 때 의도하지 않은 N번의 쿼리가 추가적으로 실행되는 것이다. 이를 N+1 문제라고 한다.
발생하는 이유
- N+1문제가 발생하는 근본적인 원인은 관계형 데이터베이스와 객체지향 언어간의 패러다임 차이로 인해 발생한다.
- 객체는 연관관계를 통해 레퍼런스를 가지고 있으면 언제든지 메모리 내에서 Random Access를 통해 연관 객체에 접근할 수 있지만 RDB의 경우 Select 쿼리를 통해서만 조회할 수 있기 때문이다.
- JPA Repository로 find 시 실행하는 첫 쿼리에서 하위 엔티티까지 한 번에 가져오지 않고, 하위 엔티티를 사용할 때 추가로 조회하고 JPQL은 기본적으로 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문에 발생한다.
발생하는 상황
- JPA Repository를 활용해 인터페이스 메소드를 호출할 때(Read 시)
- 1:N 또는 N:1 관계를 가진 엔티티를 조회할 때 발생
- JPA Fetch 전략이 EAGER 전략으로 데이터를 조회하는 경우
- JPA Fetch 전략이 LAZY 전략으로 데이터를 가져온 이후에 연관 관계인 하위 엔티티를 다시 조회하는 경우
- 즉시로딩인 경우
- JPQL에서 만든 SQL을 통해 데이터를 조회
- 이후 JPA에서 Fetch 전략을 가지고 해당 데이터의 연관 관계인 하위 엔티티들을 추가 조회
- 2번 과정으로 N + 1 문제 발생
- LAZY(지연 로딩)인 경우
- JPQL에서 만든 SQL을 통해 데이터를 조회
- JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않음
- 하지만, 하위 엔티티를 가지고 작업하게 되면 추가 조회가 발생하기 때문에 결국 N + 1 문제 발생
Sample Code
먼저 Member와 Team 엔티티를 생성하자
문제 상황을 확인하기 위해 setup으로 Team 10개, Member 10개를 저장하고 결과를 조회해보자.
-> 원하는 결과는 쿼리 1번인데 team에 해당하는 member에 대한 쿼리 10번이 추가로 나간다.
이러한 문제를 Fetch.MODE를 Eager에서 Lazy로 바꾸면 해결되는지 알아보자.
Lazy로 바꾸어도 해결되지 않는 모습을 볼 수 있다.
해결방법
그렇다면 N+1 문제를 어떻게 해결할 수 있을까?
방법은 4가지 정도가 있다.
- Fetch Join
- Entity Graph Annotation
- Subselect
- Batch Size
Fetch Join
JPA Fetch Join을 사용하면 부모와 자식 관계의 엔티티를 한 번에 조회하므로 N+1 문제를 해결할 수 있다.
EntityGraph Annotation
EntityGraph는 JPA에서 특정 엔티티의 연관된 속성들을 미리 로딩할 수 있게 도와주는 기능이다.
이 방식은 쿼리 메서드에서 연관된 엔티티를 명시적으로 지정하여 Lazy Loading을 사용하더라도 필요한 연관 엔티티를 한 번에 로드할 수 있다.
FetchMode.SUBSELECT
FetchMode.SUBSELECT는 서브쿼리 방식으로 연관된 엔티티들을 조회한다.
연관된 자식 엔티티들을 SELECT 쿼리로 여러 번 조회하지 않고, 부모 엔티티에 대한 조회가 끝난 후, 추가적인 서브쿼리를 한 번 실행하여 자식 엔티티들을 모두 가져온다.
Batch Size
Batch Size는 여러 개의 엔티티를 한 번에 배치 처리하는 방법이다.
Batch Size를 설정하면, Lazy Loading 방식으로 연관된 엔티티들을 N개씩 묶어서 한 번의 쿼리로 가져오게 된다.
Batch Size를 설정하는 방법은 직접 엔티티에 @BatchSize 어노테이션으로 적용하는 방법이 있고, application.yml 파일에 적용하는 방법이 있다.