❓select & prefetch related

yeeun lee·2020년 4월 21일
2

쿼리셋과 친해져야 장고에 데이터를 넣고 뷰를 작성할 때 문제가 없다. 우선 Queryset 관련해서 배운 내용을 정리하고, 그 뒤 mysql 데이터를 넣는 방법을 정리하고자 한다.

아래 내용은 django 공식 문서를 기반으로 한다 ⭐️

시작하기 전에

Queryset 관련해서 django 배우는 초반에 정리를 해놓긴 했는데, 많이 까먹어서 핵심적인 부분만 다시 정리한다.

  • Queryset은 objects.values()로 dictionary의 key와 value에 접근이 가능하다.
  • QuerySet()은 리스트이고, 객체는 dictionary 이기 때문에 변수[index]['key'] 의 형식으로 value를 볼 수 있다.

  • filter를 쓸 경우 내가 원하는 조건(id, price 등등) 따른 결과'들'이 리스트로 리턴되어, 인덱스[index] 를 통해 객체에 접근할 수 있다. 객체의 value는 key['key']값으로 받을 수 있다.
  • get을 쓸 경우 하나의 결과만 리턴한다. 때문에 dot notation으로 접근 가능하다.
Bracelet.objects.get(id=1)
<Bracelet: Bracelet object (1)>

Bracelet.objects.get(id=1).name <!--return:'프레지던트'-->
Bracelet.objects.get(id=2).name <!--return:'다이아몬드가 세팅된 프레지던트'-->

Bracelet.objects.filter(id=1)
<QuerySet [<Bracelet: Bracelet object (1)>]>

Bracelet.objects.filter(id=1)[0] <!--return: <Bracelet: Bracelet object (1)>-->
>>> Bracelet.objects.filter(id=1)[0].name <!--return:'프레지던트'-->

1. select_related

Foreign-key 관계를 따라오는 쿼리셋을 반환하고, 쿼리를 실행할 때 추가적으로 관련되어있는 객체 데이터를 선택해준다.

데이터베이스 쿼리 없이 foreign-key 관계를 나중에 사용할 때에, 하나 이상의 복잡한 쿼리를 가져올 수 있는 퍼포먼스 부스터라고 할 수 있다. 일반적인 lookup과 select_related() lookup을 비교해보자.

hit the database
데이터베이스 히트가 무엇인지 찾아보려고 했는데 해당 단어에 대한 내용이 정확하게 나온 글은 찾기 어렵지만, 어쨌든 single data에 대한 request를 말하는 것 같다.

lookup

select_related가 아니라면 dot notation을 통해 객체의 객체를 가져오는 방식으로 접근해야 한다. 하지만 select_related는 객체 관련 데이터를 캐싱해주기 때문에, 두번씩 data를 hit하지 않는다. 보통 정참조 관계일 때 요 함수를 쓰게 된다.

# plain lookups
# Hits the database.
e = Entry.objects.get(id=5)

# Hits the database again to get the related Blog object.
b = e.blog

# select_related lookup
e = Entry.objects.select_related('blog').get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated

caching

데이터를 가져오려면 select를 해야되는데(=연산해야 하는데), select_related로 바로 쓸 수 있도록 한번 변수로 저장해놓고 갖고온 정보로 return하게 된다. 이렇게 한 번에 쓸 수 있도록 저장해주는 것을 캐싱한다고 한다.

예를 들어 detail table이 product table에 대해 foreign key를 갖고 있다고 하자. 그러면 detail 기준으로 정방향 참조이기 때문에, detail의 특정 id를 기준으로 product에 대한 모든 정보를 가져올 수 있다.

아래는 detail table id=1의 product table 정보를 가져오는 코드다. 관련 값을 모두 캐싱해줘서 data hit을 하지 않아도 바로바로 접근이 가능한 것을 알 수 있다.

from product.models import *
d = Detail.objects.select_related('product').get(id=1)

# d는 <Detail: Detail object (1)> 즉 객체로 저장됨
# 객체이기 때문에 Dot notation으로 연결된 변수 접근 가능 
# 객체의 객체를 불러오는 것도 가능 

>>> d.size # <Size: Size object (2)>
>>> d.material # <Material: Material object (1)>
>>> d.material.name # '옐로우 골드'

2. prefetch_related

피자에 들어간 토핑은 피자 하나를 보면 바로 알 수 있다. 하지만 감자 토핑이 들어간 피자를 찾으려면 복잡해진다.

피자의 예시를 떠올리면서 생각해보자. 정뱡항은 foreign-key를 가지고 있는 것(피자가 토핑을 가지고 있는 것)이다.

내가 어디로 가야할지 알고 있기 때문에 웬만하면 select_related를 쓰는 것이 좋다. foreign-key를 갖고 있는 상황이 아니라면(토핑만 아는 상황인데 피자를 찾아야 할 때) 정보를 모르기 때문에, 이렇게 못 쓰는 부분에 대해서만 prefetch_related 쓰는 것이다.

예시

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):
        return "%s (%s)" % (
            self.name,
            ", ".join(topping.name for topping in self.toppings.all()),
        )

여기서 문제는 Pizza.__str__()self.toppings.all()을 요청할 때마다 데이터베이스에 쿼리해야한다는 것이다. 그래서 Pizza.objects.all()은 Pizza Queryset 안에서 모든 아이템에 대해 Toppings table을 쿼리해주어야 한다.

Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...

반면 prefetch_related를 쓰면 역 방향에서 객체를 가져올 수 있도록 저장해줌으로써 위와 같이 함수, 쿼리가 필요한 작업들을 줄여준다.

Pizza.objects.all().prefetch_related('toppings')

예시2

프로젝트 중에 작성한 모델을 기반으로 prefetch_related를 살펴보면 다음과 같다. 상품은 Detail table에게 참조 당하고(?) 있기 때문에 detail 관련 정보를 알지 못한다.

이런 상황에서 prefetch_related를 사용한다면, model에 many to many field를 통해 product의 size detail을 받아올 수 있다.


Product.objects.all().prefetch_related('size_detail').get(id=1)
# return <Product: Product object (1)>

Product.objects.all().prefetch_related('size_detail').get(id=1).category
<Category: Category object (1)>

Product.objects.all().prefetch_related('size_detail').get(id=1).category.name
'클래식 시계'

_set

relation manager class model. w를 wheel의 instance라고 하고, c를 car의 instance라고 하자. 이 때, w는 foreign-key를 갖고 있기 때문에 dot notation으로 바로 불러올 수 있다.

반면 c는 참조 당하는 입장이기 때문에 w에 대한 정보를 모른다. 이 때 wheel_set을 통해 car와 관련된 wheel 객체를 가져올 수 있게 된다.

class Car(models.Model):
    pass

class Wheel(models.Model):
    car = models.ForeignKey(Car, on_delete=models.CASCADE) 

# Let's say w is an instance of Wheel, and c is an instance of Car:
>>> w.car # returns the related Car object to w
>>> c.wheel_set.all() # returns all Wheel objects related to c

Related name을 쓰는 방법도 있지만, 코드를 까봐야되기 때문에 어떤 데이터인지 모를 수 있다. 때문에 _set을 써주는 것이 더 좋다.

데이터를 넣을 수 있는 방법에 대해 얼른 배워야겠다 🙄

profile
이사간 블로그: yenilee.github.io

0개의 댓글