Django ORM 쿼리 최적화

GisangLee·2022년 2월 22일
0

django orm

목록 보기
1/2

장고는 특정 시기에만 쿼리를 날린다.
그 시기를 알아야 최적화가 가능하다.

Django ORM의 특징

  • Lazy Loading
    - 지연 호출
  • Eager Loading
    - 즉시로딩 : N + 1 Problem

ORM에서는 정말 필요한 시점에만 SQL을 호출하는 특징이 있다.

다시 말해, 정말 필요한 시점이 아니면
QuerySet은 SQL을 호출하지 않는다는 것이다

Django가 쿼리를 날리는 시기

user = User.objects.all()
a = user.filter(first_name='a')  # 아직 DB에서 데이터를 가져오지않음
order_a = a.order_by('id') # 아직 DB에서 데이터를 가져오지않음
b = user.get(id=1)
parent = b.parent.name  # << 여기서 쿼리를 날림

자 그러면 아래 문장은 몇 번의 쿼리를 실행할 지 맞춰보자

users = User.objects.all()
first_user = users[0]
user_list = list(users)
  • 정답은 두 번이다.
    첫번째 유저와 모든 유저를 가져오기 위해 User.objects.all()이라는 명령어로 다 가져온듯 싶었지만,
    아까 언급했듯이 이 문장은 SQL문을 호출하지 않았다.
    아직 데이터를 가져오지 않았다는 것이다.

  • 따라서 첫번째 유저를 가져오기 위해 users[0]라고 했기 때문에, 여기서
    SQL문 호출 한 번,

  • 그리고 모든 유저의 정보를 가져오기 위한 list(users).
    즉 SQL문이 한 번더 호출되어 총 두 번의 쿼리가 발생했다

N+1 문제

for문을 돌 때, 조회할 때마다 sql이 계속 호출되는 문제를 N + 1 문제라고 한다.
(유저를 호출하는 sql 한번) + (유저 개수 N) = (N + 1 개의 쿼리)

이 문제를 해결할 수 있는 아름다운 방법을 소개하고자 한다.

1. select_related

  • 객체가 역참조하는 one-to-one or many-to-one이거나,
    또는 정참조 foreign key 일 때 사용
  • SQL INNER JOIN을 통해 즉시로딩
class Child(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

class Parent(models.Model):
    children = models.ForeignKey("Child", related_name="parent", on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    def __str__(self):
        return self.name

위 모델은 1:N으로 이루어진 관계형 모델이다.

qs = Parent.objects.select_related("children")

위 쿼리를 통해 최소한의 쿼리로(중복 쿼리를 줄이고) 데이터를 가져올 수 있음을 기대한다.
성능 향상에 도움이 되는 것이다.

위 사진은 select_related 없이 데이터를 가져오는 예시를 보여주는 사진인데
유사하거나 중복되는 쿼리가 계속 반복됨을 알 수 있다.


하지만 select_related를 사용하면 최소한의 쿼리만으로 데이터를 불러오는 것을 알 수 있다.

2. prefetch_related

  • 객체가 M:N 관계이거나, 역방향 참조할 때 사용
  • python 추가 쿼리 발생
qs = children.objects.prefetch_related("parent")

역방향 참조로 Child 입장에서 사용한 모습이다.

prefet_related를 사용하면 select_related와 마찬가지로
중복되는 쿼리를 줄일 수 있으며 성능향상을 기대해 볼 수 있다.

3. Prefetch

  • 추가 쿼리셋을 제어하는 방법
Prefetch(lookup, queryset=None, to_attr=None)

Prefetch의 원형이다.

우리는 추가적인 쿼리셋을 제어하고 싶을 때가 많다.
그럴 때 prefetch_related 안에서 Prefetch를 사용하면 된다.

    from django.db.models import Prefetch
    queryset = post_models.Parent.objects.prefetch_related(
        Prefetch(
            "child",
            queryset=post_models.Child.objects.prefetch_related(
                Prefetch(
                    "child_of_child",
                    queryset=post_models.ChildofChild.objects.prefetch_related("
                        "child_of_child_of_child"
                    ")
                )
            )
        ))

위 형태로 데이터 테이블끼리 깊게 연결된 형태도 제어할 수 있다.

profile
포폴 및 이력서 : https://gisanglee.github.io/web-porfolio/

0개의 댓글

관련 채용 정보