N+1 문제란? 발생 원인과 해결 방법

N+1 문제란 무엇인가

N+1 문제는 ORM(Object Relational Mapping)을 사용할 때 흔히 마주치는 성능 이슈로, 하나의 쿼리로 끝날 수 있었던 작업이 N개의 추가 쿼리로 쪼개져 실행되는 현상을 말합니다. 이름 그대로 최초 1번의 쿼리(부모 데이터 조회) 이후, 연관된 데이터를 가져오기 위해 N번의 추가 쿼리가 발생한다는 의미입니다. 예를 들어 게시글 10개를 조회했는데, 각 게시글의 작성자 정보를 가져오기 위해 추가로 10번의 쿼리가 실행된다면 총 11번(1+10)의 쿼리가 나가는 셈입니다.

왜 발생할까? 발생 원인과 예시

N+1 문제는 주로 JPA, Hibernate 같은 ORM에서 지연 로딩(Lazy Loading) 전략을 사용할 때 발생합니다. 지연 로딩은 연관된 엔티티를 실제로 사용하는 시점에 쿼리를 날리는 방식인데, 반복문 안에서 연관 엔티티에 접근하면 매 반복마다 쿼리가 하나씩 추가로 실행됩니다.

예를 들어 다음과 같은 코드를 생각해보겠습니다.

List<Post> posts = postRepository.findAll(); for (Post post : posts) { System.out.println(post.getAuthor().getName()); }

postRepository.findAll()로 게시글 목록을 한 번에 가져오지만, 반복문에서 post.getAuthor()를 호출하는 순간마다 작성자 정보를 조회하는 쿼리가 별도로 실행됩니다. 게시글이 100개라면 최악의 경우 101번의 쿼리가 발생하는 것이죠. 마치 반 학생 100명의 이름을 미리 받아놓고, 각 학생의 부모님 연락처를 한 명씩 따로따로 전화해서 물어보는 것과 비슷합니다. 한 번에 명단을 받아올 수 있는데도 비효율적으로 반복 조회하는 셈입니다.

해결 방법

N+1 문제는 연관 데이터를 미리, 한 번에 가져오도록 쿼리를 설계하면 해결할 수 있습니다. 대표적인 방법은 다음과 같습니다.

  • Fetch Join 사용: JPQL에서 join fetch를 사용해 연관 엔티티를 한 번의 쿼리로 함께 조회합니다.
  • EntityGraph 활용: @EntityGraph 어노테이션으로 특정 조회 시점에 필요한 연관 엔티티를 명시적으로 함께 로딩합니다.
  • Batch Size 설정: hibernate.default_batch_fetch_size 옵션을 통해 지연 로딩 시 여러 건을 IN 절로 묶어서 조회하도록 설정합니다.

실무에서는 조회 화면마다 필요한 연관 데이터가 다르기 때문에, 무조건 즉시 로딩(Eager Loading)으로 바꾸기보다는 상황에 맞게 fetch join이나 EntityGraph를 적용하는 것이 일반적입니다. 특히 API 응답 성능이 중요한 서비스에서는 쿼리 로그를 확인하며 N+1이 발생하는 지점을 찾아내는 습관이 중요합니다.

관련 글

댓글 남기기