[Django/DRF] 비동기 작업

@esthrelar·2023년 10월 10일
0

참고 블로그 :
https://backstreet-programmer.tistory.com/198

거의 블로그 그대로 차근차근 따라했음.

1. 개발 환경 설정

python -m venv .venv
source .venv/bin/activate

# Django 설치
pip install --upgrade pip
pip install django
pip install djangorestframework

# pip install 후에는 pip freeze > requirements.txt로 install한 패키지 정보 저장 및 기록 해두기.

이후 .gitignore 설정과 git init까지 된 상태에서,

# Celery 설치
pip install celery
pip install django-celery-beat
pip install django-celery-results

# Redis python package 설치
pip install redis

2. Django 프로젝트 생성

# 'NaJang' 라는 이름의 django-project 생성
django-admin startproject NaJang .

# 'api' 라는 이름의 django-app 생성
django-admin startapp users
NaJang/
        __init__.py
        settings.py
        urls.py
        ...
users/
    __init__.py
    models.py
    views.py
    tasks.py  # 여기에 tasks.py 파일을 생성합니다.

즉, 이렇게 생긴 프로젝트.

3. Docker 및 redis 설치

Linux에만 깔려있고, mac에 docker가 설치되어있지 않았던 터라,
docker 설치부터 해줌.

3-1)
https://www.docker.com/products/docker-desktop/
여기 들어가서

의 download for Mac - Apple Chip 클릭.
그러면 .dmg 파일이 다운받아지고,
다른 application과 마찬가지로 .dmg 파일 열어서 application에 끌어다 넣으면 됨.
이제 화면에 설치된 것이 보일텐데,
앱 눌러서 들어가서 accept할 거 accept 다 해서 그냥 들어가면 됨.
그러면

이렇게 docker 창이 잘 뜰 것.
(이 화면은 현재 redis container가 구동 중이기 때문에 이렇게 뜸..)

이렇게 되면 docker는 설치가 완료된 것이고,
terminal에서 docker 설치가 잘 되었는지 확인하려면,

docker

만 입력하면

이런 식으로 쭉 뜨고,

docker --version

입력하면,

이렇게 뜸. 이렇게 잘 나오면 docker 설치는 잘 완료된 것.

3-2)
이후, redis 설치는 위에서

pip install redis

로 했기에, 바로 docker를 아래 명령어로 실행시키면 됨.

# redis docker 실행
docker run -d -p 6379:6379 redis

# redis docker 접속
docker exec -it redis /bin/bash

-> 에러 발생
1)

docker: Error response from daemon: driver failed programming external connectivity on endpoint funny_moser (da6..(생략)..132): Bind for 0.0.0.0:6379 failed: port is already allocated.

: 실수로 backend가 아니라 frontend 파일에서 한번 도커를 실행하고 도커에 접속했는데, 이를 인지한 후 실행한 도커에서 exit 으로 나와서 바로 backend 파일에 가서 docker를 다시 실행하려니 발생한 에러.
기존 frontend에서 docker에 접속했으면 exit으로 나오는 것 뿐만 아니라,

docker stop (your_container_name)

으로 정지시켜줘야 함.

2)
backend에 와서 다시 docker run 부터 실행시키고,

docker exec -it redis /bin/bash

을 해주니

Error response from daemon: Container ca9..(생략)..e77fc is not running

발생. 까닭은,

docker run -d -p 6379:6379 redis

이렇게 docker를 실행했다고 하더라도, 생성된 container 이름이 redis가 아님.

docker ps

를 하면

이런 식으로 실행 중인 container 정보가 나오는데,
여기 맨 왼쪽이 container name이고,

docker exec -it <컨테이너 이름> /bin/bash

으로 넣어줘야 접속이 되기 때문에,

# 위 사진의 경우
docker stop quizzical_bassi
docker rename quizzical_bassi redis
docker start redis

로 quizzical_bassi 라는 이름을 redis라는 이름으로 변경해줌.
이러한 renaming은 컨테이너 끄고 진행해야하는 작업. 그래서 stop 해 줌.

근데 이제 frontend에서도 redis라는 이름으로 변경해 줬어서 이름이 겹친다고 생성이 안 된다길래 그냥 docker GUI 사용해서

이렇게 클릭하고 delete 눌러서 현재 있었던 redis라는 이름의 container를 삭제해줬음.

그래서 드디어

