[Django/DRF] 아주 소소한 리팩토링

조오닭·2024년 11월 11일
1

오늘은 아주 소소한 리팩토링을 기록하고자 한다.
응답시간이라도 쿼리 단계에서 조금 줄여보고자 진행한 리팩토링이었다.

1. Problem


문제(?)가 되었던 부분은 유저 프로필 조회였다.
이 api가 FE에서 많이 사용하는 것 중에 하나였는데, 한 번 호출할 때마다 시간이 400ms 정도였기에 같이 호출되는 다른 api들도 밀렸다.
(하지만 배포하는 ec2 인스턴스 성능이 별로 안 좋아서 이 것만으로 해결되는건 아니다^ㅎ^)

유저 프로필 조회 api의 Response 양식은 이와 같다.

{
    "id": int,
    "nickname": str,
    "description": str,
    "avatarURL": str,
    "email": str,
    "social": str
}

나는 지금 DB table 구조를 User, UserProfile, SocialAccount로 구분하고 있다.
User은 기본적인 회원 테이블(언제 로그인했고 uuid값 등등...),
UserProfile은 User을 외래키로 받아 운영하는 프로필(닉네임, 프사, 한줄소개) 테이블,
SocialAccount는 User을 외래키로 받아 운영하는 OAuth 관리(외부 라이브러리 사용) 테이블이다.
따라서 api의 Response를 보면 알겠지만, 하나의 Response안에 세 테이블을 조회해야 한다.

2. Before


리팩토링 전, 일부 코드는 다음과 같다.

    # view
    def get(self, request, *args, **kwargs):
        user = request.user
        profile_obj = UserProfile.objects.prefetch_related("user").get(user=user)
        serializer = ProfileSerializer(profile_obj, context={"request": request})
        return Response(serializer.data)
    
    # serializer
    def get_social(self, obj):
        return SocialAccount.objects.get(user=obj.user).provider

UserProfile 기준으로 User을 역참조(prefetch_related)로 불러오고, serializer 내부에서 SocialAccount를 조회했다.
이에 대한 SQL문을 보니 간단하게 구성되어있지만,
1:1로 매칭되는 User과 UserProfile을 역참조하니 User 테이블이 한 번 더 조회되어 비효율적이었다.
그리고 SocialAccount도 사용하는 field는 provider 하나인데, 전부 다 불러오는 게 너무 아까웠다.

3. After


따라서 UserProfile에서 User을 불러오는 방식을 select_related(참조)로 바꾸고,
SocialAccount에서의 조회도 provider 한 개만 불러오도록 했다.

    # view
    def get(self, request, *args, **kwargs):
        user = request.user
        profile_obj = UserProfile.objects.select_related("user").only(
            "user__id",
            "user__email",
            "nickname",
            "profile_pic",
            "description"
        ).get(user=user)
        serializer = ProfileSerializer(profile_obj, context={"request": request})
        return Response(serializer.data)
    
    # serializer
    def get_social(self, obj):
        return SocialAccount.objects.values_list('provider', flat=True).get(user=obj.user)

두 번째 SQL문에서 알 수 있듯이 UserProfile을 조회할 때 User테이블을 inner join하여 실행시간을 줄였다. [0.007+0.006 => 0.007]
하지만 SocialAccount시 provider만 조회하는 부분(세 번째 SQL)은 데이터가 크지 않아서인지, 줄어들기는 했지만 큰 효과가 없었다^^ [0.007 => 0.006]

profile
백엔드 응애

0개의 댓글

관련 채용 정보