모의 면접(N + 1 문제)

개발 공부 중·2022년 10월 13일
0

Spring

목록 보기
8/8
  1. N+1 문제란?

연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다. 이를 N+1 문제라고 한다.

조회 시 1개의 쿼리를 생각하고 설계를 했으나 나오지 않아도 되는 조회의 쿼리가 N개가 더 발생하는 문제.

  1. 발생 이유

N+1 문제가 발생하는 이유는 JPA가 JPQL을 분석해서 SQL을 생성할 때는 글로벌 Fetch 전략을 참고하지 않고 오직 JPQL 자체만을 사용한다.

JPA Repository로 find 시 실행하는 첫 쿼리에서 하위 엔티티까지 한 번에 가져오지 않고, 하위 엔티티를 사용할 때 추가로 조회하기 때문

JPQL은 기본적으로 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문

  1. 해결 방법

해결 방법에는 여러 방법들이 있지만 FetchJoin과 EntityGraph 두 가지 방법을 알아보도록 하겠다.

  • 일반 조인
    • Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화
    • 조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색조건에는 필요한 경우에 주로 사용됨
  • 패치 조인
    • 조회의 주체가 되는 Entity 이외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 하여 모두 영속화
    • Fetch Join이 걸린 Entity 모두 영속화하기 때문에 FetchType이 Lazy인 Entity를 참조하더라도
      이미 영속성 컨텍스트에 들어있기 때문에 따로 쿼리가 실행되지 않은 채로 N+1문제가 해결됨

(※영속 : 사전적 의미는 영원한 것과 같은 의미, https://dev-monkey-dugi.tistory.com/131 영속에 대해서는 이 자료를 참고하면 좋을 것 같다.)

  • 패치 조인(Fetch Join)
    미리 두 테이블을 JOIN 하여 한 번에 모든 데이터를 가져올 수 있다면 애초에 N+1 문제가 발생하지 않을 것이다.

    • 패치 조인(Fetch Join)의 단점
      • 쿼리 한번에 모든 데이터를 가져오기 때문에 JPA가 제공하는 Paging API 사용 불가능(Pageable 사용 불가)
      • 1:N 관계가 두 개 이상인 경우 사용 불가
      • 패치 조인 대상에게 별칭(as) 부여 불가능
      • 번거롭게 쿼리문을 작성해야 함
  • @Entity Graph
    @EntityGraph 의 attributePaths는 같이 조회할 연관 엔티티명을 적으면 된다. ,(콤마)를 통해 여러 개를 줄 수도 있다.
    Fetch join과 동일하게 JPQL을 사용해 Query문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다.

    • Fetch join의 경우 inner join을 하는 반면에 @EntityGraph는 outer join을 기본으로 한다.(기본적으로 outer join 보다 inner join이 성능 최적화에 더 유리하다.)

(여담으로 Fetch type은 default로 ~ToMany에서는 Lazy, ~ToOne에서는 Eager로 지정되어있는데 이런 부분들은 default옵션을 사용한다고 하더라도 명시해주는 것이 협업하는 다른 개발자가 보기에도 좋습니다.)

  1. Fetch Join과 @EntityGraph 사용시 주의할 점

FetchJoin과 EntityGraph는 공통적으로 카테시안 곱(Cartesian Product)이 발생 하여 중복이 생길 수 있다.
(※ 카테시안 곱 : 두 테이블 사이에 유효 join 조건을 적지 않았을 때 해당 테이블에 대한 모든 데이터를 전부 결합하여 테이블에 존재하는 행 갯수를 곱한만큼의 결과 값이 반환되는 것)

  • 중복 발생 문제를 해결하기 위한 방법
    • JPQL에 DISTINCT 를 추가하여 중복 제거
    • OneToMany 필드 타입을 Set으로 선언하여 중복 제거
      (Set은 순서가 보장되지 않는 특징이 있지만, 순서 보장이 필요한 경우 LinkedHashSet을 사용하자.)

※ 1번의 쿼리를 할 때 연관관계가 맺어진 Entity의 n개의 데이터를 조회하기 때문에 N + 1의 문제가 발생한다. 이 때, fetch join으로 한 번에 조회로 가능하다.
(개인적으로 N + 1 보다는 1 + N 이 맞는 말 같다.)

참고 자료 :
https://programmer93.tistory.com/83
https://dev-coco.tistory.com/165
https://cobbybb.tistory.com/18
https://dev-monkey-dugi.tistory.com/131
https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85

profile
개발 공부 중

0개의 댓글