-
JPA - N + 1 문제🌱 spring 2022. 10. 11. 01:44
💡 연관관계가 설정된 엔티티를 조회했을 때 조회하려는 엔티티와 연관관계를 맺고 있는 엔티티들의 수만큼 추가로 쿼리가 N번 실행된다.
- 1번의 쿼리를 날렸을 때 의도하지 않은 N번의 쿼리가 추가 실행
이러한 현상을 N+1 문제라고 한다
즉시 로딩과 지연 로딩 둘다 N+1 문제가 발생한다.
- 지연 로딩은 N+1 문제가 발생하지 않는 것처럼 보이지만 막상 개게를 조회하려고 하면 N+1 문제가 발생한다
- 결국 발생되는 시점만 다를 뿐이다.
발생
상황
- 1 : N 또는 N : 1 관계를 가진 엔티티 조회시 발생
언제
- Fetch 전략이 EAGER일 때 데이터를 조회하는 경우
- Fetch 전략이 LAZY일 때 데이터를 가져온 이후 연관관계인 다른 엔티티를 다시 조회하는 경우
왜
- JPQL은 기본적으로 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문
즉시 로딩인 경우(EAGER)
- JPQL에서 만든 SQL을 통해 데이터 조회
- JPA에서 Fetch 전략을 가지고 해당 데이터의 연관관계인 하위 엔티티들을 추가 조회
- ⇒ N + 1 문제 발생
지연 로딩인 경우(LAZY)
- JPQL에서 만든 SQL을 통해 데이터 조회
- JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않는다.
- 연관관계인 다른 엔티티를 가지고 작업하면 추가 조회가 발생하여 결국 N + 1 문제 발생
해결 방법
1. Fetch Join
JPQL을 사용하여 DB에서 데이터를 가져올 때 처음부터 연관된 데이터까지 같이 가져오게 하는 방법
- 미리 테이블을 JOIN 하여 한 번에 모든 데이터를 가져오자
- 쿼리가 1번만 발생한다.
public interface StoreRepository extends JpaRepository<Store, Long> { @Query("select s from Store s join fetch s.foods") List<Store> findAllFetchJoin(); }
⇒ 별도 지정을 하지 않으면 JPQL에서 join fetch 구문은 SQL의 inner join 구문으로 변경되어 실행된다.
단점
- JPA가 제공하는 pageable 기능을 사용할 수 없다.
- 중복된 데이터가 존재할 수 있다 (카테시안 곱이 발생)
- Dintinct를 사용해 중복된 데이터를 조회하지 않게 할 수 있다.
- 지연로딩이 의미가 없다.
- 쿼리문을 작성해야 한다.
2. @EntityGraph
어노테이션을 통해 fetch 조인을 한다
@EntityGraphdml attributePaths는 같이 조회할 연관 엔티티명을 적으면 된다.
- ‘,’ 을 통해 여러개를 줄 수 있다.
Fetch Join과 동일하게 JPQL을 사용해 Query문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다.
@EntityGraph(attributePaths = {"foods"}) @Query("select DISTINCT s Store s") List<Store> findAllEntityGraph();
3. Batch Size
Lazy Loading + Batch Size
N + 1 문제가 일어나지 않도록 하는 방법은 아니고 N + 1 문제가 발생하더라도
select * from store where food_id = ?
위 방식이 아닌 아래 방식으로 N + 1 문제가 발생하게 하는 방법
select * from store where food_id in (?, ?, ?)
이런 방식을 사용하면 batch size 만큼 일어날 N + 1 문제를 1번만 더 조회하는 방식으로 성능을 최적화 할 수 있다.
spring: jpa: properties: hibernate: default_batch_fetch_size: 1000
따라서 실제 연관관계의 엔티티가 필요한 시점에 BatchSize를 통해 최소한의 쿼리를 보내 효율적으로 지연로딩을 할 수 있다.
Batch size는 얼마나 해야 하나 ❓
Batch size가 너무 크면 한 번에 너무 많은 엔티티가 메모리에 로딩된다.
- 프로그램에 오류가 발생할 수 있다.
Batch size가 너무 작으면 더 많은 쿼리가 나가야 한다.
따라서, 100 - 1000개 수준이 적당하다고 한다.
참고 자료
https://ojt90902.tistory.com/640
'🌱 spring' 카테고리의 다른 글
Filter, Interceptor (0) 2022.10.11 OSIV 🧐 (0) 2022.10.11 영속성 컨텍스트 (0) 2022.10.11 JPA와 Hibernate, Spring data JPA (0) 2022.10.10 Spock 로 테스트 해보기 (0) 2022.10.06