주문 목록을 한 번 조회한 뒤 각 주문의 고객 정보를 따로 읽어 쿼리 수가 늘어납니다. N+1 조회를 어떻게 줄이고, JOIN으로 바꿀 때는 어떤 비용을 확인하나요?
잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?
답변과 점검 포인트 펼치기답변 접기
구두 답변
정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.
N+1은 주문 목록을 한 번 읽은 뒤 N개 주문 각각의 고객을 별도 쿼리로 읽어 한 사용자 요청의 DB 호출 수가 항목 수에 따라 증가하는 패턴입니다. 각 쿼리의 실행 시간이 짧아도 네트워크 왕복, 연결 풀 점유, 파싱·계획, 결과 조합 비용이 누적됩니다. 먼저 느린 SQL 하나가 아니라 요청 trace에서 SQL 호출 횟수와 총 DB 대기 시간을 확인하겠습니다.
JOIN과 배치 조회를 같은 결과 기준으로 비교합니다
주문 50개와 고객 정보를 함께 보여 준다면 고객 ID를 모아 WHERE customer_id IN (...)으로 한 번 읽고 애플리케이션에서 매핑하는 제한된 배치 조회가 간단한 해법입니다. 한 번의 JOIN도 가능하지만, 주문-상품-쿠폰처럼 여러 일대다 관계를 동시에 JOIN하면 부모 행이 곱집합처럼 늘어날 수 있습니다. 결과에 같은 주문과 고객 컬럼이 반복되고 네트워크 응답·정렬·애플리케이션 메모리가 커질 수 있으므로, 쿼리 수만 줄였다고 성공으로 보지 않겠습니다.
ORM의 eager loading, prefetch, batch loader는 생성 SQL과 요청 범위 캐시를 확인해야 합니다. 목록의 페이지를 먼저 결정하지 않고 전체 주문을 읽은 다음 관련 데이터를 가져오면 N+1이 N+1이 아닌 큰 메모리 문제로 바뀔 수 있습니다. 반대로 관계가 단순하고 적절한 인덱스가 있으며 반환 행이 작다면 JOIN이 가장 효율적일 수 있습니다. 고객이 없는 주문을 보존해야 하는지에 따라 INNER JOIN과 LEFT JOIN의 결과 의미도 달라집니다.
페이지 경계와 엔진 비용을 확인합니다
일대다 JOIN 결과에 부모 목록의 LIMIT 20을 바로 적용하면 자식 행이 부모 20개보다 먼저 소진되어 부모 페이지가 짧아지거나 같은 부모가 반복될 수 있습니다. 그래서 부모 주문 ID 20개를 먼저 확정한 뒤 자식 데이터를 배치로 읽거나, DBMS가 지원하는 윈도·서브쿼리 계획을 검증하겠습니다. PostgreSQL·MySQL·SQL Server는 조인 순서, hash join·nested loop·merge join, 통계와 메모리 모델이 달라 동일 SQL의 비용이 같지 않습니다.
실험은 1개·20개·500개 목록, 중복 고객이 많은 분포, 고객별 일대다 폭증, 콜드 캐시와 웜 캐시에서 쿼리 수·실제 읽은 행·반환 바이트·연결 사용 시간·p99를 측정하는 방식으로 하겠습니다. 배치 IN의 크기도 무한히 키우지 않고 파라미터 제한과 메모리 상한을 둡니다. 캐시가 문제를 숨길 수 있으므로 요청별 캐시와 전역 캐시를 분리해 검증합니다. 목표는 SQL 호출 수의 최소가 아니라 같은 의미의 결과를 과하지 않은 읽기와 왕복으로 가져오는 것입니다.
JOIN으로 바꿀 때는 중복 제거를 애플리케이션에서 수행해야 하는지도 확인하겠습니다. 부모 페이지를 먼저 고정하지 않으면 자식 행 때문에 LIMIT과 COUNT의 의미가 달라지고, LEFT JOIN을 INNER JOIN으로 바꾸면 고객이 삭제된 주문이 사라질 수 있습니다. 배치 조회는 요청별 캐시를 사용해 같은 고객을 한 번만 읽게 할 수 있지만, 요청을 넘어 전역 캐시를 두면 신선도·무효화 문제가 생깁니다. 결과 의미를 보존한 상태에서 왕복과 읽기량을 함께 줄이겠습니다.
득점 포인트
이해가 드러나는 지점을 확인해 보세요.
- 요청 단위 SQL 호출 수와 총 DB 대기를 측정한다.
- JOIN의 일대다 행 증폭과 배치 상한을 설명한다.
- 부모 페이지를 먼저 확정하고 엔진별 실행 계획을 비교한다.
감점 포인트
놓치기 쉬운 조건과 오해를 짚어 보세요.
- 각 쿼리가 빠르면 N+1이 문제가 아니라고 말한다.
- 모든 관계를 한 JOIN으로 합치면 항상 최적이라고 단정한다.
- ORM 옵션 이름만 보고 실제 SQL과 반환 행을 확인하지 않는다.
더 파고들 거리
하나 더 질문하며 이해의 범위를 넓혀 보세요.
- 같은 요청에서 고객을 여러 번 읽습니다. 요청별 loader cache와 전역 cache를 어떻게 구분하고 측정하나요?
- 주문·상품을 JOIN한 결과에 LIMIT을 걸었더니 주문 개수가 부족합니다. 부모 페이지를 어떻게 먼저 확정하나요?
- 배치 크기와 실제 읽기·응답 크기의 최적점을 찾아 보세요.
득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.
원리부터 공부하고 싶다면
답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.