JPA에서 N+1 문제는 성능 저하를 유발할 수 있는 대표적인 ORM 사용 시의 이슈 중 하나입니다. 아래에 자세히 설명드릴게요.
N+1 문제란?
N+1 문제는 JPA에서 연관된 엔티티를 지연 로딩(LAZY loading) 방식으로 조회할 때 발생합니다.
예를 들어, 다음과 같은 상황을 가정해 봅시다:
- 게시글(Post)과 작성자(Member)는 다대일(N:1) 관계이다.
- 게시글 1개는 작성자 1명을 가진다.
@Entity
public class Post {
@ManyToOne(fetch = FetchType.LAZY)
private Member author;
}
게시글 리스트를 조회하는 쿼리를 날리면:
List<Post> posts = postRepository.findAll();
이때 JPA는 다음과 같은 방식으로 작동합니다:
- 1번의 쿼리로
posts를 전부 조회합니다. (이게 1) - 각각의
post.getAuthor()가 호출될 때마다 작성자(Member)를 별도 쿼리로 조회합니다. 게시글이 N개라면 작성자 쿼리가 N번 나감. (이게 N)
즉, 총 1 + N번의 쿼리가 실행되며, 이것이 바로 N+1 문제입니다.
왜 문제가 될까?
- 게시글 수가 적을 때는 괜찮지만, 수천 건이 되면 데이터베이스에 엄청난 부하가 발생합니다.
- 의도하지 않은 대량 쿼리로 성능 병목을 유발합니다.
해결 방법
1. Fetch Join 사용
@Query("SELECT p FROM Post p JOIN FETCH p.author")
List<Post> findAllWithAuthor();
JPA의 Fetch Join은 연관된 엔티티를 한 번의 쿼리로 함께 가져오게 해줍니다.
2. EntityGraph 사용
@EntityGraph(attributePaths = {"author"})
List<Post> findAll();
3. Batch Size 설정
JPA 구현체(Hibernate 등)에서 @BatchSize를 설정하면 지연 로딩 시 연관된 엔티티를 IN 쿼리로 묶어서 가져올 수 있습니다.
@BatchSize(size = 10)
@ManyToOne(fetch = FetchType.LAZY)
private Member author;
정리
| 방식 | 장점 | 단점 |
|---|---|---|
| Fetch Join | 쿼리 수 최소화 | 조인 수가 많으면 복잡해짐 |
| EntityGraph | 선언적 방식 | 복잡한 그래프에서는 한계 |
| BatchSize | 비교적 간단 | 메모리 사용량 증가 가능 |