본문 바로가기
Djnago

[Django] prefetch_related

by UnoCode 2020. 7. 25.

prefetch_related

를 효율적으로 활용하면 가독성을 높이고 성능을 개선할 수 있습니다

 

목차

  1. prefetch_related 연산은 쿼리가 여러번 수행된다
  2. Relation(prefetch_related, select_related)를 지정하지 않고 filter에 조건을 추가하면 자동으로 inner join 질의가 수행된다
  3. prefetch_related 연산결과를 to_attr 인수에 담아서 사용하자

1. prefetch_related 연산은 쿼리가 여러번 수행된다

perfetch_related 함수는 조인을 하지 않고 개별 쿼리를 실행 후, django에서 직접 데이터 조합을 합니다.

 

 

예를 들어 reviews 모델과 이를 참조하는 worry 모델을 가정합니다.

 

class Worry(models.Model):
    name = models.CharField(max_length=255)


class Review(core_models.TempDate):
    product = models.ForeignKey("products.Product", on_delete=models.CASCADE, null=True)
    # userid 필요
    star_point = models.IntegerField(default=0)
    worry = models.ForeignKey(
        "Worry", on_delete=models.CASCADE, null=True
    )
    skintype = models.ForeignKey("SkinType", on_delete=models.CASCADE, null=True)
    content = models.TextField(max_length=1000)
    image_url = models.TextField(max_length=255)
    
    queryset = Review.objects.prefetch_related('worry')
2020-07-25 13:33:34,715 DEBUG (0.001) SELECT `reviews`.`id`, `reviews`.`created`, `reviews`.`updated`, `reviews`.`product_id`, `reviews`.`star_point`, `reviews`.`worry_id`, `reviews`.`skintype_id`, `reviews`.`content`, `reviews`.`image_url` FROM `reviews` LIMIT 21; args=()
2020-07-25 13:33:34,719 DEBUG (0.001) SELECT `worries`.`id`, `worries`.`name` FROM `worries` WHERE `worries`.`id` IN (1, 2, 3, 4, 5); args=(1, 2, 3, 4, 5)

 

reviews 테이블에서 조건에 맞는 데이터 쿼리를 수행합니다..

그 후에 위의 리스트를 조건으로 worry 테이블 쿼리를 수행합니다

 따라서 prefetch_related 연산은 쿼리를 최소한 두번은 수행합니다.

(filter 조건에 따라서 순서가 반대로 될 수 있습니다.

worry모델의 컬럼으로 조건이 들어가 있다면, worry 테이블을 조회 후 reviews 테이블을 조회하게 됩니다)

prefetch_related 연산이 추가되면, 그만큼 쿼리 수행도 증가합니다.

Review.objects.prefetch_related(
    'worry'
).prefetch_related(
    'skintype'
)

 

skintype 이라는 모델이 추가되고 related_name이 skintype라고 할때,

위의 쿼리셋은 Review 테이블 조회 → worry 테이블 조회 → skintype 테이블 조회를 하여 총 세번을 수행합니다.

3. 복잡한 Prefetch 코드의 가독성을 높이고 성능을 개선하는 방법

Relation(prefetch_related, select_related)를 지정하지 않고 filter에 조건을 추가하면 inner join으로 쿼리를 한번만 수행하게 할 수 있습니다

상황에 따라서 prefetch_related를 써야만 할 때가 있습니다.

 

예로 rest_framework의 ViewSet에서는 반환하는 QuerySet 객체가 정해져있습니다. 부모모델에서 자식모델의 필드를 접근하거나, filter를 위해서는 prefetch_related를 사용하게 됩니다

 

# views.py
from rest_framework.viewsets import ModelViewSet
class ReviewViewset(ModelViewSet):
    def get_queryset(self):
        return = Review.objects.prefetch_related('worry')

 

Prefetch연산을 여러번 해야하는 경우엔 코드 가독성이 급격하게 나빠질 뿐만 아니라 쿼리 질의수도 그만큼 증가합니다.

이를 해결 하는 방법은 바로, 필터에만 조건을 추가하고 Relation을 제거하는 것 입니다.

 

# 기존의 queryset
Review.objects.prefetch_related(
    'worry'
).filter(
    worry__name='트러블'
)
# 변경후의 queryset
Review.objects.filter(
    worry__name='트러블'
)

Relation을 제거 후, 쿼리셋을 수행하면 어떻게 될까요?

2020-07-25 13:52:10,938 DEBUG (0.002) SELECT `reviews`.`id`, `reviews`.`created`,
`reviews`.`updated`, `reviews`.`product_id`, `reviews`.`star_point`, 
`reviews`.`worry_id`, `reviews`.`skintype_id`, `reviews`.`content`, `reviews`.
`image_url` 
FROM `reviews` 
INNER JOIN `worries` ON (`reviews`.`worry_id` = `worries`.`id`)
WHERE `worries`.`name` = '트러블' LIMIT 21; args=('트러블',)

실행결과를 보면 inner join으로 한번만 질의가 수행됩니다.

그 이유는 장고 내부에서 join chain이 없으면 자동으로 inner join으로 쿼리를 하도록 강제하기 때문입니다.

왜냐하면 join chain이 없다면 필터조건이 아무리 많이 추가되어도 쿼리결과에 영향을 주지 못하기 때문입니다.

 

queryset = Review.objects.prefetch_related(
    Prefetch(
        'worry',
        queryset=Worry.objects.all(),
        to_attr='to_worry'
    )
)

4. Prefetch_related 연산을 저장하여 쿼리 질의 수를 줄이는 방법

 

prefetch 연산 결과를 to_attr 인자로 할당하여 접근할 때 마다 수행되는 쿼리를 제거할 수 있습니다

 

Review쿼리셋 객체에 to_worry라는 필드가 생성되고, worry 객체의 속성을 접근해도 쿼리가 수행되지 않습니다.

예를 들어 위의 쿼리셋 결과로 worry의 name 목록가져오는 코드는 다음과 같습니다.

 

# to_attr 사용전
name_list = [ worry.name for worry in queryset.worry.all() ] 


# to_attr 사용후
name_list = [ worry.name for worry in queryset.worry ]

 

위의 쿼리결과는 쿼리를 추가적으로 또 수행해야 합니다. worry은 QuerySetManager이기 때문에 명시적으로 .all()로 쿼리를 수행해야 worry 객체에 접근할 수 있습니다.

아래 결과는 이미 prefetch할때 결과를 list 형태로 담고 있기 때문에 더 이상의 쿼리질의가 일어나지 않습니다.

이게 좋은 부분은, viewset에서 쿼리셋을 수행시 to_attr에 담아두고, serializer에서 가져다 쓸때 쿼리가 수행을 획기적으로 줄일 수 있게 됩니다.

 

네줄로 요약하자면

  • prefetch_related 연산은 쿼리가 여러번 수행된다
  • ORM 필터 조건에 따라서 select_related 보다 prefetch_related가 유리할 때도 있다
  • relation(prefetch_related, select_related)를 지정하지 않고 filter에 조건을 추가하면 자동으로 inner join 질의가 수행된다
  • prefetch_related 연산결과를 to_attr 인수에 담아서 사용하자

'Djnago' 카테고리의 다른 글

[Django] DB Query_Count And Logging  (0) 2020.08.09
[Django] login Decorator useing pyjwt  (0) 2020.07.18
[Django] 기본 세팅  (0) 2020.07.16
[Django] Custom command Create  (0) 2020.07.04
[Django] Django-tutorial 따라하기 1-2  (0) 2020.07.02

댓글