노트

N+1 문제

N+1 Query Problem

백엔드#jpa · 연결된 개념 10개

쉽게 말하면

N+1 문제는 장보기 목록을 한 번 받아 놓고, 적힌 물건마다 마트를 따로 한 번씩 다녀오는 거예요. 물건이 적을 땐 티가 안 나다가, 많아지면 왕복 횟수가 그만큼 늘어 느려지죠.

비유의 한계 원인은 대개 지연 로딩이라 코드에 반복 조회가 안 보여도 생겨요. fetch join이나 @EntityGraph로 한 번에 가져오거나 배치 페치로 묶고, SQL 로그로 쿼리 수를 직접 세어 봐요.

목록을 한 번 조회(1)한 뒤 각 행의 연관 엔티티를 따로 조회해서 쿼리가 N번 더 나가는 문제. 데이터가 적은 개발 환경에서는 잘 보이지 않는다. 원인은 대개 지연 로딩이다.

해법은 상황에 따라 고른다.

  • fetch join: JPQL(Jakarta Persistence Query Language) join fetch로 한 번에 가져온다(JPQL과 @Query). 컬렉션을 fetch join하면 페이징과 함께 쓰기 어렵다
  • @EntityGraph: 메서드마다 함께 가져올 연관을 선언한다
  • 배치 페치(batch fetching): default_batch_fetch_size로 지연 로딩을 IN (?, ?, …) 묶음 조회로 바꾼다
  • DTO(Data Transfer Object) 프로젝션(projection): 필요한 컬럼만 조회한다
sequenceDiagram
  participant A as 애플리케이션
  participant DB as DB
  Note over A,DB: 지연 로딩 그대로 - 1 + N번
  A->>DB: 목록 조회 1번
  loop 행마다 N번
    A->>DB: 연관 엔티티 조회
  end
  Note over A,DB: fetch join - 1번
  A->>DB: join fetch로 목록과 연관을 함께 조회

무엇보다 SQL 로그를 켜 두고 쿼리 수를 눈으로 확인하는 습관이 먼저다.

책: 8장. 운영에서 터지는 것들 — N+1

출처: Hibernate ORM User Guide: Batch fetching · Entity graph · Best practices: Fetching

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • JDBC와 JdbcTemplate

    JDBC(Java Database Connectivity)는 자바가 여러 DB와 통신하기 위한 표준 API이고, Hibernate·MyBatis·스프링 JDBC 모두 그 위에 있다. JdbcTemplate은 스프링이 JDBC의 반복 작업(연결 열고 닫기, 예외 변환, 결과 순회)을 대신해 주는 클래스다.

  • JPA 엔티티 기본 매핑

    엔티티 매핑(entity mapping)은 자바 클래스와 필드를 테이블과 컬럼에 대응시키는 JPA(Jakarta Persistence)의 기본 설정이다. 애너테이션은 jakarta.persistence 패키지에 있다. 테이블 사이의 외래키를 참조로 옮기는 일은 jpa-relationship에서 다룬다.

  • 낙관적 잠금과 비관적 잠금

    낙관적 잠금(Optimistic Locking)은 충돌이 드물다고 보고 읽을 때 잠그지 않는 동시성 제어 방식이다. 쓸 때 읽어 둔 버전(또는 타임스탬프)과 지금 값을 비교해, 그사이 다른 쪽이 바꿨으면 쓰기를 실패시킨다. 두 사용자가 같은 행을 읽고 차례로 저장해 먼저 저장한 변경이 사라지는 갱신 손실(lost update)을 막는다.

  • 테스트 냄새

    테스트 코드나 테스트 습관에서 나는, 더 깊은 문제를 알리는 신호. 제라드 메스자로스의 xUnit 테스트 패턴 정리가 이름을 붙였다.

  • 최종 일관성

    최종 일관성(eventual consistency)은 "지금 당장은 저장소마다 값이 다를 수 있지만, 새 변경이 멈추면 결국 같아진다"는 보장이다. 원본 DB와 검색 색인·캐시·다른 서비스처럼 물리적으로 분리된 저장소를 한 트랜잭션으로 묶을 수 없을 때 받아들이는 일관성 모델(consistency model)이다.

보기 옵션