문제상황
조회 할 때 쿼리가 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하는 n+1 문제가 발생하였습니다. 쿼리가 추가로 발생해서 성능 저하로 이어지기 때문에 문제를 해결해야 하는 상황입니다.
추가로 발생되는 쿼리 확인
Post가 10개씩 조회되는데 아래의 쿼리가 10개씩 추가로 발생하고 있습니다.
2024-10-23 04:00:12.166 DEBUG 71806 --- [nio-8080-exec-1] org.hibernate.SQL :
select
postimgs0_.post_id as post_id4_10_0_,
postimgs0_.post_img_id as post_img1_10_0_,
postimgs0_.post_img_id as post_img1_10_1_,
postimgs0_.name as name2_10_1_,
postimgs0_.post_id as post_id4_10_1_,
postimgs0_.url as url3_10_1_
from
post_img postimgs0_
where
postimgs0_.post_id=?
원인 파악
JPQL은 객체의 이름으로 쿼리를 하는데 1개의 쿼리로 연관관계는 무시하고 entity들을 조회하고 연관된 entity를 따로 쿼리를 날리게 됩니다.
제 코드에서는 Post를 먼저 찾고 Post와 1:N 연관관계가 있는 PostImg에 N번의 쿼리를 요청하고 있었습니다.
성능비교를 위해 문제해결 전 성능 측정
users 100명이 1분동안 계속 요청 보낸 결과
해결 과정
fetch join
SELECT p FROM Post p LEFT JOIN FETCH p.postImgs
fetch join을 사용했더니 N+1 문제는 해결 되었지만 다른 문제가 발생하였습니다.
2024-10-23 04:21:41.314 WARN 73399 --- [nio-8080-exec-1] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
지금 현재 Post 데이터가 10000개가 있는데 10000개를 전부 조회하고 조회한 데이터에서 Pagination을 하기 때문에 경고가 발생한 것으로 보입니다.
JPA에서 Paging을 하게 되면 OneToMany 관계는 fetch join이 불가능합니다. 왜냐하면 이렇게 일대다 테이블을 조인하면 데이터의 수가 변하기 때문입니다.
@Batchsize
연관된 엔티티를 조회할 때 지정된 size 만큼 SQL의 IN절을 사용해서 조회합니다. Post의 갯수만큼 추가 SQL을 날리지 않고, 조회한 postimg의 id들을 모아서 SQL IN 절을 날립니다.
불필요한 쿼리를 방지하는 것을 확인할 수 있었습니다.
select
postimgs0_.post_id as post_id4_10_1_,
postimgs0_.post_img_id as post_img1_10_1_,
postimgs0_.post_img_id as post_img1_10_0_,
postimgs0_.name as name2_10_0_,
postimgs0_.post_id as post_id4_10_0_,
postimgs0_.url as url3_10_0_
from
post_img postimgs0_
where
postimgs0_.post_id in (
?, ?, ?, ?, ?
)
개선 후 성능 측정
평균 응답 속도 79ms -> 40ms
BatchSize를 사용해서 N+1문제를 해결해보았습니다. 불필요한 쿼리를 줄여서 db에 불필요한 부하를 개선할 수 있었습니다.
'Dev' 카테고리의 다른 글
Index를 생성해서 약 10배 성능 개선 (0) | 2024.12.04 |
---|---|
stream 만들어낸 이유가 있지 않을까? (0) | 2024.12.04 |
일단 단위 테스트부터 테스트 코드 작성해보기 (0) | 2024.12.04 |
상품 주문하기 동시성 문제 해결하기 (0) | 2024.12.04 |
과도한 트래픽에 대한 방어하기 (2) | 2024.12.04 |