Django ORM N+1 쿼리 문제 해결

문제 상황

대시보드 페이지 로딩 시간이 3초 이상 걸린다는 이슈가 들어왔다. Django Debug Toolbar로 확인해보니 SQL 쿼리가 200개 이상 실행되고 있었다.

기존 코드는 이랬다.

def get_projects(request):
    projects = Project.objects.all()
    return render(request, 'dashboard.html', {'projects': projects})

템플릿에서 project.owner.nameproject.tasks.count()를 호출할 때마다 추가 쿼리가 발생했다. 전형적인 N+1 문제였다.

해결 방법

1. select_related (1:1, N:1 관계)

외래키 관계는 select_related로 JOIN 처리했다.

projects = Project.objects.select_related('owner')

이제 owner 정보를 가져올 때 추가 쿼리가 발생하지 않는다.

2. prefetch_related (1:N, M:N 관계)

tasks 같은 역참조 관계는 prefetch_related를 사용했다.

projects = Project.objects.select_related('owner').prefetch_related('tasks')

내부적으로 별도 쿼리를 실행하지만, Python 레벨에서 조인하여 쿼리 수를 최소화한다.

최종 코드

def get_projects(request):
    projects = Project.objects.select_related(
        'owner'
    ).prefetch_related(
        'tasks',
        'tasks__assignee'
    )
    return render(request, 'dashboard.html', {'projects': projects})

결과

  • SQL 쿼리: 200+ → 4개
  • 로딩 시간: 3.2초 → 0.4초

Django ORM은 편리하지만 Lazy Loading 특성상 N+1 문제가 자주 발생한다. 쿼리 최적화는 항상 Debug Toolbar로 확인하면서 진행해야 한다.