article thumbnail image
Published 2022. 5. 27. 11:44

프로젝트에서 spring-boot JPA 사용  N+1 문제가 발생해서 기록합니다.

Spring JPA(ORM)의 N+1이란 ? 

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

 

DBMS 툴을 이용해 직접 쿼리문을 만들어 조회할 때는 물론 하나의 쿼리가 발생하겠지만 mybatis, 넘어서는 JPA가 등장함에 따라 자동화된 쿼리문들이 생겨나면서 어쩔 수 없이 발생하는 문제입니다. JPA의 경우에는 객체에 대해서 조회한다고 해도 다양한 연관관계들의 매핑에 의해서 관계가 맺어진 다른 객체가 함께 조회되는 경우에 N+1이 발생하게 됩니다.

케이스들에 대해서 아래에서 이야기하겠지만 지금 간단하게 예를 들면, 유저 한명이 쓴 게시글들을 조회할 때 유저-게시글을 join한 형태의 쿼리문을 원했지만 N개의 게시글을 또 조회하는 쿼리가 날아가는 경우가 있을 수 있겠습니다.

 

N+1 쿼리 문제의 원인

Spring Data JPA에서 제공하는 Repository의 ‘findAll()’, ‘findById()’ 등과 같은 메소드를 사용하면 바로 DB에 SQL 쿼리를 날리는 것이 아닙니다. JPQL이라는 객체지향 쿼리 언어를 생성, 실행시킨 후 JPA는 이것을 분석해서 SQL을 생성, 실행하 는 동작에서 N+1 쿼리 문제가 발생합니다. JPQL 입장에서는 LAZY 로딩, EAGER 로딩과 같은 글로벌 패치 전략을 신경쓰지 않고 JPQL만 사용해서 SQL을 생성합니다.

*JPQL이란 플랫폼에 독립적인 객체지향 쿼리 언어입니다. 자바 코드에서 데이터베이스를 조회할 때 특정 SQL이나 저장 엔진에 종속되지 않게 도와줍니다*
 

N+1 쿼리 문제는 언제 발생할까?

발생하는 경우는 다음과 같은 2가지 경우가 있습니다.

두 개의 엔티티가 1:N 관계를 가지며 JPQL로 객체를 조회할 때

  1. EAGER 전략으로 데이터를 가져오는 경우
  2. LAZY 전략으로 데이터를 가져온 이후에 가져온 데이터에서 하위 엔티티를 다시 조회하는 경우

 

N+1 문제 해결 방안

 

즉시로딩, 지연로딩으로 넘어오면서 우리는 근본적으로 N+1이 JPA에서 생기는 원인을 알 수 있습니다.

JPA가 자동으로 먼저 생성해주는 Jpql을 통해서 우선적으로 쿼리를 만들다보니 연관관계가 걸려있어도 join이 바로 걸리지 않는다.

 

일단  우리가 커스텀할 수 있는 부분이 존재하지 않기 때문에 우리는 바로 사용을 할 객체에 대해서는 join을 걸 수 있도록 조정해주어야 합니다. 그것이 fetch join인거죠.

@Query("select distinct u from User u left join fetch u.articles")
List<User> findAllJPQLFetch();

 

fetch join 적용 전

 

fetch join 적용 후

row 6000개 기준으로 N+1 문제

해결 전 49198ms 

해결 후 505ms

 

성능 측정에 있어 많은 차이가 있음을 확인 할 수 있었습니다.

 

 

출처 : https://wwlee94.github.io/category/blog/spring-jpa-n+1-query/

출처 : 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#%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9%EC%97%90%EC%84%9C%EC%9D%98-%ED%95%B4%EA%B2%B0%EC%B1%85---fetch-join

복사했습니다!