개발/Project

JPA N+1 문제 성능 최적화하기(1)

라니킴 2023. 1. 19. 20:53

조회하는 부분에서 속도가 너무 느려서 성능 최적화를 하기 위해 query를 자세히 살펴보았다.

역시 N+1문제가 발생하고 있었다.

 

N+1문제란?

JPA로 애플리케이션을 개발할 때 성능상 가장 주의해야 하는 문제다. 
처음 조회한 데이터 수만큼 다시 SQL을 사용해서 조회하는 것이다. 
예를 들면 회원이 5명이면 회원에 따른 주문도 5번 조회된다.
N+1이 발생하면 SQL이 상당히 많이 호출되므로 조회 성능에 치명적이다.

 


지연로딩으로 모두 설정해놔서 N+1문제에서 자유로울 것이라고 생각했는데  N+1 문제는 즉시 로딩과 지연 로딩일 때 모두 발생할 수 있다고 한다.

 

개선한 부분

1. 주변 식당 리스트 조회

  • join fetch 적용 전

  • join fetch 적용 후

식당조회시 리뷰까지 한 번에 받아오는 것을 볼 수 있다. 

 

2. 마이페이지-내가 찜한 식당리스트

  • 수정 전 
  • List<RestaurantLikes> findAllByUser(User user, Pageable pageable);

 

작성한 식당 개수만큼 쿼리가 나가고 있었다.

  • 1차 수정 후
 @Query("select r from RestaurantLikes r join fetch r.restaurant where r.user = :user")
    List<RestaurantLikes> findAllByUser(@Param(value = "user")User user, Pageable pageable);

식당에 연관된 리뷰에서는 여전히 N+1 문제가 발생했다.

fetch join을 또 사용해봤지만 오류가 났다. fetch join을 쓰는 데도 조건이 있었다.

* JPA에서 Fetch Join의 사용 조건

- ToOne은 몇개든 사용 가능 

- ToMany는 1개만 가능

⇒ fetch join 두 개 사용할 수 없다. 

 

  • 2차 수정 후

Restaurant Entity에 추가

@org.hibernate.annotations.BatchSize(size = 100)
@OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL)
List<Review> reviews = new ArrayList<>();

해당 옵션은 지정된 수만큼 in절에 부모key를 사용하게 해준다.

즉, 1000개를 옵션값으로 지정하면 1000개 단위로 in절에 부모 Key가 넘어가서 자식 엔티티들이 조회되는 것이다.(글로벌로도 설정 가능하다.)

where 부분 query가 변경된 걸 볼 수 있었다.

3. 모임 상세페이지 조회

  • 수정 전

모임 Entity는 참가자와 댓글 Entity와 mapping 되어 있다. 모임 참가자와 모임에 딸린 댓글 개수만큼 query가 나가는 것을 볼 수 있다.

  • 수정 후

findById로 meeting을 조회하던 부분을 위와 같이 query를 추가해줬다. 


🤣성능개선 방법 정리 

1. Fetch Join을 이용해 최대한의 성능 튜닝을 진행하고

2. Fetch Join으로 해결이 안되는 조회 쿼리에 대해서는 default_batch_fetch_size 옵션으로 최소한의 성능을 보장해준다.

global로 적용시 application.yml 파일에 추가해주면 된다. 

N+1 문제가 발생하는 곳이 많아서 global로 적용되도록 수정했다. 

Refactoring하면서 N+1문제가 발생하는 곳을 찾아서 모두 개선하여 성능최적화를 할 예정이다.