25.03.04 TIL N+1

신성훈·2025년 3월 4일
0

TIL

목록 보기
141/162

1. N+1 문제란?

N+1 문제ORM(Object-Relational Mapping)에서 발생하는 성능 저하 문제입니다.
특정 데이터를 조회할 때 1개의 기본 쿼리(1)를 실행한 후, 연관된 데이터를 가져오기 위해 추가로 N개의 쿼리(N)를 실행하면서 발생합니다.

결과적으로 쿼리가 (1 + N)번 실행되면서 성능이 저하됩니다.


2. N+1 문제 예제 (JPA 기준)

N+1 문제 발생 코드 (Lazy Loading)

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members;
}

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;
}
// 팀 리스트 조회
List<Team> teams = teamRepository.findAll(); // (1) 팀 조회 (1번 쿼리 실행)

// 각 팀에 속한 멤버 조회 (N번 쿼리 실행)
for (Team team : teams) {
    List<Member> members = team.getMembers(); // (N) 멤버 조회 (N번 쿼리 실행)
}

문제점:

  • findAll() 실행 시 팀을 한 번 조회 (SELECT * FROM team)
  • 이후 getMembers() 호출할 때마다 각 팀의 멤버를 조회 (SELECT * FROM member WHERE team_id = ?)
  • 만약 팀이 10개라면, 총 11번(1 + 10) 쿼리 실행 → 성능 저하 발생!

3. N+1 문제 해결 방법

1) Fetch Join 사용 (즉시 로딩)

@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();
  • JOIN FETCH를 사용하여 한 번의 쿼리로 팀과 멤버를 함께 조회
  • 즉시 로딩(Eager Loading) 효과를 제공
  • 쿼리 실행 횟수를 1번으로 줄일 수 있음

2) EntityGraph 사용

@EntityGraph(attributePaths = {"members"})
@Query("SELECT t FROM Team t")
List<Team> findAllWithMembers();
  • @EntityGraph를 사용하여 연관 데이터를 한 번에 로딩
  • JOIN FETCH와 비슷한 효과

3) Batch Size 설정 (IN 절 활용)

spring:
  jpa:
    properties:
      hibernate.default_batch_fetch_size: 100
  • Hibernate의 IN 절을 활용하여 한 번에 여러 개의 연관 데이터를 가져옴
  • N번 실행되던 쿼리를 소수의 쿼리로 최적화 가능

4. 마무리

JPA를 사용할 때 Lazy Loading(지연 로딩)이 기본 설정이므로 연관된 데이터를 조회할 때 N+1 문제가 발생할 가능성이 높다는 점을 깨달았습니다.
Fetch Join, EntityGraph, Batch Size 조정 등 다양한 최적화 기법을 활용하여 성능 문제를 해결하는 것이 중요하다고 느꼈습니다!

profile
조급해하지 말고, 흐름을 만들고, 기록하면서 쌓아가자.

0개의 댓글