Django 모델 개편, Custom Migration 만들기

noname2048·2021년 4월 10일
0

다음 글은 스택오버플로우 를 참고하여 작성되었습니다.

문제사항확인

어느 한적한 주말, 장고에서 게시판을 만들다가, 생각보다 게시판 로딩이 너무 오래 걸린 다는 사실을 깨달았습니다.

debug-django-toolbar 를 통해 본 SQL 관련 사항은깨 정말 복잡하기 그지 없었는데요. 자세히 살펴보니, POST 하나를 불러 표시하는데 서브쿼리가 굉장히 많이 달려있다는 것을 발견합니다.

하아... 원인은 fk (foreign key)로 설정해둔 user account 에서 유저 정보를 불러오느라 조금, fk로 설정해둔 카테고리에서 또 카테고리 이름 불러오느라 조금씩 서브쿼리를 사용하고 있던 것이였습니다.

물론, 이 방법이 다른 로직 없이도 즉시 변경되는 정보를 반영해서 표시하기에 가장 간단한 해결책인 것은 맞지만, 이대로라면 서버에 많은 부담이 갈 것이라고 생각하게 되었습니다. 그래서 모델을 뜯어 고치기로 마음먹었습니다. 반정규화를 적용해서, auther의 username과 category의 name 정도는 title 모델에서 가지고 있다 보여주고, 변경이 생기면 그때로직으로 처리하는 것으로 하려고 합니다.

redis도 이러한 반정규화랑 함께라면 좀 더 쉽고, 잘 사용수 있을거라(?) 믿으며 열심히 모델을 고쳤습니다. 열심히 고쳤는데...

SystemCheckError: System check identified some issues:

ERRORS:
<class 'forum.admin.ForumPostAdmin'>: (admin.E108) The value of 'list_display[3]' refers to 'author', which is not a callable, an attribute of 'ForumPostAdmin', or an attribute or method on 'forum.ForumPost'.

Custom Migration 하기

아무래도 모델에 고칠 사항이 너무 많다보니, Django가 자동으로 감지하지 못하는 것 같습니다. 이러면 어떻하기 고민하던 찰나, 이 게시글의 계기가 된 스택오버플로우 글을 발견합니다. 핵심적인 내용은 --empty 태그를 붙이면 빈 migrations 를 만들어서 사용자가 내용을 수정하거나 custom 할 수 있다는 내용입니다.

저는 docker 에서 진행하고 있기 때문에 따로 exec 명령어를 통해 접속한뒤, 다음과 같은 명령어를 실행했습니다. python manage.py makemigrations --name post_renew forum --empty

물론 Django 공식문서 에도 #data-migrations 항목에 나와있습니다.

수정사항 기록하기

# Generated by Django 3.1.7 on 2021-04-09 23:57

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('forum', '0004_auto_20210404_2044'),
    ]

    operations = [
        migrations.RenameField(
            model_name='forumpost',
            old_name='author',
            new_name='user',
        ),
        migrations.RenameField(
            model_name='forumpost',
            old_name='ip',
            new_name='last_ip',
        ),
    ]

데이터베이스 추가 항목과 수정항목을 모두 기술해도 좋겠지만, 추가는 기존에 추가만 하고 makemigrations 과정을 거치는게 쉬울 것이라 생각해서, custom 에는 수정항목만 먼저 기술 한뒤에, 추가를 진행하기로 하였습니다.

추가사항 기록하기

그리고 코멘트를 해제한 뒤, 마이그레이션을 다시 만들었습니다.
(blank가 많아 보이는 이유는 admin 관리에서 추가하지 않을 영역에 대해서 blank를 넣었는데, 원래는 이렇게 하면 안되고, 따로 form을 등록하거나 custom이 들어가야합니다.)

# Generated by Django 3.1.7 on 2021-04-10 01:18

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('forum', '0005_post_renew'),
    ]

    operations = [
        migrations.AddField(
            model_name='forumpost',
            name='cache_comments_count',
            field=models.PositiveIntegerField(default=0),
        ),
        migrations.AddField(
            model_name='forumpost',
            name='cache_likes_count',
            field=models.PositiveIntegerField(default=0),
        ),
        migrations.AddField(
            model_name='forumpost',
            name='cache_views_count',
            field=models.PositiveBigIntegerField(default=0),
        ),
        migrations.AddField(
            model_name='forumpost',
            name='category_name',
            field=models.CharField(blank=True, max_length=30),
        ),
        migrations.AddField(
            model_name='forumpost',
            name='user_name',
            field=models.CharField(blank=True, max_length=30),
        ),
    ]

결과확인하기

이제, Template과 View를 수정해서, 지금 추가한 정보들을 이용하게 바꿔봤습니다. 그랬더니, 검색에 걸린 시간이 2.90ms로 줄어들었습니다. 본래는 여러번 시도해서 평균을 내서 계산을 해야하지만, 간단하게 계산해보면. 41.50ms 에서 2.90ms 로 줄었으니, 무려 20배가 걸리던 시간을 줄일 수 있었습니다.

정보갱신하기

그런데, 모델을 만들기만 하고, 지금은 아무 정보도 담고 있지 않기 때문에, 이 각각의 cache에 실제하는 정보를 넣어야 하겠습니다.

from django.core.management import BaseCommand, CommandParser
from django.conf import settings

from django.contrib.auth import get_user_model
from forum.models import ForumPost


class Command(BaseCommand):
    help = "renew cache in forumpost model"

    def handle(self, *args, **options):
        posts = ForumPost.objects.all()
        for post in posts:

            post.cache_comments_count = post.forumcomment_set.count()
            if post.category:
                post.category_name = post.category.name
            else:
                post.category_name = "없음"
            post.user_name = post.user.username
            post.cache_likes_count = post.forumlike_set.count()
            post.cache_views_count = post.forumposthitcount_set.count()

            post.save()

그리고 새로고침을 하면... 짜잔!

정상적으로 작동하는 것을 확인했습니다!

몇가지더

이제 Post를 등록하거나 수정할 때, 혹은 조회수가 1올라갈 때마다 cache를 수정하는 일이남았지만.. 관련 로직은 많고 복잡하니 서비스에 맞게 업데이트 하는 것으로 맡기겠습니다. :)

profile
설명을 쉽게 잘하는 개발자를 꿈꾸는 웹 개발 주니어

0개의 댓글