n+1 쿼리는 데이터베이스에서 컬렉션 레코드(1개 쿼리)와 각 레코드의 연관 데이터(n개 쿼리)를 로드할 때 총 n+1개의 쿼리가 발생하는 현상입니다. 이는 Ruby on Rails의 지연 로딩(lazy-loading) 특성으로 인해 흔히 발생하며, 애플리케이션 성능 저하의 주요 원인입니다.
n+1 쿼리 발생 원인
- 컬렉션 반복: 반복문 내에서 미리 로드되지 않은 연관 데이터에 접근할 때 발생합니다.
- 코드 추상화: 헬퍼, 모델 메서드, 메타 프로그래밍,
delegate
패턴 등에 의해 숨겨져 코드만으로 식별하기 어렵습니다. - Law of Demeter 위반: 객체가 직접 의존성이 아닌 ‘낯선’ 객체의 메서드를 호출할 때 유발될 수 있습니다.
n+1 쿼리 식별 방법
n+1 쿼리는 애플리케이션 로그 분석을 통해 가장 효과적으로 식별됩니다.
- 반복되는 유사 쿼리: 특정 테이블에 대한 유사
SELECT
쿼리가 연속적으로 또는 비순차적으로 반복되는 패턴을 찾습니다. - 중복 쿼리:
exists?
,count
,load
등 유사 기능이 별개의 쿼리를 유발하는 경우를 파악합니다.
n+1 쿼리 최적화 고려 사항
모든 n+1 쿼리를 해결하는 것이 항상 최선은 아닙니다. 다음 요소를 종합적으로 고려해야 합니다.
- SLO (Service Level Objectives) 충족 여부: 엔드포인트가 성능 목표를 충족한다면, 최적화 우선순위가 낮을 수 있습니다.
- 캐싱 (Caching) 효과: 캐싱 전략이 적용된 경우, n+1 쿼리가 실제 성능에 미치는 영향이 미미하거나 오히려 이점이 될 수 있습니다.
- Eager-loading의 실제 성능: 복잡한
JOIN
쿼리보다 여러 개의 간단한 쿼리가 특정 환경에서 더 효율적일 수 있으므로, 실제 벤치마킹을 통한 검증이 필수적입니다.