본문 바로가기
Java+Spring 박살내기

N+1 원인과 해결 방법

by 창브로 2024. 7. 4.
728x90

오늘은 JPA의 N+1 문제와 해결법에 대해 알아보자.

 

N+1이란?

한 번의 쿼리를 날렸을 때 의도하지 않은 N번의 쿼리가 추가적으로 실행되는 것을 뜻한다.
즉, 연관 관계가 설정된 엔티티 사이에서 한 엔티티를 조회 하였을때 한번의 쿼리로 해결할 수 있는 작업을 n+1 개의 쿼리로 실행하게 되는 문제이다.


이해가 안되시나요?

예를들어 하나의 POST가 있다고 칩시다. POST는 COMMENT와 OneToMany의 연관관계를 가지고 있습니다.

순서대로 진행해 봅시다.

POST 전체를 조회합니다. (1번의 쿼리 발생)

POST 제목, 내용 조회 요청 (쿼리 발생 x)

POST에 달린 COMMENT 내용 조회 요청 (조회된 POST의 개수(N) 만큼 추가적인 쿼리 발생)

여기서 N+1이 발생합니다. (연관된 Entity를 불러올때)

한 번에 다 불러올 수 있는 것을 N번 더 불러와서 N+1이라 부릅니다.



그럼 어떻게 해결하나요???

여러가지 방법이 있지만 대표적인 fetch join과 BatchSize를 통해 해결하는 법을 알아보겠습니다!


1. fetch join (@xToOne 일때 주로 사용)

먼저 @OneToOne 관계를 맺고있는 Author와 Profile이 있습니다.

 

아래 코드를 실행하면 n+1 상황이 발생하게 됩니다.


왜냐하면


모든 작가를 가져오는 쿼리 실행 (1번)



각 작가의 프로필을 가져오는 쿼리 (작가 수 만큼 실행)




하지만 fetch join을 사용하면


한 번의 쿼리로 모든 작가와 그들의 프로필을 가져올 수 있습니다.





2. BatchSize (@xToMany 일때 주로 사용)


이번엔 @OneToMany 관계를 가지고 있는 Author와 Book이 있습니다.



N+1을 발생시키는 상황입니다.


@xToOne 관계를 설명했을때와 같이

모든 작가를 가져오는 쿼리(1번 실행)

각 작가의 책을 가져오는 쿼리 (작가 수만큼(N번) 실행)

이런 식으로 N+1 상황이 발생하게 됩니다.



하지만 저희는 BatchSize를 아래 코드처럼 사용하여

한 번에 여러 개의 Book을 로드하도록 최적화하여 쿼리 수를 줄일 수 있습니다.



근데 왜 @xToOne은 fetch join을 사용하고 @xToMany는 batch size를 사용하는 것을 권장할까요?

그 이유는 @xToOne 관계에서는 보통 연관된 엔티티가 적으므로 fetch join을 사용하여
즉시 로딩하고 추가 쿼리를 방지하는 반면

@xToMany 관계에서는 보통 연관된 엔티티가 많으므로 BatchSize를 활용하여 한 번에 들고오기 보다 조금씩 나눠 들고와서 추가 쿼리를 최소화 하는 것 입니다.