ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • select_related 와 prefetch_related
    Django 2022. 4. 2. 15:41

    < select_relatedprefetch_related 를 학습하기까지의 여정>

    1. 1차 프로젝트를 통해 정참조, 역참조 개념 정리

    2. 2차 프로젝트를 통해 데이터를 불러올 때의 편의성을 고려하는 것이 중요하단 것을 깨달음

       - 처음 프로젝트를 진행하시는 분들은 무슨 말인지 모르실 수 있을 텐데 직접 그 불편함을 겪으신 후에 이에 대해

         학습하고 개선하시는 게 좋은 경험이 될 것이기에 저처럼 모른 상태에서 시작하시는 것도 좋다고 생각합니다.ㅎㅎ

    3. 기존 프로젝트들의 데이터가 워낙 적었고 기능 구현하기 바빠서 Query에 대한 고려를 하지 못 했었기에 이번에          select_related 와 prefetch_related 에 대해 학습하고 이를 이번 과제에서 사용할 목적으로 학습하려고 합니다.

     

     

    아래는 기존의 프로젝트에서 작성하던 방식으로 했을 때의 Query log 입니다.

    DB의 테이블도 7개밖에 없고 큰 틀의 데이터도 5개밖에 없는 정말 작은 데이터가 담긴 DB를 한번 전체 조회하는데 이렇게 많은 쿼리를 날렸다는 것을 알 수 있었습니다.

     

    기존의 Query log

    하지만 아래의 쿼리 로그를 보시면 굉장히 적어진 것을 보실 수 있습니다. 단지 select_related 와 prefetch_related를 적용했을 뿐인데 말이죠.

    지금이야 데이터도 적고 유저도 저 한 명이니 문제가 없겠지만 위와 같은 방식으로 작성해둔 코드라면 조회하는 유저가 많아지고 데이터와 기능들이 많아지면 서버에 많은 부하가 갈 것이기에 selct_related 와 prefetch_related 를 적절하게 사용하는 것이 굉장히 중요합니다.

     

    select_related 와 prefetch_related 적용 후의 Query log

     

    select_relatedprefetch_related 는 무엇이고 왜 사용할까?

    < 사용하는 이유 >

    데이터 중복 호출을 방지하여 DB 접근을 최소화함으로써 부하를 줄이고 이를 통해 성능을 향상하기 위해서 사용

     

    < 어떻게 DB 접근을 최소화하는가 >

    불러온 데이터들을 캐쉬에 저장해 두고 거기에서 활용할 수 있는 데이터들은 DB에 접근하지 않고 캐쉬에서 가져다 씀

     

    < select_related >

    < 사용 범위 >

    정참조에서 사용할 수 있기에 1:1 관계1:N 관계의 N 에서 사용

     

    < 특징 >

    SQL 문법 중 Join을 실시하는 Django ORM이다.

    한 번의 Query를 날려서 related objects 까지 불러온다.

    서버가 종료되기 전까지 이런 데이터들을 캐쉬에 저장해 두고 사용한다.

     

     

    < prefetch_related >

    < 사용 범위 >

    select_related + 역참조에서 사용

    대체적으로 1:N의 1 그리고 다대다 관계의 역참조에서 사용

     

    select_related보다 기본적으로 쿼리를 한번 더 날리기에 정참조에서는 굳이 사용할 필요가 없기에 역참조 관계에서 사용합니다.

     

    < 특징 >

    공식문서를 보면 select_related를 쓸 수 있는 관계에서도 사용이 가능하나

    prefetch_related는 추가 쿼리를 발생시키기에 대체적으로 select_related를 사용할 수 없는 관계에서 사용하는 것이 좋다.

     

    # models.py
    
    class Main_image(models.Model):
        image_url = models.CharField(max_length=1000)
        
        class Meta:
            db_table = 'main_images'
    
    class Movie(models.Model):
        main_image     = models.OneToOneField('Main_image', on_delete=models.SET_NULL, null=True)
        genres         = models.ManyToManyField('Genre')
        directors      = models.ManyToManyField('Director')
        actors         = models.ManyToManyField('Actor')
        
        class Meta:
            db_table = 'movies'

    select_related를 사용하지 않는다면 전체 리스트를 조회할 때 만약 5개의 데이터가 있어서 for문을 돌린다면 5번의 쿼리를 날리게 됩니다. 만약 데이터가 1,000개라면..? 1번의 요청에 1,000개의 쿼리를 날리게 될 테고 이는 DB에 부하를 줄 것입니다.

     

    그런데 이때 아래처럼 두 가지를 적절하게 사용한다면 정참조의 테이블에는 select_related로 1번, 역참조의 테이블에는 prefetch_related로 각 1번씩만 쿼리를 날려서 DB 부하를 줄일 수 있습니다.

    그래서 아래의 경우에는 select_related 쿼리 1번, prefetch_related 쿼리 5번이기에 총 6개의 쿼리를 날리게 됩니다.

    all_movie = Movie.objects\
               .select_related('main_image')\
               .prefetch_related('genres', 'directors', 'actors', 'sub_image_set', 'video_set')\
               .all()

     

    Q. 그러면 prefetch_related는 모든 관계에서 사용된다고 하는데 그러면 select_related를 안 쓰고 이것만 사용하면 되는 것 아닌가?

    우선 둘의 가장 큰 차이점은 추가 쿼리의 발생 여부입니다.

    그래서 위에서 설명한 것을 토대로 select_related 와 prefetch_related 를 적절하게 사용해야 합니다.

     

     

    < 마무리 >

    이번에 직접 사용해보고 Query log를 찍어보면서 그 효과를 조금이나마 실감할 수 있었습니다.

    아직 내부 동작에 대해서는 이해가 부족하여 이 부분을 보충해야겠습니다.

     

     

     

     

    < 참고 자료 >

    Django 공식 문서

    https://docs.djangoproject.com/en/4.0/ref/models/querysets/

     

    김성렬 님의 영상인데 자세하고 쉽게 설명해 주셔서 꼭 보시길 바랍니다.

    Django ORM (QuerySet)구조와 원리 그리고 최적화전략 - 김성렬 - PyCon Korea 2020

    https://youtu.be/EZgLfDrUlrk

     

    https://jupiny.tistory.com/entry/selectrelated%EC%99%80-prefetchrelated

     

     

     

    댓글

Designed by Tistory.