Spring/강의

[📘 심화 Spring] 3-4. N+1 문제

가지코딩 2025. 6. 9. 22:04

📘 목차

  1. N + 1 문제
  2. Fetch Join
  3. @BatchSize 어노테이션
  4. 정리

1. N + 1 문제

N + 1 문제

  •  ORM 프레임워크(특히 JPA, Hibernate)를 사용할 때 가장 빈번하게 발생하는 성능 병목 현상이다.
  • N + 1
    • 1번의 쿼리로 기본 데이터(부모 엔티티)를 조회하고,
    • 이후에 N번의 쿼리로 연관된 데이터(자식 엔티티)를 각각 조회하는 상황을 의미한다.
  • 즉, 전체적으로는 N + 1번의 쿼리가 발생하여, 쿼리 수가 기하급수적으로 늘어나서 성능 저하를 유발한다.

 

 

N + 1 문제 발생 원리와 과정

  1. 부모 엔티티 한 번 조회 (1번 쿼리)
    • 예: 게시글 10개를 조회한다.
    • 쿼리 1번 실행 → select * from post
  2. 자식 엔티티를 지연로딩 방식으로 각각 조회 (N번 쿼리)
    • 각 게시글마다 작성자(Member)를 접근하면, 지연로딩 때문에 작성자 정보를 별도 쿼리로 조회한다.
    • 게시글이 10개면 작성자 조회 쿼리 10번 실행 → select * from member where member_id = ?
  3. 총 N + 1번 쿼리 실행
    • 부모 조회 1번 + 자식 조회 N번 → 성능 저하 발생

 

왜 이런 일이 발생하는가?

  • JPA는 기본적으로 연관된 엔티티를 지연로딩(LAZY)으로 설정한다.
  • 즉, 실제로 참조 필드를 접근하는 시점에 DB에서 데이터를 가져온다.
  • 하지만 이 접근이 여러 객체에 대해 반복되면, 매번 별도의 쿼리가 발생한다.

2. Fetch Join

Fetch Join은 JPA에서 N+1 문제를 해결하기 위해 사용하는 기법이다. 한 번의 쿼리로 연관된 엔티티를 함께 조회할 수 있다.

 

 

Entity Fetch Join

  • 단일 연관관계(@ManyToOne, @OneToOne)에 주로 사용한다.
  • 한 건의 부모 엔티티와 단일 자식 엔티티를 조인해서 함께 가져온다.
  • 결과는 하나의 부모 엔티티이고, 조인된 자식 엔티티가 내부 필드로 채워진다.
select o from Order o join fetch o.member
  • Order 1건 조회와 동시에 Order가 참조하는 Member를 함께 조회한다.
  • 보통 ManyToOne, OneToOne 관계에서 사용하며, 즉시 로딩(eager loading) 효과가 있다.

 

 

Collection Fetch Join

  • @OneToMany, @ManyToMany 같은 컬렉션 관계에 사용된다.
  • 여러 개의 자식 엔티티(컬렉션)를 부모 엔티티와 함께 조회한다.
  • 결과는 부모 엔티티가 컬렉션에 연관된 자식 엔티티들을 모두 포함한다.
  • 주의점: 컬렉션 fetch join 시, 결과가 중복될 수 있어 페이징에 제한이 있다.
select o from Order o join fetch o.orderItems
  • Order와 관련된 여러 OrderItem을 한 번에 조회한다.

3. @BatchSize 어노테이션

  • Fetch Join 외에 N+1 문제를 완화하는 방법으로, Hibernate가 제공하는 기능이다.
  • 연관된 엔티티를 지연로딩할 때, 한 번에 N개 단위로 묶어서 조회한다.
  • 엔티티 클래스나 연관관계 필드에 적용 가능하다.
  • ex, @BatchSize(size=10)을 설정하면 10개씩 묶어서 쿼리를 실행해 N+1 문제를 줄인다.
@Entity
public class Order {
    @ManyToOne(fetch = FetchType.LAZY)
    @BatchSize(size = 10)
    private Member member;
}
  • 위 예시에서는 여러 Order의 Member를 조회할 때 10개 단위로 묶어서 한 번에 쿼리 실행한다.
  • 완전한 fetch join만큼 성능 최적화가 되지는 않지만, 쿼리 수를 크게 줄일 수 있다.

4. 정리

구분 설명 장점 주의사항
Entity Fetch Join 단일 연관관계(@ManyToOne, @OneToOne)를 한 번에 조인해서 조회 1번 쿼리로 연관 엔티티 즉시 로딩 크게 문제 없음
Collection Fetch Join 컬렉션 연관관계(@OneToMany, @ManyToMany)를 한 번에 조회 1번 쿼리로 컬렉션 포함 모든 데이터 조회 중복 데이터 발생, 페이징 불가
@BatchSize 지연로딩 시 여러 개 묶어서 한 번에 쿼리 실행 (Hibernate 전용) N+1 문제 완화, 쿼리 수 감소 완전한 fetch join보다 덜 효율적

 

 

 


👏 👏 👏  심화 Spring 강의 완강  👏 👏 👏