Foundry
데이터베이스
중급
핵심

N+1 문제

ORM에서 발생하는 다중 쿼리 문제, Eager Loading

N+1 문제

N+1이란?

1번의 목록 쿼리 + N번의 상세 쿼리

예시

-- 1번: 게시글 10개 조회
SELECT * FROM posts LIMIT 10;

-- N번: 각 게시글 작성자 (10번 반복!)
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
...
SELECT * FROM users WHERE id = 10;
-- 총 11번! (1 + 10)

해결 방법

방법SQL쿼리 수
JOINposts JOIN users1번
Eager LoadORM include2번
BatchWHERE id IN (...)2번

ORM별 해결법

# Django
Post.objects.select_related('author')

# JPA/Hibernate
@EntityGraph(attributePaths={"author"})

# Prisma
prisma.post.findMany({
  include: { author: true }
})

실무 포인트

  • ORM 사용 시 쿼리 로그 확인 필수
  • 목록 10건: 11번 vs 1번 = 10배 차이
면접에서 이렇게 나옵니다

Q.N+1 문제란? 어떻게 해결하나요?

목록을 한 번 조회한 뒤, 각 항목마다 추가 쿼리가 나가는 현상입니다.

1번  글 목록 100건 조회
100번 각 글의 작성자를 하나씩 조회
      = 101번

해결은 한 번에 모아 가져오는 것입니다.

방법내용특징
조인한 쿼리로 함께 가져온다왕복 1회. 같은 작성자 정보가 반복 전송된다
배치 조회작성자 id 를 모아 IN 으로 한 번 더왕복 2회. 중복 전송이 없다

어느 쪽이 나은지는 중복 정도로 갈립니다. 글 100건이 실제로는 12명이 쓴 것이라면, 조인은 작성자 정보를 100번 실어 오고 배치는 12번만 가져옵니다.

흔한 실수: N+1 만 없애면 끝이라고 보는 것. 조인으로 바꿨는데 전송량이 늘어 더 느려지는 경우가 있습니다. 왕복 횟수와 전송량을 함께 봐야 합니다.

Q.JPA에서 N+1 문제 해결 방법은?

방법내용주의
페치 조인조인으로 한 번에 가져온다컬렉션을 여러 개 조인하면 행이 곱해진다
배치 크기 설정지연 로딩을 IN 으로 묶어 가져온다왕복이 몇 번으로 줄어든다
엔티티 그래프어떤 연관을 함께 가져올지 선언한다조회마다 다르게 지정할 수 있다
DTO 로 직접 조회필요한 컬럼만 프로젝션엔티티가 아니라 변경 감지가 없다

컬렉션 페치 조인에는 함정이 있습니다. 글 100건에 댓글이 각 10개면 결과가 1,000행이 되고, 페이징을 함께 쓰면 DB 에서 자를 수 없어 메모리에서 처리합니다. 그래서 컬렉션은 배치 크기로 푸는 편이 안전합니다.

흔한 실수: 모든 연관을 즉시 로딩으로 바꾸는 것. N+1 은 사라지지만 쓰지도 않는 연관까지 매번 가져와 더 느려집니다.

Q.Eager vs Lazy Loading 차이는?

연관 데이터를 언제 가져오는지가 다릅니다.

항목즉시 로딩지연 로딩
시점본체를 읽을 때 함께실제로 접근할 때
장점추가 쿼리가 없다안 쓰면 안 가져온다
단점안 쓰는 것도 가져온다. 조인이 커진다접근 시점에 N+1 이 나기 쉽다

기본은 지연 로딩입니다. 필요한 것만 가져오고, 함께 써야 하는 곳에서만 페치 조인이나 배치로 명시하는 방식이 예측 가능합니다.

즉시 로딩을 기본으로 두면 어떤 쿼리가 나갈지 코드에서 읽히지 않고, 연관이 연쇄되면 한 번의 조회가 테이블 여러 개를 끌고 옵니다.

흔한 실수: 지연 로딩을 쓰면 N+1 이 없다고 보는 것. 오히려 지연 로딩에서 반복문 안 접근으로 N+1 이 생깁니다. 지연을 기본으로 두되 필요한 곳에서 명시적으로 함께 가져오는 것이 답입니다.

Q.N+1 문제를 어떻게 탐지하나요?

방법내용
쿼리 로그 확인한 요청에서 비슷한 쿼리가 반복되는지 본다
요청당 쿼리 수 계측임계치를 넘으면 경고를 남긴다
APM 의 트레이스한 요청 안의 DB 호출 횟수와 시간을 본다
테스트에서 검증특정 API 호출 시 쿼리 수를 단언한다
개발 환경 도구프레임워크별 N+1 감지 플러그인

가장 확실한 것은 요청당 쿼리 수를 지표로 만드는 것입니다. 목록 API 가 항상 3번 이하로 끝나야 한다면 그것을 테스트로 고정합니다. 그러면 나중에 누군가 연관을 추가해도 바로 드러납니다.

운영에서는 목록 응답 시간이 데이터 양에 비례해 늘어나는 패턴이 신호입니다. 10건일 때 50ms, 100건일 때 500ms 면 건당 쿼리가 나가고 있을 가능성이 큽니다.

흔한 실수: 개발 환경에서 데이터가 적어 못 잡는 것. 10건으로는 11번 쿼리가 빨라 보입니다. 운영 규모의 데이터로 확인해야 합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.