
프로젝트에서 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을 생성합니다.
N+1 쿼리 문제는 언제 발생할까?
발생하는 경우는 다음과 같은 2가지 경우가 있습니다.
두 개의 엔티티가 1:N 관계를 가지며 JPQL로 객체를 조회할 때
- EAGER 전략으로 데이터를 가져오는 경우
- 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();
row 6000개 기준으로 N+1 문제
해결 전 49198ms
해결 후 505ms
성능 측정에 있어 많은 차이가 있음을 확인 할 수 있었습니다.
출처 : https://wwlee94.github.io/category/blog/spring-jpa-n+1-query/
'Spring-boot' 카테고리의 다른 글
Spring-boot 란? (0) | 2022.06.03 |
---|---|
Spring-boot에서 github Action & nginx CI/CD구축하기 4 (0) | 2022.06.01 |
Spring-boot에서 github Action & nginx CI/CD구축하기 3 (0) | 2022.06.01 |
Spring-boot에서 github Action & nginx CI/CD구축하기 2 (0) | 2022.06.01 |
Spring-boot에서 github Action & nginx CI/CD구축하기 1 (0) | 2022.06.01 |