ORM 최적화를 이용한 데이터 로딩시간 줄이기

Choi Rim·2021년 8월 23일
0

Django

목록 보기
19/21
post-thumbnail

프로젝트 진행 중 상세 페이지의 데이터와 댓글 데이터의 로딩 시간이 느려도 너~무 느려서 (내가 사용자였으면 나갔을만한 로딩 시간..) ORM 최적화를 진행하기로 했다.


LOGGING = {
    'disable_existing_loggers': False,
    'version' : 1,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': False
        }
    },
}
  • 위 코드를 settings.py 파일에 추가한다.
import functools, time
from django.db import connection, reset_queries
from django.conf import settings

def query_debugger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        reset_queries()
        number_of_start_queries = len(connection.queries)
        start  = time.perf_counter()
        result = func(*args, **kwargs)
        end    = time.perf_counter()
        number_of_end_queries = len(connection.queries)
        print(f"------------------------------------------------------")
        print(f"Function : {func.__name__}")
        print(f"Number of Queries : {number_of_end_queries-number_of_start_queries}")
        print(f"Finished in : {(end - start):.2f}s")
        print(f"------------------------------------------------------")
        return result
    return wrapper
  • decorators.py 파일에 위 코드를 작성한다.
------------------------------------------------------
Function : get
Number of Queries : 103
Finished in : 23.45s
------------------------------------------------------
  • 위 작업을 마치고 원하는 view의 class를 실행하니 무려 103개의 쿼리와 23초의 시간이 걸렸다..
    @query_debugger
    def get(self, request, project_id):
        comments = Comment.objects.filter(project_id=project_id)

        comments._result_cache

        comment_list = [{   
                'user_id'           : comment.user_id,
                'nickname'          : comment.user.nickname,
                'description'       : comment.description,
                } for comment in comments
            ]
        return JsonResponse({'comments':comment_list}, status=200)

N+1 problems
comment에서 user Instance에 접근하는 과정에서 많은 쿼리가 발생하였다.

(0.186) SELECT `projects`.`id`, `projects`.`created_at`, `projects`.`updated_at`, `projects`.`name`, `projects`.`user_id`, `projects`.`aim_amount`, `projects`.`description`, `projects`.`end_date`, `projects`.`category_id`, `projects`.`main_image_url`, `projects`.`isvisible`, `projects`.`isdeleted` FROM `projects` WHERE `projects`.`id` = 4 LIMIT 21; args=(4,)
(0.189) SELECT `users`.`id`, `users`.`email`, `users`.`nickname`, `users`.`password`, `users`.`kakao`, `users`.`google`, `users`.`auth_level`, `users`.`isdeleted` FROM `users` WHERE `users`.`id` = 3 LIMIT 21; args=(3,)
  • 모든 projects를 조회하는 SQL이 한번 발생하고 projects의 user를 매번 조회하는 SQL이 N번 발생한다.
  • 실제로 저 뒤에 다수의 user를 조회하는 SQL이 실행되었다...
    @query_debugger
    def get(self, request, project_id):
        comments = Comment.objects.filter(project_id=project_id).select_related('user')

        comments._result_cache

        comment_list = [{   
                'user_id'           : comment.user_id,
                'nickname'          : comment.user.nickname,
                'description'       : comment.description,
                } for comment in comments
            ]
        return JsonResponse({'comments':comment_list}, status=200)
  • Comment.objects.filter(project_id=project_id).select_related('user')
    • select_related method를 통해 project가 정참조하고 있는 user Instance를 불러왔다.
(0.208) SELECT `comments`.`id`, `comments`.`user_id`, `comments`.`project_id`, `comments`.`description`, `users`.`id`, `users`.`email`, `users`.`nickname`, `users`.`password`, `users`.`kakao`, `users`.`google`, `users`.`auth_level`, `users`.`isdeleted` FROM `comments` INNER JOIN `users` ON (`comments`.`user_id` = `users`.`id`) WHERE `comments`.`project_id` = 4; args=(4,)
------------------------------------------------------
Function : get
Number of Queries : 3
Finished in : 2.39s
------------------------------------------------------
  • selected_related를 쓰기전에는 103개의 쿼리가 발생했었는데 selected_related를 쓰고 난 후 무려 3개!!! 의 쿼리로 줄어든 것을 확인할 수 있다.
    너무 신기하다👍
  • selected_related를 쓰기 전에는 project에 해당하는 user를 매번 불러왔었는데, selected_related를 사용하면 join을 사용하여 user를 한번에 가져온다.
    • 이것을 eager loading (이거 로딩, 즉시 로딩)이라고 한다.
    • 정참조 케이스는 selected_related를 사용하면 된다.
    • 역참조 케이스는 prefetch_related를 사용하면 된다.
profile
https://rimi0108.github.io/

0개의 댓글