docker exec -it redis /bin/bash

로 실행해주니, 아래 shell 창 처럼 또 뭐 입력할 수 있는 터미널 같은게 생김. 그러면 잘 돌아간 것.
불안하다면,

redis-cli info

이런거 입력해서

이런식으로 쭉 나오면 잘 돌아가는 것.

4. NaJang/settings.py, NaJang/init.py 수정

# NaJang/settings.py

...

INSTALLED_APPS = [

    ...
   
    "rest_framework",
    "django_celery_beat",
    "django_celery_results",
    "users", # app 이름
]

...

CELERY_BROKER_URL = 'redis://127.0.0.1:6379'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379'

...

이런 식으로 다 추가해주기.

# NaJang/__init__.py

from .celery import app as celery_app

__all__ = ("celery_app",)

Django 가 시작될 때 app이 구동되도록 하여 @shared_task 데코레이터를 사용할 수 있도록 하는 것이라고 함.

5. NaJang/celery.py, users/tasks.py 추가

# NaJang/celery.py

import os
from celery import Celery
from celery.schedules import crontab

# 이 부분은 'celery' program 구동을 위한 Default Django settings module을 setting합니다.
# 이후 'celery -A NaJang worker -l info' 뭐 이런 식의 명령어를 수행할 텐데, 이때 저 NaJang이 Django 프로젝트의 설정 모듈을 의미함. 이 설정 모듈이 여기서 설정된 것. 
os.environ.setdefault(
    "DJANGO_SETTINGS_MODULE",
    "NaJang.settings",
)

app = Celery('NaJang') # 프로젝트 이름 넣어준 것.

app.config_from_object(
    "django.conf:settings",
    namespace="CELERY",
)

app.autodiscover_tasks()

(자세한 내용은 맨 위에 첨부한 링크 확인)

# users/tasks.py

from typing import List

from celery import shared_task
from django.core.mail import send_mail

# 일단 잘 돌아가는 지 확인하기 위해 간단한 test_task를 돌려봄. 위 첨부한 링크에 그대로 제공된 코드.
@shared_task
def test_task(a: int, b: int):
    print("test Celery task : ", a + b)
    return a + b

@shared_task
def send_verification_email(user_id, verification_url, recipient_email):
    subject = '이메일 확인 링크'
    message = f'이메일 확인을 완료하려면 다음 링크를 클릭하세요: {verification_url}'
    from_email = '~~비밀~~@gmail.com'
    
    send_mail(subject, message, from_email, [recipient_email])

6. 기본 설정은 완료, 이제 API 호출하기

-> 위의 간단한 테스트 먼저 잘 돌아가는 지 확인.

6-1. users/urls.py 설정

# users/urls.py

from django.urls import path
from users import views


urlpatterns = [
	# ...
    path('test', views.Test.as_view()),
]

6-2. users/views.py 설정

# users/views.py

from django.http import HttpRequest
from rest_framework import views
from rest_framework.response import Response

from .tasks import test_task


class Test(views.APIView):
    def get(self, request: HttpRequest):
        test_task.delay(2, 5)
        return Response("Celery Task Running")

=> 2와 5를 더하는 task를 비동기식으로 돌리는 것.

7. Django App, Celery Worker 실행

2개의 터미널을 열고 각각 django app과 celery worker를 구동합니다.

# Django 구동 - terminal 1
python manage.py runserver

# Celery 구동 - terminal 2
celery -A NaJang worker -l info

(settings.py를 건드렸기 때문에 기존에 돌아가고 있던 서버가 있더라도 끄고 다시 시작해주는 것이 좋음. (그래야 하는 듯?) 또, migrate할 것들도 있어서 python manage.py migrate하고 했다.)

이 과정에서 서버는 다시 잘 켜졌으나,
celery 구동할 때 에러 발생.

Usage: celery [OPTIONS] COMMAND [ARGS]...
Try 'celery --help' for help.
Error: Invalid value for '-A' / '--app':
Unable to load celery application.
The module NaJang was not found.

이 에러는, Celery가 설정된 Django 프로젝트의 루트 디렉토리에서 실행되어야 하는데, Celery 애플리케이션을 올바르게 로드할 수 없는 경우에 발생함.

내 프로젝트의 디렉토리 구조를 보면,

2-pro-backend/
	NaJang/
        __init__.py
        settings.py
        urls.py
        ...
	users/
    	__init__.py
    	models.py
    	views.py
    	tasks.py  
        ...

