ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring JPA] JPA N+1 문제
    Spring JPA 2024. 10. 13. 20:07

    N + 1 문제는 하나의 쿼리를 통해 데이터를 조회할 때, 연관된 엔티티들을 지연 로딩(Lazy Loading) 또는 즉시 로딩(Eager Loading)으로 추가 조회하면서, 의도하지 않은 N개의 추가 쿼리가 실행되는 현상을 의미한다.

     

    언제 발생할까?

     

    • JPA Repository를 사용하여 엔티티 조회 메서드를 호출할 때, 주로 SELECT(READ) 쿼리에서 발생합니다.

     

    누가 발생 시킬까?

     

    • 1 대 다(1:N) 또는 다 대 1(N:1) 연관 관계를 가진 엔티티를 조회할 때 발생합니다.
    • 주로 Fetch 전략에 의해 발생합니다. 특히 EAGER와 LAZY 로딩 전략에 따라 다르게 발생합니다.

     

    어떤 상황에서 발생할까?

     

    1. EAGER Fetch 전략을 사용하여 데이터를 조회하는 경우:

    • 연관된 엔티티를 즉시 조회하므로, 첫 번째 조회 쿼리 외에도 연관된 하위 엔티티들을 위한 추가 쿼리가 발생합니다.

    2. LAZY Fetch 전략을 사용하여 데이터를 조회하고, 이후 해당 연관 관계 엔티티를 사용할 때:

    • 초기에는 연관된 엔티티를 조회하지 않지만, 연관된 엔티티에 접근하는 순간 추가 쿼리가 실행되어 N + 1 문제가 발생할 수 있습니다.

     

    도대체 왜 발생하는가?

     

    • JPA Repository에서 find 메서드로 엔티티를 조회할 때, JPA는 기본적으로 설정된 Fetch 전략에 따라 데이터를 조회합니다.
    • 연관된 하위 엔티티를 즉시 가져오지 않고, 필요할 때 추가적으로 조회하는 경우, 이로 인해 예상치 못한 N개의 추가 쿼리가 발생하게 됩니다.
    • 또한, JPQL은 기본적으로 Fetch 전략을 무시하고, JPQL에 정의된 것만 가지고 SQL을 생성하기 때문에 발생할 수 있습니다.

     

    Fetch 전략에 따른 문제

     

    1. EAGER Fetch 전략일 때:

    • JPQL이 생성한 SQL을 통해 첫 번째 쿼리에서 상위 엔티티(1)를 조회합니다.
    • 이후 JPA가 EAGER 전략에 의해 해당 엔티티의 연관된 하위 엔티티들(N)을 즉시 조회합니다.
    • 이 과정에서 N + 1 문제가 발생할 수 있습니다.

     

    2. LAZY Fetch 전략일 때:

    • JPQL이 생성한 SQL을 통해 상위 엔티티(1)를 조회하지만, 하위 엔티티는 지연 로딩(Lazy)되므로 추가 조회는 발생하지 않습니다.
    • 그러나 하위 엔티티에 접근하는 순간, 해당 하위 엔티티에 대한 추가 쿼리가 발생하게 되므로, 이때도 N + 1 문제가 발생할 수 있습니다.

     

    문제 해결 방법

     

    1. Fetch Join 사용

    • JPQL 또는 QueryDSL을 사용할 때, 연관된 엔티티들을 Fetch Join으로 함께 조회하면 N + 1 문제를 방지할 수 있다

    왜냐하면 N + 1 발생하는 이유가 한쪽 테이블만 조회하고 연결된 다른 테이블은 따로 조회하기 때문이다 

    fetch join으로 함께 조회하면 문제가 발생하지 않을 것이다

    SELECT i FROM Information i JOIN FETCH i.greatInformation

     

    하지만 단점이 있다

    1. 패치조인은 한번에 연관된 테이블을 가져오는 형식이라 연관된 엔티티의 수가 많을 경우에 쿼리가 복잡하면 성능이 저하된다
    2. 페이징 처리가 불가능하다
    3. 연관관계가 다대일, 일대다 관계면 하위 엔티티가 여러개라서 상위 엔티티 데이터가 중복 조회가 일어 날 수 있다

    다대다 관계일때는 패치 조인을 피하는게 더 나을 것 같다

     

     

    2. Entity Grapgh 사용

    • 특정 조회 시점에 EntityGraph를 통해 연관 엔티티를 함께 조회하도록 설정할 수 있다
    @EntityGraph(attributePaths = {"greatInformation"})

     

    장점:

    • Fetch Join보다 더 선택적으로 연관된 엔티티를 함께 조회할 수 있습니다.
    • 코드 가독성이 향상되며, 필요한 시점에만 추가적인 엔티티를 가져오므로 유연성이 높습니다.

    단점:

    • Eager Loading처럼 동작할 수 있기 때문에 불필요한 엔티티까지 함께 가져오는 상황이 발생할 수 있습니다.
    • 모든 관계를 일괄적으로 설정할 수 없고, 조회할 때마다 세밀하게 설정해야 하는 부담이 있습니다.

     

     

    3. 배치 사이즈 설정

    • @BatchSize를 설정하여 한번에 가져오는 엔티티 수를 조정함으로써 추가적인 쿼리의 발생을 줄일 수 있다
    @BatchSize(size = 100)

     

    장점:

    • 성능 최적화에 큰 도움이 됩니다. 연관된 엔티티들을 일정량씩 배치로 가져오기 때문에 성능 저하를 방지할 수 있습니다.
    • N + 1 문제를 해결하면서도 추가적인 쿼리 수를 제어할 수 있습니다.

    단점:

    • 설정된 배치 크기에 따라 성능이 좌우될 수 있습니다. 배치 사이즈가 너무 작으면 성능에 크게 영향을 주지 않으며, 너무 크면 메모리 사용량이 늘어날 수 있습니다.

     

Designed by Tistory.