⚠️
N+1 쿼리 문제
성능의 적 — includes로 해결하는 방법
N+1 문제는 Rails에서 가장 흔한 성능 문제입니다.
문제 상황:
# Controller
@posts = Post.all # 쿼리 1: SELECT * FROM posts
# View
<% @posts.each do |post| %>
<%= post.user.name %> # 쿼리 N: SELECT * FROM users WHERE id = ? (매번 실행!)
<% end %>
포스트가 100개면 101번 쿼리 실행!
해결: includes
@posts = Post.includes(:user) # 쿼리 2번으로 해결
# SELECT * FROM posts
# SELECT * FROM users WHERE id IN (1, 2, 3, ...)
3가지 방법:
includes— Rails가 자동으로 최적 전략 선택preload— 별도 쿼리로 로드 (기본)eager_load— LEFT OUTER JOIN 사용
탐지 도구: bullet gem이 N+1을 자동 감지하여 경고를 표시합니다.
구조 다이어그램
N+1 문제 (101 쿼리!)
1. SELECT * FROM posts
2. SELECT * FROM users WHERE id = 1
3. SELECT * FROM users WHERE id = 2
4. SELECT * FROM users WHERE id = 3
... (N번 반복!)
101. SELECT * FROM users WHERE id = 100
Post.all → post.user (매번 쿼리)
includes 적용 (2 쿼리!)
1. SELECT * FROM posts
2. SELECT * FROM users WHERE id IN (1, 2, 3, ..., 100)
끝! 단 2번의 쿼리로 완료
Post.includes(:user) → 미리 로드
코드 비교:
Before
@posts = Post.all
After
@posts = Post.includes(:user)
핵심: <strong>includes 한 줄</strong>로 101번 쿼리를 2번으로 줄임 — bullet gem으로 자동 탐지 가능
핵심 포인트
1
문제 인식: 반복문 안에서 연관 데이터에 접근하면 N+1 발생
2
rails 로그에서 반복되는 SELECT 쿼리 확인
3
includes(:association)를 컨트롤러 쿼리에 추가
4
중첩 관계: includes(posts: :comments) 또는 includes(:posts, :profile)
5
bullet gem 설치로 N+1 자동 감지
6
strict_loading 모드로 N+1 발생 시 예외 발생하도록 설정 (Rails 6.1+)
장점
- ✓ includes 한 줄로 성능 수십 배 향상
- ✓ bullet gem으로 자동 탐지 가능
- ✓ strict_loading으로 런타임 방지 가능
- ✓ SQL 로그로 쉽게 확인 가능
단점
- ✗ 모든 곳에 includes를 넣으면 불필요한 데이터 로딩
- ✗ eager_load는 JOIN으로 인한 데이터 중복 가능
- ✗ 복잡한 관계에서는 최적 전략 판단 필요
- ✗ 메모리 사용량 증가 가능
사용 사례
게시글 목록 + 작성자 표시
주문 목록 + 상품 정보
카테고리 + 하위 아이템
대시보드 통계 페이지