이런 식으로 생겼는데,
원래는 계속 터미널

(venv) (base) esthrelar@ueunjins-MacBook-Air 2-pro-backend % <명령어>

이렇게 2-pro-backend에서 입력하다가,
celery가 설정된 Django 프로젝트의 루트 디렉토리에서 실행되어야 하는데
이전에 뭐 잘못 써서 발생한 에러 고치다가

cd NaJang

해서

(venv) (base) esthrelar@ueunjins-MacBook-Air NaJang % <명령어>

에 들어가 있는 상태에서 celery 구동 명령을 치고 있었어서 위와 같은 에러가 발생했던 것.

그래서

cd ..

해서 한 칸 전으로 (다시 2-pro-backend로) 이동해서
거기서 이제 celery 구동 명령 치니까

뭐 이런식으로 아래 쭉 나옴.

그러면 이제 Postman 들어가서 API 호출해서 확인하면 됨.
일단 간단한 test 작성한 것 확인을 하기 위해,

이렇게 요청을 보내면, 아래처럼 Celery Test Running이라는 응답을 받고

터미널 창에는 이렇게 아까 2와 5를 더한 결과인 7이 이렇게 뜸.

이렇게 하면 비동기식으로 잘 돌아가는 것!


그러면 이와 동일한 방식으로 이제

# users/tasks.py

from typing import List

from celery import shared_task
from django.core.mail import send_mail


@shared_task
def send_verification_email(user_id, verification_url, recipient_email):
    subject = '이메일 확인 링크'
    message = f'이메일 확인을 완료하려면 다음 링크를 클릭하세요: {verification_url}'
    from_email = 'estherwoo01@gmail.com'
    
    send_mail(subject, message, from_email, [recipient_email])

tasks.py에 아까 작성한 이 send_verification_email을 가지고

# users/views.py

from .tasks import send_verification_email

class SignupView(APIView):
    def post(self, request):
        """사용자 정보를 받아 회원가입합니다."""

        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()

            # 이메일 확인 토큰 생성
            token = default_token_generator.make_token(user)
            uid = urlsafe_base64_encode(force_bytes(user.pk))

			# 이메일 전송 코드 작성 및 이메일에 verification_url을 포함하여 보내기
            verification_url = f"http://127.0.0.1:8000/users/verify-email/{uid}/{token}/"

            # 이메일 전송
            # subject = '이메일 확인 링크'
            # message = f'이메일 확인을 완료하려면 다음 링크를 클릭하세요: {verification_url}'
            # from_email = 'estherwoo01@gmail.com'
            # recipient_list = [user.email]

            # send_mail(subject, message, from_email, recipient_list)
            
            # -> 이 주석 부분은 동기식 메일 보내기. 메일이 보내진 후에야 Response가 가므로 회원가입 후 로딩 시간이 너무 길었음.

            send_verification_email.delay(user.id, verification_url, user.email)

            return Response({"message": "회원가입 성공! 이메일을 확인하세요."}, status=status.HTTP_201_CREATED)
        else:
            return Response({"message":serializer.errors}, status=status.HTTP_400_BAD_REQUEST

이렇게 수정해서 API 다시 보내면,
프론트와 연결했을 때 빠르게 다음 페이지로 넘어가고, 비동기식으로 처리되기 때문에 넘어간 이후에 이메일이 보내지는 것을 확인할 수 있었음.

+) 에러

... (엄청 긴 어쩌구)
KeyError: 'users.tasks.send_verification_email'

간단한 테스트 확인한 후, 본격 프로젝트 내용으로 들어가기 위해서 tasks.py를 수정하고 API를 바로 보내보니 이러한 에러가 떴음.
알고보니 작업 등록을 수정한 후에는 Celery 워커를 다시 시작해야 해서 이러한 에러가 발생한 것.
그래서 ctrl + C 로 한 번 나간 후에 다시

celery -A NaJang worker -l info

로 실행해주니, 잘 작동하는 것을 확인할 수 있었음.

+)

결과 보니,
메일 보내는 데에는 약 3-4초 정도 걸리는 것 같음.

+) 현재 gmail로 메일을 보내고 있는데, gmail 말고 naver 메일로 하면 이것보다는 조금 빠르다고 함.

profile
moved to tistory. ( linked w/ the home btn below. )

0개의 댓글