(Django) select_related()와 prefetch_related()
select_related()와 prefetch_related()
프로젝트를 진행하면서 화면 로딩이 느린 문제가 발생했다. 이를 해결하기 위해 django-debug-toolbar를 설치하였다. 이를 통해 페이지가 로딩되면서 발생하는 쿼리를 확인해 보았다. 그 결과 3개의 데이터를 조회하는데 중복과 비슷한 쿼리가 굉장히 많이 발생하는 것을 볼 수 있었다. 쿼리의 중복을 없애고 쿼리를 최소화 할 수 있는 방법으로 위 2개의 메소드를 사용하는 것이였다.
select_related()
- 정참조 ForeignKey, OneToOneField 관계와 역참조 OneToOneField에서 활용한다.
- 데이터베이스단에서 inner join으로 쿼리가 수행된다.
어떻게 활용하는가
toolbar를 읽어보면 어디부분에서 쿼리가 중복으로 발생했는지 볼 수 있다. 템플릿을 보면 article인스턴스가 1:n 관계인 user의 writer 필드에 접근하기 때문에 articles의 개수만큼 쿼리가 중복으로 발생한다.
{% for article in articles %}
<strong>{{article.writer.nickname}}</strong></span>
Article 객체를 쿼리로 반환하는 과정에서 Article객체에서 1:n으로 물고있는 User 모델의 writer 속성을 미리 로딩하여 캐싱하게 된다.
article_list = Article.objects.filter(is_deleted = False).select_related('writer')
단순 쿼리와의 차이점
단순쿼리의 경우 , 아래와 같이 작성하게 된다면 article 객체만 쿼리하기 때문에 각각 writer와 category를 얻기 위해서 다시 쿼리에 접근한다.
article = Article.objects.get(id=1)
writer = article.writer
category = article.category
select_related를 사용하게 되면 article 객체만 쿼리를 해도 sql에서 미리 category와 writer를 join하여 캐싱한다.
article_list = Article.objects.filter(is_deleted = False).select_related('writer', 'category')
prefetch_related()
- 정참조 ManyToMany, 역참조 ForeignKey, ManyToMany에서 활용한다.
- select_related()와 역할은 비슷하지만 방식이 좀 다르다
- sql문에서 join이 되지 않고 각 관계별로 데이터베이스 쿼리를 수행하고 파이썬단에서 join을 수행한다
comment는 article에서 역참조를 하고 있는 테이블이다. 해당 속성들을 미리 로딩하기 위해 prefetch_related를 사용한다
<div class="social">
<span class="social-comment">댓글 + </span>
</div>
article_list = Article.objects.filter(is_deleted = False).prefetch_related('comment')
위의 로직을 수행한 결과 10개의 쿼리로 줄어들었으며 중복이 제거 된 것을 볼 수 있다