ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 7월 7일 TIL - 캐시 사용하기
    TIL(Today I Learned) 2023. 7. 7. 17:53

    ▷ 오늘의 배움

    💬 캐시란?

        데이터나 계산 결과 등을 미리 저장해 두어서, 이후에 동일한 작업을 할 때 더 빠르게 처리할 수 있도록 해 준다. 

     

    📖 캐싱을 구현하는 방법들

    1. The per-site cache (사이트별 캐시)

    # update 미들웨어는 리스트의 첫번째에 있어야 한다. fetch 미들웨어는 마지막에 있어야 한다.
    MIDDLEWARE = [
    	...
        'django.middleware.cache.UpdateCacheMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.cache.FetchFromCacheMiddleware',
        ...
    ]

    2.The per-view cache (뷰별 캐시) - view에서 리턴하는 데이터를 캐시

    from django.views.decorators.cache import cache_page
    
    # 데코레이터 활용
    @cache_page(60 * 15)
    def my_view(request):
        ...
        
    # URLconf에서 활용
    urlpatterns = [
        path("foo/<int:code>/", cache_page(60 * 15)(my_view)),
    ]

    3.Template fragment caching(캐시 템플릿 태그)

    {% load cache %}
    {% cache 500 sidebar %}
        .. sidebar ..
    {% endcache %}
    
    # 템플릿 내에선 반드시 {% block %}과 {% endblock %} 사이에서 사용해야 한다.

     

    ▷ 오늘의 시도

    ⚠️ 메인페이지에 띄워주는 데이터가 새로고침 몇번하면 바뀌는 문제 발생. 스케줄러로 데이터 만드는 함수를 따로 설정해놨지만 몇번만에 바뀌어 버린다. 튜터님께 조언 받아 캐시를 설정하여 해결하고자 한다.

     

    처음에 하나의 뷰를 캐시하려고 view에 데코레이터를 쓰는 방식으로 가고자 하였다.

    from django.views.decorators.cache import cache_page
    
    @cache_page(60 * 15)
    def my_view(request):

    캐시를 사용할 view가 ListAPIView였는데 method가 없다는 오류가 발생하였다. 데코레이터 위치를 바꾸니 request인자가 없다고 그러고,, 찾아보니 method_decorator를 사용해서 캐시 데코레이터를 사용할 수 있었다.

    class ReleasedGamesListView(generics.ListAPIView):
        permission_classes = [permissions.AllowAny]
        serializer_class = GameSerializer
        pagination_class = GamesPagination
    
        @method_decorator(cache_page(45 * 60))
        def get(self, *args, **kwargs):
            return super().get(*args, **kwargs)
    
        def get_queryset(self):
            # omitted

    하지만 난 간편하게 URLconf에서 사용하겠다.

        path("article-random/", cache_page( 60 * 15)(views.ArticleRandomView.as_view()), name="article_random"),

    이제 새로운 문제점이 캐시를 매일 11시 59분 59초에 실행되도록 하는것. 그리고 뷰 내에서 캐시 예외를 두는 것! -> 메인 페이지엔 최신리스트를 불러오는 것도 있는데 그 코드는 최신 게시물이 업데이트 될때마다 바뀌어야 하기 때문이다.

     

    1. 첫번째 문제점 캐시를 매일 11시 59분 59초에 실행시키자!

    -> 캐시는 일반적으로 일정시간 후에 만료 된다. 매일 몇분 몇초를 설정해줄 수가 없다. 그렇기에 캐시 timeout을 None으로 설정하고 캐시를 갱신하는 함수를 스케줄러를 통해 매일 11시 59분 59초에 실행시키자!

    def update_cache_task():
        cache.clear()
        
    scheduler.add_job(update_cache_task, "cron", hour=0, id="update_cache")

     

    2. 두번째 문제 뷰 내에서 캐시 예외 두기!

    현재 cache_page를 설정한 view는 아래와 같은 형식이다. 여기서 예외로 두고 싶은게 최신 게시물 반환하는 부분이다. 새로운 게시물이 작성 될 때마다 캐시가 업데이트 되게 설정해주고 싶다. 하지만 이렇게 저렇게 만져본 결과 전체 뷰를 캐시 설정 해놔서 안쪽의 부분이 바뀌지가 않았다..

    class ArticleRandomView(generics.ListAPIView):
        """
        여러개의 게시물을 무작위로 반환합니다.
        태그를 무작위로 하나 선택하여 그 태그가 달린 게시물들을 반환합니다.
    
        input: 쿼리=option
        ouput: 무작위 게시물들
    
        """
    
        serializer_class = ArticleSerializer
    
        def get_queryset(self):
            # 게시물 임의 선택
            if self.request.query_params.get("option") == "article":
                # 랜덤 아티클 반환
            # 임의의 태그 선택
            elif self.request.query_params.get("option") == "tag":
                # 오늘의 태그를 포함한 아티클 반환
            # 최신 게시물 5개
            if self.request.query_params.get("option") == "update":
                # 최신 게시물 5개 반환
            
            return Response({"message": "다시 시도해주세요"},status=status.HTTP_404_NOT_FOUND,)

    그래서 고안한것이 각각 캐시 설정을 해주는 것이다. 최신 게시물같은 경우는 게시글 작성 view에 캐시 삭제 코드를 넣어놓고 메인을 로드할 때 다시 캐시를 저장하는 방식으로 설정해주었다.

    class ArticleRandomView(generics.ListAPIView):
        """
        여러개의 게시물을 무작위로 반환합니다.
        태그를 무작위로 하나 선택하여 그 태그가 달린 게시물들을 반환합니다.
    
        input: 쿼리=option
        ouput: 무작위 게시물들
    
        """
    
        serializer_class = ArticleSerializer
    
        def get_queryset(self):
            # 게시물 임의 선택
            if self.request.query_params.get("option") == "article":
                articles = cache.get('random_article')
                if not articles:
                    try:
                        articles = Article.objects.filter(db_status=1).order_by("?")[:5]
                    except:
                        articles = Article.objects.filter(db_status=1)
                    cache.set('random_article', articles)
                return articles
            # 임의의 태그 선택
            elif self.request.query_params.get("option") == "tag":
                today_tag = cache.get('today_tag')
                if not today_tag:
                    weekly_tags = WeeklyTags.objects.all()
                    if weekly_tags.count() == 0:
                        get_weekly_tags()
                    tag = weekly_tags[0]
                    today_tag = tag.tag.article_set.filter(db_status=1).order_by("-created_at")
                    if today_tag.count() == 0:
                        get_weekly_tags()
                        weekly_tags = WeeklyTags.objects.all()
                        tag = weekly_tags[0]
                        today_tag = tag.tag.article_set.filter(db_status=1).order_by(
                            "-created_at"
                        )
                        cache.set('today_tag', today_tag)
                return today_tag
            # 최신 게시물 5개
            if self.request.query_params.get("option") == "update":
                update_article = cache.get('update_article')
                if update_article is None:
                    update_article = Article.objects.filter(db_status=1).order_by("-created_at")[0:5]
                    cache.set('update_article', update_article)
                return update_article
            return Response({"message": "다시 시도해주세요"},status=status.HTTP_404_NOT_FOUND,)

    댓글

Designed by Tistory.