C.W.K.
Stream
Lesson 04 of 10 · published

N+1 쿼리 문제

~14 min · operations, queries

Level 0스키마 새싹
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

가장 흔한 ORM 버그

유저 100 명 쿼리. 각 유저마다 그 주문 요청 — 1 + 100 = 101 쿼리. 각 쿼리가 isolation 에선 빠름; round-trip 오버헤드가 페이지 파괴. 이게 N+1 쿼리 문제, 모든 ORM, 모든 프레임워크, 모든 팀에 등장.

발견하는 법

드라이버 레벨 쿼리 로깅 켜고 카운트 봐. 5 쿼리 해야 할 페이지가 500 하면 N+1. Django Debug Toolbar, Rails Bullet, SQLAlchemy events 시스템 같은 도구가 패턴 자동 flag.

세 수정

  1. Eager loading — 관련 행을 단일 JOIN 또는 단일 IN-clause 에 fetch. 모든 ORM 이 이름 가짐: selectinload (SQLAlchemy), prefetch_related (Django), includes (Rails).
  2. Raw SQL 의 수동 JOIN — ORM eager-loading 이 어색하면, SQL JOIN 항상 가용.
  3. Python 아닌 SQL 에서 aggregate — '각 유저마다 주문 카운트' 대신 COUNT(*) ... GROUP BY user_id.

Code

위장한 N+1·python
# N+1: user 1 쿼리, 그 다음 order N 쿼리
users = session.query(User).all()
for u in users:
    orders = u.orders  # ← 각 접근이 쿼리 발사
    print(u.name, len(orders))
수정: eager loading·python
from sqlalchemy.orm import selectinload

users = session.query(User).options(selectinload(User.orders)).all()
for u in users:
    print(u.name, len(u.orders))  # 추가 쿼리 0
수정: SQL 에서 aggregate·python
# 단일 쿼리: user + order count
rows = session.execute(
    text("""
      SELECT u.id, u.name, COUNT(o.id) AS order_count
      FROM users u
      LEFT JOIN orders o ON o.user_id = u.id
      GROUP BY u.id, u.name
    """)
).all()

External links

Exercise

프로젝트의 list-view 엔드포인트 찾기. 쿼리 로깅 켜기. 요청당 쿼리 몇 개 발사하는지 카운트. 5 넘으면 N+1 찾고 eager loading 또는 수동 JOIN 으로 수정.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.