prefetch_related
를 효율적으로 활용하면 가독성을 높이고 성능을 개선할 수 있습니다
목차
- prefetch_related 연산은 쿼리가 여러번 수행된다
- Relation(prefetch_related, select_related)를 지정하지 않고 filter에 조건을 추가하면 자동으로 inner join 질의가 수행된다
- 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 |
댓글