ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django] Django 연습장 - 7. 이메일 전송 비동기 방식 (celery + redis)
    프로그래밍/Django 2022. 4. 17. 02:15
    반응형

    지난번 회원 가입 시 이메일 인증하는 프로세스를 추가했습니다.

     

    그렇지만 문제가 있습니다.

     

    이메일을 전송하는 함수는 다소 시간이 소요됩니다.

    만약 이메일에 무언가를 첨부한다면 시간은 더욱 늘어납니다.

     

    그러나 django는 Task들을 순서대로 처리합니다.

    이 말은 앞에 일이 늦게 끝나면 뒤에 일 들도 처리가 늦어집니다.

     

    이를 방지하기 위해 이메일 전송 및 시간이 오래 걸리는 작업들은 비동기 방식으로 처리합니다.

     

    예를 들어 Event A, Event B 순서로 요청이 들어왔을 때 A->B 순서로 이루어지게 된다.

    A의 처리가 늦어질수록 B의 처리에 영향이 간다.

     

    그러나 비동기 방식으로 프로세스를 개선한다면 A의 처리완료 여부와 상관없이 B의 작업이 일어난다.

     

    이를 위해서 사용하는 것이 Celery이다.

     

    [작업내용]

    - celery 설치 & 세팅

    - celery.py

    - 코드 변경 (task로 실행)

     

    1. celery 설치 & 세팅 + redis (redis 모듈 설치를 해도 redis는 서버 따로 띄워줘야 함)

    pip install celery
    pip install redis

    settings.py

    #celery
    CELERY_BROKER_URL = 'localhost:6379'
    CELERY_RESULT_BACKEND = 'localhost:6379'
    CELERY_ACCEPT_CONTENT = ['application/json']
    CELERY_RESULT_SERIALIZER = 'json'
    CELERY_TASK_SERIALIZER = 'json'
    CELERY_TIMEZONE = TIME_ZONE

    * redis는 docker로 띄웠음. docker-compose.yml 참고

    version: '3.7' 
    services: 
      redis: 
        container_name: redis
        image: redis:latest 
        ports: 
          - 6379:6379 
        restart: always 
        volumes: 
          - C:\<로컬 경로>:/data

     

    2. config > celery.py

    from __future__ import absolute_import, unicode_literals
    
    import os
    from celery import Celery
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') # FIXME 추후 prod 변경 할것.
    
    app = Celery('config')
    
    
    app.config_from_object('django.conf:settings', namespace='CELERY')
    app.autodiscover_tasks()
    
    
    @app.task(bind=True)
    def debug_task(self):
        print(f'Request: {self.request!r}')
    

     

    3. 기존 코드 변경

    app_accounts > task.py

    from celery import shared_task
    
    #데코레이터추가 & retry
    @shared_task
    def task_send_register_mail(user, domain):
        try:
    
            template = 'mail_register.html'
            now_time = time.mktime(datetime.now().timetuple())
    
            kwargs = {
                'uid': urlsafe_base64_encode(force_bytes(user.id)),
                'token': activate_token.make_token(user),
                'time': int(now_time)
            }
            message = render_to_string(template, {
                'user': user,
                'domain': domain,
                'kwargs' : kwargs,
            })
            mail_subject = '회원가입 인증메일'
            to_email = [user.email]
            from_email = DEFAULT_FROM_EMAIL
            body = ''
            email = EmailMultiAlternatives(mail_subject, body, from_email, to_email)
            email.attach_alternative(message, "text/html")
            email.send()
    
        except User.DoesNotExist as e:
            task_send_register_mail.retry(countdown=1)

     

     

    # 회원가입
    class CreateAccounts(FormView):
        template_name = 'register.html'
        form_class = UserForm
        success_url = '/accounts/signin'
    
        def form_valid(self, form):
            password = make_password(form.data.get('password'))
            user = User(
                email=form.data.get('email'),
                password=password,
                nickname=form.data.get('nickname'),
            )
            user.is_active = False
            user.save()
    
            #회원 가입 이메일 전송송
            task_send_register_mail.delay(user, get_current_site(self.request).__str__())
    
            return super().form_valid(form)

    4. celery 실행 & 테스트

    [실행]

    celery -A config worker --pool=solo -l info

    성공하면 이런 화면이 나옴

     

    이제 회원가입을 해보겠음.

    문제가 발생했음

     

    기존에 user로 넘겨주던 객체가 celery로 변경하고 task.py를 나중에 실행하려니까 serializable하지 않은 데이터 형식이라 에러가 나타남

    그래서 그냥 user_id를 넘겨주는 방식으로 코드 변경

    app_accounts > views.py

    user -> user.pk로 변경

    class CreateAccounts(FormView):
        template_name = 'register.html'
        form_class = UserForm
        success_url = '/accounts/signin'
    
        def form_valid(self, form):
            password = make_password(form.data.get('password'))
            user = User(
                email=form.data.get('email'),
                password=password,
                nickname=form.data.get('nickname'),
            )
            user.is_active = False
            user.save()
    
            #회원 가입 이메일 전송송
            task_send_register_mail.delay(user.pk, get_current_site(self.request).__str__())
    
            return super().form_valid(form)

    redis 연결에서 문제가 있나 봄..

    kombu.exceptions.OperationalError: [WinError 10061] 에러가 계속 발생함..

    -> 한참을 리서치하다 보니 예전에도 같은 문제를 겪었었고

    config 폴더의 __init__. py에 

    from __future__ import absolute_import, unicode_literals
    
    from .celery import app as celery_app
    
    __all__ = ('celery_app',)

    해당 코드를 넣어주니 해결됐습니다.

    django에서 celery를 인식하기 위해서 해당 코드를 입력해야 하고

    이후 회원가입을 시도하니..

    celery작업이 동작한 것을 확인할 수 있었습니다.

     

    너무 예전 일이라서 까먹고 오래 삽질했지만 다시 해결할 수 있어서 다행입니다..!

    반응형
Designed by Tistory.