본문 바로가기
N+1 문제란? django에서의 해결 방법은?
백엔드

N+1 문제란? django에서의 해결 방법은?

김혜원
김혜원위니브 백엔드 개발자2026/09/30

N+1 문제란? django에서의 해결 방법은 무엇인가요?

카페 비유로 이해하기

카페에서 웨이터가 손님 10개 테이블의 주문을 주방에 전달해야 하는 상황을 가정해봅시다.

  • 비효율적인 방법: 1번 테이블 주문 받기 → 주방에 전달 → 2번 테이블 주문 받기 → 주방에 전달 → … 주방을 10번 왕복
  • 효율적인 방법: 10개 테이블 주문을 한 번에 다 받고 → 주방에 한 번만 전달

여기서 "주방 왕복"이 바로 데이터베이스 쿼리입니다. 쉽게 말해 데이터베이스에 요청을 보내는 것인데요, 요청을 보내는 자체가 시간이 걸리는 작업이기 때문에 100번 왔다갔다 하느냐 vs 1번에 끝내느냐는 성능에 큰 차이를 만듭니다.

N+1 문제는 이처럼 목록 조회 1번 + 각 항목마다 추가 조회 N번 = 총 N+1번 쿼리가 발생하는 현상입니다.


코드로 보는 N+1 문제

강의(Course)와 강사(Instructor)가 있는 플랫폼을 예로 들겠습니다.

# models.py
class Instructor(models.Model):
    name = models.CharField(max_length=100)

class Course(models.Model):
    title = models.CharField(max_length=200)
    instructor = models.ForeignKey(Instructor, on_delete=models.CASCADE)
# views.py (문제 있는 코드)
courses = Course.objects.all()  # 쿼리 1번: 강의 목록 조회

for course in courses:
    print(course.instructor.name)  # 쿼리 N번: 강의마다 강사 정보를 따로 조회

강의가 100개라면? 101번의 쿼리가 실행됩니다. 1000개라면 1001번이고요.

DRF에서도 Serializer 안에서 관계 필드를 접근할 때 정확히 같은 문제가 발생합니다.

class CourseSerializer(serializers.ModelSerializer):
    instructor_name = serializers.CharField(source='instructor.name')  # 여기서 N번 쿼리 발생

    class Meta:
        model = Course
        fields = ['title', 'instructor_name']

Django ORM은 이 문제를 해결하는 두 가지 도구를 제공합니다.

ForeignKey, OneToOneField 관계에서 사용합니다. SQL의 JOIN을 사용해서 쿼리 1번으로 관련 데이터를 함께 가져옵니다.

# 쿼리 1번으로 강의 + 강사 정보를 함께 조회
courses = Course.objects.select_related('instructor').all()

for course in courses:
    print(course.instructor.name)  # 추가 쿼리 없음!

생성되는 SQL

SELECT course.*, instructor.*
FROM course
INNER JOIN instructor ON course.instructor_id = instructor.id;

ManyToManyField, 역방향 ForeignKey에서 사용합니다. 쿼리를 2번 실행하되, Python에서 미리 연결해줍니다.

# 강사별로 담당 강의 목록을 가져올 때
instructors = Instructor.objects.prefetch_related('course_set').all()

for instructor in instructors:
    for course in instructor.course_set.all():  # 추가 쿼리 없음!
        print(course.title)

생성되는 SQL

-- 쿼리 1: 강사 목록
SELECT * FROM instructor;

-- 쿼리 2: 해당 강사들의 강의 목록
SELECT * FROM course WHERE instructor_id IN (1, 2, 3, ...);

DRF에서 적용하기

ViewSet의 get_queryset에서 처리하는 것이 일반적입니다.

class CourseViewSet(viewsets.ModelViewSet):
    serializer_class = CourseSerializer

    def get_queryset(self):
        return (
            Course.objects
            .select_related('instructor')         # ForeignKey
            .prefetch_related('tags', 'students') # ManyToMany
        )

언제 뭘 써야 하나요?

관계 유형사용할 메서드이유
ForeignKey, OneToOneFieldselect_relatedJOIN이 효율적
ManyToManyFieldprefetch_relatedJOIN 시 행이 폭발적으로 증가
역방향 FK (_set)prefetch_related동일
중첩 관계select_related('a__b')연쇄 JOIN

마치며

N+1 문제는 코드만 보면 절대 눈에 띄지 않습니다. 실제로 발생하는 쿼리를 확인하려면 django-debug-toolbar나 Django의 connection.queries를 활용해보세요. 로컬에서는 데이터가 적어 빠르게 느껴지더라도, 실서비스에서 데이터가 쌓이면 즉각 체감하게 됩니다.

select_related와 prefetch_related를 습관적으로 챙기는 것만으로도 API 응답 속도가 눈에 띄게 달라집니다.

김혜원
김혜원
위니브 백엔드 개발자