Django prefetch_related(1)

2 분 소요


앞서…

열심히 Django document를 보며 prefetch_related를 공부했습니다.

사실 장고는 document가 굉장히 잘 정리되어있는 편이라 document만 잘 정독해도 원하는 거의 대부분의 내용을 얻을 수 있습니다.

저는 제가 공부한 prefetch_related의 내용을 정리하고자 포스팅을 했습니다.

정확하지 않은 내용이 있을 수 있습니다!!

틀린 부분이 있다면 꼭 댓글로 남겨주시길 부탁드리겠습니다!

감사합니다!



Prefetch related

prefetch_related()는 select_related()와 마찬가지로 N + 1 problem을 해결하기 위해 사용되는 django ORM 메서드입니다.

select_related와 목적은 같지만 사용되어야 하는 상황이나 컨셉은 전혀 다릅니다.




prefetch_related() vs select_related()

select_related()는 INNER JOIN이었음을 확인할 수 있었습니다.

그렇다면 prefetch_related()는 어떻게 동작하는 것일까요?

prefetch_related 또한 select_related와 같이 eager loading을 하게 됩니다.

prefetch_related로 지정한 관계된 테이블의 데이터를 eager loading 하는 것이죠.

메인으로 가져오는 테이블의 쿼리문 한개, 그리고 역참조, 혹은 다대다 관계를 가지고 있는 테이블의 데이터를 가져오는 쿼리 한개, 총 두개의 쿼리를 사용합니다.

select_related는 일대일 관계(One to One), 정참조 관계에서 사용됩니다.

그에 반해 prefetch_related는 역참조 관계, 다대다 관계(Many to Many)에서 사용됩니다.




Simple example - 역참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Chef(models.Model):
    name = models.CharField()
    grade = models.IntegerField()
    store = models.ForeignKey(Store)

    class Meta:
        db_table = 'chef'


class Store(models.Model):
    name = models.CharField()
    address = models.CharField()

    class Meta:
        db_table = 'store'

위와 같이 모델링이 되어있을때 Chef 👉 Store는 정참조 관계, Store 👉 Chef는 역참조 관계에 있는 것을 알 수가 있습니다.

이때, 성동구 혹은 마포구에 위치한 가게를 불러오되, 그 가게들에서 일하고 있는 요리사들을 earger loading 하고 싶다면 아래와 같이 작성해주시면 됩니다.

1
Store.objects.filter(address__in=['성동구', '마포구']).prefetch_related('chef_set')




_set

음… 이상한 부분이 하나 있군요?

prefetch_related()에 인자로 넘겨준 string을 보면 ‘chef’가 아닌 ‘chef_set’으로 되어있습니다.

역참조 관계에 있는 데이터를 불러올 때 django는 자동으로 {역참조_관계의_클래스명.lower()}를 사용하도록 합니다.

만약 클래스명이 Chef가 아닌 Cook 이라면 ‘cook_set’으로 인자를 넘겨주면 되겠습니다.

_set 이 아닌 직접 역참조 관계를 불러올 이름을 설정하고 싶으면 ForeignKey() 필드에 인자로 related_name = chefs을 넘겨주면 됩니다.

이럴 경우에는 Store.objects.prefetch_related('chefs')로 사용하면 됩니다.

위 경우에서는 Store에 대한 데이터를 불러오는 쿼리 한개, 그리고 관계된 Chef에 대한 데이터를 불러오는 쿼리 한개, 총 두개의 쿼리를 사용하게 됩니다.

  • TMI! related_name = '+'와 같이 +를 related_name의 인자로 넘겨줄 경우 역참조를 허용하지 않겠다는 의미가 됩니다. store = models.ForeignKey(Store, related_name='+')로 설정한 경우 Store.objects.prefetch_related('chef')를 사용할 시 다음과 같은 에러 메세지가 나타납니다.

    AttributeError: Cannot find ‘chef’ on Store object, ‘chef’ is an invalid parameter to prefetch_related()




어라…? 쿼리를 더 많이 쓰나요?

맞습니다. prefetch_related를 사용하게 되면 무조건 쿼리를 최소한 한개 추가로 사용하게 됩니다

한개 이상인 이유는 prefetch_related() 내에 넘겨주는 인자로 N개의 관계 테이블명을 넘겨주면 N개의 쿼리를 추가로 발생하기 때문입니다.

그렇다면 prefetch_related는 결국 실행하는 쿼리 수를 늘리는 것일까요?

상황에 따라 다릅니다.

prefetch_related를 적절히 사용하지 않으면 오히려 불필요하게 쿼리 수를 늘릴 수도 있습니다.

댓글남기기