ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL.72 select_related 와 prefetch_related
    TIL 2020. 12. 19. 23:37
    728x90

    이전 JsonResponse를 활용하여 모든 데이터를 응답으로 보내보았다.
    codermun-log.tistory.com/category/TIL

     

    'TIL' 카테고리의 글 목록

    성실과 기본기를 중요시하는 백엔드 개발자입니다.

    codermun-log.tistory.com

    하지만 언급하였듯 위와 같은 코드는 전혀 개발자스럽지 못한 코드이다.

    Django 의 ORM을 이용하여 DB에 접근하며 내가 원하는 정보를 가져와야한다.

    이때 로직을 어떻게 구성하느냐에 따라 DB에 몇 번 접근해야하는지에 대한 효율을 따져볼 수 있게 된다.

    당연히, DB에 자주 접근하게 될 수록 속도는 느려지며 비효율적인 코드라 할 수 있다.

    또한, Django ORM을 이용해 DB에 접근하여 Queryset objecst를 가져올때 얼마나 많은 Query를 가져오는지에 따라

    개발자 역량이 달라진다 해도 과언이 아니다.

     

    all(), filter(), get()... 등으로 DB에 접근하여 정보를 가져 올 경우 불필요한 query를 가져오는 경우가 생긴다.

    로직을 구현하는데 사용하지 않는 불필요한 query까지 한꺼번에 가져오는 문제가있다.

    실제로 위와 같은 문제로 인해(DB 저장된 정보의 양에따라)

    사내 서버가 다운되기도 한다고 한다니 Query를 줄이려는 생각을 항상 가지고 개발에 임해야 한다.

     


    Queryset 이란

    이때 이전까지 무수히 사용하였던 QuerysetAPI의 핵심 개념인 Querset이 등장한다.

    Query DB에 정보를 요청해주는 것을 의미하며 파이썬으로 작성한 코드가 SQL 로 매핑되어 

    Queryset 이라는 자료 형태로 값으로 원하는 정보를 가져오게 된다.

    이는 순회가능한 데이터로서 이를 이용하여 1개 이상의 데이터를 불러와 사용할 수 있다.


    내가 구성한 모델간의 관계를 기반으로 Select related 와 Prefetch related를 함께 사용한다면

    획기적으로 Query를 줄여 굉장히 효율적인 로직을 구성할 수 있다고 한다

    얼마나 효율이 좋게 변하는지를 확인하기 위해서는 아래와 같은 방법들을 사용할 수 있다.

     

    1.django-debug-toolbar

    2. Logging 모듈

    실제로는 toolbar가 훨씬 눈에 띄는 효과를 볼 수 있다고 한다.

     

    첫 번째 클론 프로젝트에서는 작성한 로그를 보기위해서는

    여기서는 Logging 을 사용하기 위해 Import가 아닌 settings.py 설정값으로 지정해주었다.

     

    아래 코드 말고도 구글에 많은 Logging에 대한 설정 정보가 있으니 참고하여도 좋다.

    이제 Select related 와 Prefetch related를 활용해 얼마만큼의 쿼리가 줄어드는지 체감할 수 있다.

    대부분 많은 사람이 느낄것 같지만 django-debug-toolbar를 사용하는것이 훨씬 가독성이 높다고 생각된다.

    2차 프로젝트에서는 Logging을 사용하지 않고 django-debug-toolbar 를 사용하도록 하자.

    LOGGING = {
        'disable_existing_loggers': False,
        'version': 1,
        'handlers': {
            'console': {
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'level': 'DEBUG',
                'propagate': False,
            },
        },
    }

    docs.djangoproject.com/en/3.1/topics/logging/

     

    Logging | Django documentation | Django

    Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

    docs.djangoproject.com


    여기서 select related / pefetch related를 정확히 집어보자.

    프로젝트 진행간 아래의 내용으로 착각을 했었다

    Select = 정참조로 속성값을 가져올때 사용하는것으로 착각

    Prefetch = 역참조로 속성값을 가져올때 사용하는것으로 착각

     

    절대로 정참조/역참조 selectprefetch로 속성값을 가져올 수 있는게 아니라는 사실을 알아야한다.

    직접적인 모델 간의 참조관계에 접근하는 개념이 아닌 DB와 연결되어있는 개념임을 기억해야한다.

    다시 한번 말하지만 절대 정/역 참조로 접근할 수 있게 도와주는 개념이 아니다

    select_related와 prefetch_related 는 불필요한 쿼리를 줄이기 위함이지

    정참조, 역참조 관계에서 가져올 수 없는 값을 가져오게 해주는 메소드는 아니다

    Django ORM은 python code <-> Sql code로 자동 변환되는 편리한 기능이 있음을 알 고 있다.

    이때 all, filter, get, first.. 등 이외에 Select related, Prefetch related를 사용하면

    Sql의 기능 중 하나인 join 기능을 활용 할 수 있다

    따라서 Selcet / Prefetch 는 Sql 의 join 기능을 수행할 수 있게 해주는 QuerySet 이라 볼 수 있다.


    Select_related

    OneToOne 관계 / OneToMany 관계에서 M(Many)이 사용할 수 있다.

    또한 Sql 기능 중 하나인 join기능을 활용할 수 있게 도와주는 QuerySet이라 할 수 있는데

    Sql Query 문의 Join 과 Foreign_key(OTO, OTM) 사용하여 정참조 할 시 DB에 접근하여 QuerySet을 가져올때 미리 related  objects 까지 불러오는 메서드이다.

    비록 Query가 복잡해지지만 한번 DB에 접근하여 불러온 Data는 database 서버가 종료되기 전까지 Cache에 남아 있어

    매 Query 마라 DB에 접근하지 않아도 된다

    즉, Select_related를 사용함으로서 DB 접근 빈도를 줄여 자원 낭비를 줄일 수 있다. (불필요한 접근 및 부하) 

     

    Perfetch_related

    Select_related 의 범위에서 더 나아가 ManyToMany  등 대부분 모든 관계에서 사용 할 수 있다.

    OneToMany 관계에서 O(One)이 사용 할 수 있다.

    Select_related와 마찬가지로 related objects를 함께 불러오는 메서드이다. 동일하게 Cache에 남아있게 하여 DB에 불필요한 접근을 줄여준다.

     

     

    Select_related 와 Prefetch_related 차이

    정참조, 역참조의 차이도 있지만 실제 Join 을 어디서 하냐의 차이가 있다. 

    select_related 같은경우 DB에서 Join 을 한 채로 가져오고, 

    prefetch 는 가져와서 Python 으로 Join 을 하게 된다. 

     

    정말 간단하게는 Select는 DB에서 join 기능을 수행한 후 가져오므로 1 Query가 실행된다면

    Prefetch는 python에서 join 기능을 수행하므로 불러올때 1 Query를 실행하고 불러온 후 1 Query를 한번더 실행한다.

    즉, 가급적 Select_related를 사용하는것이 효율적이라 할 수 있겠다.


    개념에 대해 착각하게 된 이유내가 원하는 정보를 정/역 참조의 관계에서 가져오려 할때였다.

     

    내가 원하는 Column이 어떠한 기준이 되는 Model에 없는 값일 경우

    다른 ModelColumn 값을 가져와 접근해야하는 경우가 많다.

     

    아래와 같은 상황으로 예를 들어보자

    현재 review 는 product 을 Foreinkey로  참조하고 있는 상황이다.

    # product/models
    class Product(model.Model):
    	name        = models.CharField(max_length=50)
        price       = models.DecimalField(max_digits=10, decimal_places=2)
        created_at  = models.DateTimeField(auto_now_add=True)
        updated_at  = models.DateTimeField(auto_now=True)
        category    = models.ForeignKey(Category, on_delete=models.CASCADE)
        likes       = models.ManyToManyField(User, through="Wishlist")
        is_in_stock = models.BooleanField(default=True)
        discount    = models.ForeignKey(Discount, on_delete=models.CASCADE, null=True)
    
        class Meta:
            db_table = "products"
    #review/models
    
    class Review(models.Model):
        rate                = models.FloatField(validators=[MinValueValidator(0.5),MaxValueValidator(5.0)])
        contents            = models.TextField()
        user                = models.ForeignKey(User, on_delete=models.CASCADE)
        product             = models.ForeignKey(Product, on_delete=models.CASCADE)
        created_at          = models.DateTimeField(auto_now_add=True)
        updated_at          = models.DateTimeField(auto_now=True)
        is_monthly_reviewed = models.BooleanField(default=True)
    
        class Meta:
            db_table ="reviews"

     

    현재 product table column 값으로는 review에 대한 어떠한 내용도 들어가 있지 않다.

    하지만 product로 접근해서 review table column에 접근해야하는 경우가 반드시! 생길 것이다.

     

    Model에서 related_name 를 지정하여 지정된 related_name 값으로 다른 모델의 정보에 접근하거나, (OR)

    아래와 같이 _set 를 이용할 수 있다. (단, 역참조가 관계가 이미 성립되어있어야한다.)

    즉, product에 대한 Queryset object 를 불러와서 reivew table column값에 접근할 수 있다

    (Select / Prefetch 를 쓰지 않고 정보를 불러왔음을 기억하자)

    product = Product.objects.get(id=3)
    
    reveiew_contents = product.review_set.get().contents
    
    print(review_contents)
    # 제품이 정 말 좋 아 요!

     

     


    결국 Seclt_related와 Prefetch_related를 적절히 사용하여

    불필요한 Query를 줄이고 속도를 높혀 효율적인 로직을 구성해야한다.

     

    이는 DB의 데이터 양에 따라 서버의 부하와 속도저하를 일으키는

    가장 대표적인 이유 중 하나이기 때문에 항상 고려하여 로직을 구성하도록 하자.

     

    # 나의 기술 블로그 모두에 해당하는 내용이지만 꼭 잘못된 정보가 있다면 지적해주시면 

    서로의 발전에 큰 도움이 될것이라 믿습니다. 감사합니다

     

     

    출처 :

    파이썬(Django) :: Select_related 와 Prefetch_related (tistory.com)

    [Django] 데이터베이스 조회, queryset (tistory.com)

    728x90

    'TIL' 카테고리의 다른 글

    TIL.75 RSETfulAPI  (0) 2020.12.22
    TIL.73 Github / Git 충돌(Conflict) 발생 해결  (0) 2020.12.20
    TIL.71 JsonResponse 활용  (0) 2020.12.18
    TIL.67 Trello 다루기  (0) 2020.12.14
    TIL.66 스크럼(Scrum)  (0) 2020.12.13
Designed by Tistory.