[Spring JPA] N+1 문제와 해결방안(Fetch Join, BatchSize)

김재진·2026년 1월 22일

내일배움캠프

목록 보기
42/70

1. N+1 문제란 무엇인가?

N+1 문제는 연관 관계가 설정된 엔티티를 조회할 때, 의도하지 않은 추가 쿼리가 발생하는 현상.

  • 1 (조회 쿼리): 처음에 대상 엔티티를 조회하기 위한 쿼리 1번.
  • N (추가 쿼리): 조회된 결과가 N개일 때, 연관된 데이터를 가져오기 위해 각 결과마다 추가 쿼리가 N번 발생.
  • 결과: 데이터가 많아질수록 쿼리 수가 폭증하여 DB 부하가 발생하고 시스템 전체가 느려짐.

2. 해결 방법 1: Fetch Join

가장 확실한 방법은 SQL의 JOIN 문법을 사용해 연관된 데이터를 한 번에 가져오는 것.

특징

  • JPQL에서 fetch 키워드를 사용: select m from Member m join fetch m.team
  • 장점: 쿼리 한 번으로 모든 데이터를 가져오므로 성능상 가장 이득.
  • 단점: 페이징(Paging) 제한: 1:N 관계에서 사용 시 메모리에서 페이징을 처리하므로 OutOfMemory 위험이 있음.
  • 중복 데이터: 일대다 조인 시 결과 데이터가 뻥튀기될 수 있다. (DISTINCT로 해결 필요)

3. 해결 방법 2: @BatchSize

Fetch Join의 단점(페이징 불가)을 보완하는 아주 강력한 옵션.

동작 원리

  • 연관된 엔티티를 조회할 때 하나씩 쿼리를 날리지 않고, 지정한 사이즈만큼 IN 절에 ID를 모아서 한꺼번에 조회.
  • 설정: application.ymldefault_batch_fetch_size: 100 설정.
  • 장점: 페이징 처리가 가능하면서도 쿼리 횟수를 획기적으로 줄임. (N번의 쿼리가 N/100번으로 감소)

4. 전략

  1. 기본 설정: 모든 연관 관계는 지연 로딩(LAZY)으로 설정.
  2. ToOne(1:1, N:1) 관계: 데이터 중복 문제가 없으므로 Fetch Join을 사용.
  3. ToMany(1:N) 관계:
    • 페이징이 필요 없다면 → Fetch Join
    • 페이징이 필요하다면 → BatchSize 설정으로 최적화

5. 마무리

JPA의 편리함 뒤에는 N+1 문제라는 큰 함정이 있다. 이 함정을 해결하는데 무조건 Fetch Join이 답이 아니라는 것, 특히 페이징 상황에서는 BatchSize가 필수라는 점이 오늘 학습의 핵심.
"기능이 돌아가게 만드는 것"을 넘어 "효율적으로 돌아가게 만드는" 개발자가 되기 위해 쿼리 로그를 꼼꼼히 확인하는 습관을 가져야겠다.

profile
개발공부 처음해보는 사람

0개의 댓글