스프링 부트 기반의 애플리케이션에서 자주 발생하는
N+1 쿼리 문제의 개념과 원인, 그리고 이를 해결하기 위한 실용적인 전략들을 정리하였다.
특히 JPA(Hibernate)와 연관된 엔티티 조회 시 발생할 수 있는 성능 이슈 중심으로 설명되었다.
1. N+1 문제란?
N+1 문제는 다음과 같은 상황에서 발생한다:
- 1번의 쿼리로 N개의 엔티티를 조회한 뒤,
- 각 엔티티에 연관된 데이터를 조회할 때 추가로 N번의 쿼리가 실행되는 현상
즉, 총 1 + N번의 SQL 쿼리가 실행되며, 이는 대규모 데이터 처리 시 심각한 성능 저하를 초래할 수 있다.
예시 (JPA 기준):
List<Post> posts = postRepository.findAll(); // 1번 쿼리
for (Post post : posts) {
post.getComments().size(); // N번 쿼리 발생
}
이 경우, 각 Post마다 연관된 Comment를 지연 로딩(LAZY) 방식으로 가져오기 때문에N개의 추가 쿼리가 발생한다.
2. 발생 원인
- JPA의 기본 연관관계 로딩 전략은
@ManyToOne,@OneToMany관계에서 기본적으로 지연 로딩(LAZY)이다. - 엔티티 컬렉션을 반복하면서 연관 객체를 호출할 경우, 프록시가 초기화되며 개별 쿼리가 실행된다.
- SQL 수준에서 보면, SELECT N+1 쿼리가 순차적으로 발생한다.
3. 해결 방법
✅ 1) 페치 조인(Fetch Join) 사용
JPA에서는 연관된 엔티티를 한 번의 쿼리로 함께 조회할 수 있도록 JOIN FETCH 구문을 사용할 수 있다.
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
List<Post> findAllWithComments();
- 연관된
Comment엔티티도 함께 조회되어 N번의 추가 쿼리 없이 처리됨 - 다만, 페치 조인은 다대일 관계에서는 안전하지만, 일대다에서는 중복 데이터 주의가 필요하다
✅ 2) EntityGraph 사용
JPA 2.1부터 지원되는 @EntityGraph를 활용하면, JPQL 없이도 연관 엔티티를 함께 로딩할 수 있다.
@EntityGraph(attributePaths = "comments")
List<Post> findAll();
- 선언적이고 재사용 가능한 방식
- 페치 조인보다 유지보수에 유리할 수 있음
✅ 3) DTO로 직접 조회
JPQL이나 QueryDSL 등을 활용해 필요한 데이터만 DTO로 직접 조회하는 방식도 대안이 될 수 있다.
@Query("SELECT new com.example.dto.PostWithCommentDto(p.id, p.title, c.content) " +
"FROM Post p JOIN p.comments c")
List<PostWithCommentDto> findAllPostWithComment();
- 필요한 필드만 선택적으로 가져올 수 있어 성능상 유리함
- 엔티티와의 결합도를 낮출 수 있음
4. 개발 시 체크리스트
- 연관관계는 항상 지연 로딩을 기본으로 설정하고, 필요한 곳만 페치 조인 사용
- 실무에서는 반드시 쿼리 로그 확인 (
spring.jpa.show-sql=true, Hibernate format 설정 등) - 페이징 처리 시에는 컬렉션 페치 조인 금지 (
Pageable과@Query조합 필요) - 복잡한 조회는 DTO 변환 또는 QueryDSL로 대체
학습 정리
N+1 문제는 ORM을 사용할 때 매우 흔하게 발생하는 성능 이슈이며,
특히 무심코 작성한 코드가 수백 수천 개의 쿼리를 유발할 수 있다는 점에서 주의가 필요하다.
스프링 부트 환경에서는 JPA의 Fetch Join, EntityGraph, DTO 직접 조회 등 다양한 방식으로 이를 해결할 수 있으며,
쿼리 구조와 로딩 전략을 이해하는 것이 핵심이다.
'CS공부 > 기타 CS지식' 카테고리의 다른 글
| 락(Lock)의 종류와 적용 전략: Optimistic vs Pessimistic (0) | 2025.05.14 |
|---|---|
| 해싱 보안 이슈 및 강화 가이드 (1) | 2025.04.26 |
| SK텔레콤 유짐정보 해킹, BPFdoor 악성코드 사건 정리 (3) | 2025.04.26 |
| 데드락(Deadlock) (0) | 2025.04.21 |
| BASE 64 (0) | 2025.04.21 |