• 티스토리 홈
starryeye
  • 프로필사진
    starryeye
    • 분류 전체보기 (189)
      • C++ (17)
      • Java (24)
      • OOP (5)
      • Spring Reactive Stack (12)
        • Reactive Streams (3)
        • Netty (4)
        • Reactor (1)
        • Webflux (3)
        • DB, Cache 연동 (1)
      • Spring (90)
        • Core (17)
        • MVC (33)
        • Client (2)
        • Security (4)
        • DB, Cache 연동 (33)
      • DataBase (12)
        • RDBMS (2)
        • NoSQL (10)
      • Message Broker (6)
      • Web (4)
      • Network (4)
      • 대규모 시스템 설계 (15)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • Spring Data JPA (Hibernate), N + 1 Query Problem
        2024년 02월 20일
        • starryeye
        • 작성자
        • 2024.02.20.:18
        반응형

        N + 1 문제가 일어나는 모든 경우의 수에 대해 알아보고..

        한번 정리를 해보겠다.

         

         

        N + 1 Query problem ?

        어떤 엔티티를 조회하기 위해 쿼리(1)를 수행했는데

        예상치 못한 쿼리들(N)이 추가로 수행되는 상황을 말한다.

        주로 일대다, 다대일 관계에서 발생한다.

         

        이번 포스팅에서 사용할 DB 다이어그램이다.

        Team 이 여러 Member 를 가지는 관계이다.

         

         

        N + 1 조회 문제가 일어나는 케이스를 살펴보겠다.

         

        글로벌 페치 전략이 즉시 로딩(Eager loading) 인 경우

        JPQL 을 이용하여 어떤 엔티티를 조회 할 경우 발생한다.

        -> 글로벌 페치 전략이 즉시 로딩이지만, 사용자의 쿼리(JPQL) 가 우선이기 때문에

        JPQL 을 충실히 수행후,

        글로벌 페치 전략에 의해 연관관계에 있는 엔티티나 컬렉션 엔티티가

        사용 여부와 상관 없이 조회되는 상황이다.

         

        예시

        더보기

        ManyToOne 예시

        Member 를 조회(1) 하면 즉시 로딩에 의해

        Member 에 연관된 Team 엔티티가 Member 조회 결과 갯수(N) 만큼 추가로 조회 된다.

        -> 영속성 컨텍스트에 조회하므로, Team 을 조회하다가

        동일한 Team 이 필요하면 1 차 캐시에서 조회되어 N 보다 작은 값 만큼 조회 될 수 도 있다.

        (Member Table 에는 team_id 가 존재하므로 1차 캐시에서 식별 가능)

         

        OneToMany 예시

        Team 을 조회(1) 하면 즉시 로딩에 의해

        Team 에 연관된 Member 컬렉션 엔티티가 Team 조회 결과 갯수(N) 만큼 추가로 조회 된다.

        -> Team 에 속한 Member 들이 한번에 조회되는 방식

        (Team Table 에는 member 에 대한 식별자가 없으므로 N 보다 작은 값 만큼 조회 될 수 없을 듯..)

         

        주의 사항

        Spring Data JPA 사용 시, findById() 빼고는 모두...(?)

        JPQL 을 생성하여 조회하는 것이므로

        N + 1 문제가 발생한다. (메서드 이름 조회, findAll(), 등등)

         

        참고

        Spring Data JPA 사용 시, findById() 에서는

        연관된 엔티티나 컬렉션 엔티티를 함께 조회하는 쿼리로 수행된다.

        이 때는 N + 1 문제가 일어나지 않는다.

        (EntityManager::find 사용 됨)

         

         

        글로벌 페치 전략이 지연 로딩(Lazy loading) 인 경우

        어떤 엔티티를 조회 후, 연관된 엔티티나 컬렉션 엔티티를 실제 사용할 때

        지연 로딩이 발생하는데 이 경우도 N + 1 문제이다.

        -> 글로벌 페치 전략이 지연 로딩이므로, 어떤 엔티티를 조회 하더라도

        연관 관계에 있는 엔티티나 컬렉션 엔티티는 프록시로 할당하여 반환된다.

        그리고 연관 관계에 있는 엔티티나 컬렉션 엔티티를 실제 사용할 때

        프록시가 초기화 된다. (지연 로딩, 쿼리 수행됨)

         

        예시

        더보기

        ManyToOne 예시

        Member 를 조회(1) 하고 Team 을 사용할 때 지연 로딩에 의해

        Member 에 연관된 Team 엔티티가 Member 조회 결과 갯수(N) 만큼 추가로 조회 된다.

        -> 영속성 컨텍스트에 조회하므로, Team 을 조회하다가

        동일한 Team 이 필요하면 1 차 캐시에서 조회되어 N 보다 작은 값 만큼 조회 될 수 도 있다.

        (Member Table 에는 team_id 가 존재하므로 1차 캐시에서 식별 가능)

         

        OneToMany 예시

        Team 을 조회(1) 하고 Member 를 사용할 때 지연 로딩에 의해

        Team 에 연관된 Member 컬렉션 엔티티가 Team 조회 결과 갯수(N) 만큼 추가로 조회 된다.

        -> Team 에 속한 Member 들이 한번에 조회되는 방식

        (Team Table 에는 member 에 대한 식별자가 없으므로 N 보다 작은 값 만큼 조회 될 수 없을 듯..)

         

        해결 법 1. 페치 조인(or @EntityGraph) 사용

        JPQL 의 join fetch 를 사용하여

        연관 관계에 있는 엔티티나 컬렉션 엔티티도 함께 조회(영속화) 한다.

        N + 1 개의 쿼리가 1 개의 쿼리로 최적화 된다.

         

        주의 사항 1

        컬렉션 엔티티를 페치 조인 하면 페이징 쿼리를 사용할 수 없다.

        페이징 쿼리로 수행되지 않고 전체 쿼리가 수행된 후

        메모리에서 페이징 된다.

         

        주의 사항 2

        컬렉션 엔티티를 둘 이상 한번에 페치 조인 하면

        MultipleBagFetchException 이 발생한다.

         

        주의 사항 3

        DB 의 결과와 헷갈려서 join fetch 를 사용하지 않고

        일반적인 join 을 사용하면 join 대상은 영속화 되지 않는다.

         

        참고 1

        Hibernate 6 버전 부터는 fetch join 사용 시,

        쿼리에 자동으로 distinct 가 추가되어 중복 엔티티를 걸러준다.

         

        참고 2

        @EntityGraph 를 사용하면 left outer join 을 사용하게 된다.

         

        해결 법 2. Hibernate Batch Size 옵션 사용

        어떤 글로벌 페치 전략이든 N + 1 문제가 생길 수 있는데

        N + 1 개의 쿼리가 1 + 1 개의 쿼리로 최적화 될 수 있다.

         

        단일 설정은 @org.hibernate.annotations.BatchSize

        글로벌 설정은 spring.jpa.properties.hibernate.default_batch_fetch_size

        을 이용하며, size 를 설정하여 한번에 몇개의 쿼리(N)를 1개의 쿼리로 합칠 것인지

        설정할 수 있다.

         

        해결 법 3. Hibernate SubSelect 기능 사용

        서브 쿼리를 사용하여 N + 1 개의 쿼리를 1 + 1 개의 쿼리로 최적화 한다.

         

        적용하고 싶은 연관 관계 컬렉션 엔티티에 아래 어노테이션을 적용한다.

        @org.hibernate.annotations.Fetch(FetchMode.SUBSELCT)

         

        주의 사항

        ManyToOne 관계에서는 사용하지 못한다.

         

         

        정리

        1. 즉시 로딩은 사용하지 않는 것이 좋다.

        -> 연관 관계에 있는 엔티티나 컬렉션 엔티티를 항상 사용하는 것이 아니기 때문

         

        2. 모두 지연 로딩으로 설정하고 필요하면 페치 조인을 사용하자

        -> 영속성 컨텍스트 범위를 벗어나면 지연 로딩을 수행하지 못하므로 

        페치 조인을 이용한 성능 최적화를 챙기면서 미리 로딩을 하는 게 좋다.

         

        3. 페치 조인을 사용하지 못하는 케이스일 경우엔 Batch 옵션을 활용하자

         

        반응형

        'Spring > DB, Cache 연동' 카테고리의 다른 글

        DBCP 설정 값 정하기  (0) 2024.05.01
        DBCP (HikariCP)  (0) 2024.04.22
        Spring 과 JPA  (0) 2024.02.14
        JPA 등록, 기본 키 생성 전략  (0) 2023.06.19
        JPA 변경 감지와 플러시  (0) 2023.06.15
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바