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.name과 project.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로 확인하면서 진행해야 한다.