We.TIL 29 : prefetch_related와 to_attr을 활용한 효과적인 역참조 방법

김기욱·2020년 8월 25일
4

We.TIL

목록 보기
47/69

prefetch_related란?

<장고 공식문서를 참조 : 미숙한 번역 주의>
프리페치란 지정해놓은 관계된 객체의 특정열을 하나로 미리 묶어 쿼리셋으로 반환하는 기능이다.
본질적으로 프리페치는 select_related와 동일한 목적으로 사용되는데, 둘 다 (연관된 객체들을 참조할때마다 발생되는) 과다한 database hit를 줄이는데 의의를 가지고 있다. 물론 목적은 같지만 작동방식은 꽤나 차이난다.

우선 select_related의 경우, SQL_join기능으로 작동된다. SQL_join은 SQL 구문 중 select구문영역에서 발생되는데, 동일한 데이터베이스 쿼리선에서 관계된 객체들을 '한 번'에 검색해서 '한 번'에 뽑아 오는 원리다. 그래서, 복잡한 관계에서 한번에 너무 많은 데이터를 추출했을 때, 발생 가능한 오류를 피하기 위해 one to one이나 FK로 정참조로 연결된 one to many에서만 쓰인다.

프리페치의 경우는 조금 다른데, select_related처럼 한번에 모든 관계를 단 한번에 검색하는게 아니라 프리페치로 설정해놓은 객체마다 (쿼리를 보내)검색을 하고, 가져온다. 즉 쿼리 횟수는 selected_related보다 일반적으로 증가하게 된다. 또한 SQL선에서 join하지 않고 파이썬 내에서 join한다. 그렇기에 프리페치는 select_related와는 달리 프리페치는 one to one, one to many를 포함한 모든 관계에서 사용이 가능하다.(범용성이 좋다)

to_attr

to_attr은 프리페치의 옵션기능인데, 이를 사용하기 위해서는 우선 장고 내장모듈인 Prefetch를 import해야된다. 다음과 같이 작성하면 된다.

from django.db.models import Prefetch

to_attr옵션은 프리페치로 붙여놓은 항목을 자동으로 리스트로 변환시켜줘서 캐시에 내장시켜주는 기능을 가지고 있는데, 반환되는 값이 Query_set이여서 for loof 사용이 필요할 때, 사용한다면 코드 길이 및 쿼리실행 단축을 이끌어낼 수 있다.(ex : 역참조관계)

자 이제 간단한 예시와 함께 attr을 사용법을 익혀보자.

# 상품 테이블
class Product(models.Model):
    sub_category    = models.ForeignKey('SubCategory',on_delete=models.CASCADE)
    name            = models.CharField(max_length=100)
    color           = models.ManyToManyField('Color',through='ProductColor')
    
    class Meta:
        db_table = 'products'

#상품 색상 테이블
class Color(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        db_table = 'colors'

#중간 테이블
class ProductColor(models.Model):
    product = models.ForeignKey('Product',on_delete=models.CASCADE)
    color   = models.ForeignKey('Color',on_delete=models.CASCADE)

    class Meta:
        db_table = 'product_colors'

Set_manager를 활용해 color의 이름들까지 뽑아내려면 다음과 같이 쿼리문을 작성해야 한다.

products = Product.objects.get(id=1)

[p.color.name for p in products.productcolor_set.all()]

길이도 긴데다가 get과 all() 두 번을 사용해 db hit / 쿼리실행도 많이 일어나게 된다. 그렇다면 여기서 to_attr를 써보자.

products = Product.objects.prefetch(Prefetch('color',
	to_attr='to_color')).get(id=1)
    
[p.name for p in products.to_color]

이미 color 테이블과 프리페치로 연결되어있으므로 중간테이블을 경유할 필요없이 바로 리스트 컴프리헨션을 써서 넣어주면 되며, to_attr로 지정된 to_color를 쓰는 순간 지정된 객체가 리스트로 형변환이 일어나기때문에 .all() 이나 .filter() 같은 쿼리셋에 붙이는 메소드를 쓸 필요가 없어지게 된다.

물론 프리페치 고유효과로 인한 디비히트수 감소는 기본이다.

profile
어려운 것은 없다, 다만 아직 익숙치않을뿐이다.

2개의 댓글

comment-user-thumbnail
2020년 8월 30일

이것도 완전 신기....

답글 달기
comment-user-thumbnail
2020년 9월 6일

기욱님 화이팅~

답글 달기