민팽로그

[JPA] N+1 문제 본문

프로젝트/감성 스팟 가이드(2022 한이음)

[JPA] N+1 문제

민팽 2022. 7. 31. 18:55

저번 프로젝트를 진행할 땐 N+1 문제를 신경쓰지 않았다. 나중에 공부를 하다보니 이런 문제가 발생할 수 있구나 정도로만 알고 있었다.

그리고 이번 프로젝트를 진행하면서 sql 쿼리를 콘솔에서 확인하다보니, 의도한 것보다 select 쿼리가 훨씬 많이 발생하고 있다는 것을 발견하고 N+1 문제를 정리해보게 되었다.

 

N+1 문제란?

1. 즉시 로딩과 지연 로딩

  • 즉시 로딩: 연관 관계에 있는 엔티티를 사용하지 않아도 한번에 바로 로딩. 비어있는 내용은 프록시 객체로 대신함.
  • 지연 로딩: 연관 관계에 있는 엔티티를 바로 로딩하지 않고 사용하는 시점에서 로딩

연관된 모든 엔티티를 매번 즉시 로딩하는 것은 좋은 설계가 아니다. 해당 엔티티의 조회가 필요할 때마다 조회하는게 좋다. 

 

2. 기본 패치 전략

  • @ManyToOne, @OneToOne: FetchType.EAGER(즉시 로딩)
  • @OneToMany: FetchType.LAZY(지연 로딩)

실제로 사용할 땐 꼭 필요한 상황이 아니라면 가능한 모든 연관 관계 패치 전략을 지연 로딩으로 설정하는게 좋다.

 

3. N+1 문제

예시로 들 엔티티는 Spot과 SpotTag이고 1:N 관계이다. 패치 전략은 지연 로딩을 사용했다.

ERD 상으로 이런 상태

SpotTag 테이블의 상위 5개 데이터를 조회하는 테스트 코드를 아래와 같이 작성했다.

@Test
@DisplayName("N+1 문제 발생 테스트")
void testSpotTagRepository() {
    List<SpotTag> spotTags = spotTagRepository.findTop5By();

    for (SpotTag spotTag : spotTags) {
        System.out.println("---- spotTag.spot 조회: " + spotTag.getSpot().getSpotName());

    }
}

 

결과 쿼리를 보면 1개가 아니라 총 6개의 쿼리가 발생했다.

맨 위 분홍색 네모박스에 해당하는 쿼리를 보면, Spot 테이블과 조인되지 않는 것을 확인할 수 있다. 지연 로딩 전략을 사용했기 때문에 연관 관계에 있는 Spot 엔티티에 대한 정보는 가지고 오지 않았기 때문이다.

이 상황에서 Spot 엔티티의 데이터를 가져오려 했기 때문에 파란색 네모박스처럼 5개의 추가 select 쿼리가 발생한다. 즉 원래 의도한 쿼리 1개 + 5개의 조회 건수에 대한 추가 쿼리가 발생하여 총 6개의 쿼리가 발생하는 것이다. 이러한 문제가 N+1 문제이다.

 

해결 방법

연관관계에 있는 엔티티에 접근할 필요가 없다면 접근하지 않는 것이 기본이다. 접근이 필요하다면 패치조인, @EntityGraph 어노테이션 사용 등의 방법으로 N+1 문제를 해결할 수 있다.

 

1. 패치 조인

패치 조인은 JPQL에서 성능 최적화를 위해 지원하는 조인이다. 연관된 엔티티까지 함께 가져올 수 있다.

패치 조인은 다음과 같이 사용한다.

 

테스트 코드를 다시 실행하고 결과를 확인해 보니,

이전과는 다르게 inner join을 통해 Spot테이블과 SpotTag 테이블을 조인한 후 모두 select한다.

 

만약 패치 조인을 사용하지 않고 일반 조인을 사용한다면 어떻게 될까?

아래와 같이 쿼리를 수정하고 결과를 확인해보자.

 

실행 결과 조인은 되었지만 select 절을 보면 Spot의 컬럼은 포함되어 있지 않다. 또한 N+1 문제가 다시 발생하는 것을 확인할 수 있다.

물론 이 방법으로도 DTO를 사용해 프로젝션 해준다면 N+1 문제를 해결할 수 있지만 유연성이 떨어져 효율적이지 않다.

 

2. @EntityGraph

이 어노테이션을 사용하여 N+1 문제를 해결할 수 있지만 패치 조인과 차이점은 left outer 조인이라는 것이다. 

위와 같이 사용한다.

 

결과 쿼리를 보면 left outer join으로 조인하며 select 절에 Spot과 SpotTag의 컬럼이 모두 포함되어 있는 것을 확인할 수 있다. N+1 문제 또한 발생하고 있지 않다.

 

또한 위와 같이 이름을 붙여주고 사용할 수도 있다.

 

reference

https://www.youtube.com/watch?v=MpXdx8-qWzo&t=596s

 

Comments