ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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)

    1. JPQL에서 만든 SQL을 통해 데이터 조회
    2. JPA에서 Fetch 전략을 가지고 해당 데이터의 연관관계인 하위 엔티티들을 추가 조회
    3. N + 1 문제 발생

    지연 로딩인 경우(LAZY)

    1. JPQL에서 만든 SQL을 통해 데이터 조회
    2. JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않는다.
    3. 연관관계인 다른 엔티티를 가지고 작업하면 추가 조회가 발생하여 결국 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 구문으로 변경되어 실행된다.

     

    단점

    1. JPA가 제공하는 pageable 기능을 사용할 수 없다.
    2. 중복된 데이터가 존재할 수 있다 (카테시안 곱이 발생)
      • Dintinct를 사용해 중복된 데이터를 조회하지 않게 할 수 있다.
    3. 지연로딩이 의미가 없다.
    4. 쿼리문을 작성해야 한다.

    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

    https://programmer93.tistory.com/83

    https://lng1982.tistory.com/297

    '🌱 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

    댓글

Designed by Tistory.