[Django] - Celery로 비동기 촥촥~

오동훈·2023년 1월 3일
1

Django

목록 보기
16/23
post-thumbnail
post-custom-banner

1. 동기 vs 비동기

우선 celery는 비동기 작업을 하기 위해 사용하고 있습니다. 이런 비동기 작업이 왜 필요한지 살펴보면 다음과 같습니다.

1. 동기

동기는 말 그대로 동시에 일어난다는 뜻입니다. 요청과 그 결과가 동시에 일어난다는 약속인데, 바로 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 나타나야 합니다.

  • 요청한 사항에 대해 결과를 즉시 주어야 함
  • 노드 사이의 작업 처리 단위(transaction)를 동시에 맞춰야함

2. 비동기

비동기는 동시에 일어나지 않는다는 의미입니다. 요청한 결과는 동시에 일어나지 않을거라는 말입니다.

  • 요청한 사항에 대해 결과를 즉시 주지 않음
  • 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 됨

3. 장단점

동기 방식은 설계가 매우 간단하고 직관적이지만, 결과가 주어질 때까지 아무것도 하지 못하고 대기해야 된다는 단점을 가지고 있습니다.

비동기 방식은 동기보다 복잡하지만, 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다는 장점을 가지고 있습니다.

2. Celery 사용법

Celery는 위에서 얘기했다시피 비동기 작업을 이용하기 위해 사용하려 합니다. 그러기 위해서는 크게 3가지의 구성을 필요로 합니다.

  1. Client : Task를 생성
  2. Broker : Task를 Worker에게 전달 (Redis)
  3. Worker : Task를 실행하고 처리 (Celery)

이 중에 우선 Broker를 살펴보겠습니다.

1. Broker (Redis)

우선 Broker에는 대표적으로 2개가 있습니다. 어떤 작업을 하고 어떤 것을 중요시 하느냐에 따라 Broker를 선택해주면 됩니다. 이번 포스팅은 Redis를 가지고 설명하겠습니다.

  1. RabbitMQ - 안정성 👍, 속도 👎
  2. Redis - 안정성 👎, 속도 👍

Redis 설치

아래의 명령어를 가지고 설치해주면 마지막에 ping-pong이 나오면 성공적으로 설치한 것입니다.

$ wget http://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
$ cd redis-stable
$ make
$ redis-server # redis 실행
$ redis-cli ping # 정상 설치되었는지 확인
> PONG

2. Worker (Celery)

python으로 작성된 비동기 작업 큐(Asynchronous task queue/job queue)

Celery는 Redis가 가져다 준 Task를 처리하는 모듈입니다.

1. Celery 설치

pip를 이용해 cerlry 모듈과 redis와의 연동을 위한 dependency를 한 번에 설치해줍니다.

pip install 'celery[redis]'

이후에 Celery 환경설정을 해주면 됩니다.

2. settings.py

수정하고 할 것도 없습니다. 일단 적어주세요.

# config/settings.py

CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Seoul'

3. celery.py

app폴더가 아닌 프로젝트 폴더. settings.py 가 있는 폴더 안에 만드세요!

# config/celery.py

import os

from celery import Celery
from django.conf import settings

# DJANGO_SETTINGS_MODULE의 환경 변수를 설정해준다.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
app = Celery('qna')

# namespace를 설정해준 것은 celery 구성 옵션들이 모두 앞에 CELERY_가 붙게 되는 것을 의미합니다.
app.config_from_object('django.conf:settings', namespace='CELERY')

# 다음을 추가해주면 celery가 자동적으로 tasks를 찾는데, 우리가 설치되어 있는 앱에서 찾아줍니다. 
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)


@app.task(bind=True)
def debug_task(self):
    print("Request: {0!r}".format(self.request))

4. __init.py

이렇게 해주면 Django가 시작될 때 @shared_task 데코레이터가 앱을 사용할 수 있도록 앱이 로드됩니다.
우선 이번에는 @app_task를 사용하기 때문에 따로 적어주지 않아도 괜찮습니다.

# config/__init__.py

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ['celery_app']

3. Django 연동

앞서 SMTP를 가지고 메일을 보내는 코드를 비동기로 연동해보려 합니다.

1. [app]/tasks.py

우선 view에 있던 send_email 기능을 worker에게 전달할 업무로 따로 빼주겠습니다.
하나만 있으면 섭섭하니 아래에 덧셈 함수도 호딱 만들어줍니다.

# qna/tasks.py

from django.core.mail import EmailMessage

from config.celery import app


@app.task()
def task_send_email():
    subject = "message"
    to = ["odh0112@naver.com"]
    from_email = "odh0112@naver.com"
    message = "메세지 테스트"
    EmailMessage(subject=subject, body=message, to=to, from_email=from_email).send()
    
    
@app.task()
def task_add(x, y):
	return x + y

아 그리고 @app.task를 이용하는 방법이 있고, @shared_task를 이용하는 방법이 있는데 이 둘의 차이는 우선 패쑤

2. [app]/views.py

그러면 View는 이렇게 허전하게 남아있게 되구용

# qna/views.py

from rest_framework.response import Response
from .tasks import task_send_email

def send_email(request):
    try:
        task_send_email.delay()
        task_add.delay(1, 2)
        return Response("성공했습니다.")
    except Exception as e:
        return Response("성공적으로 보내지 못했습니다.")

3. config/urls.py

이를 사용하기 위해 URL 라우팅을 해주면 끝입니다.

# config/urls.py

from qna.views import *

urlpatterns = [
    path('qna/', send_email, name="send_email"),
]

4. 실행 방법

1. redis 실행

다음과 같이 입력해주면 redis를 간단히 실행할 수 있습니다.

# Redis 실행
redis-server

# Redis 정지
$ redis-cli shutdown
or
$ systemctl stop redis
(venv) root@ip-172-31-45-200:/home/ubuntu/celery_test# redis-server
2114:C 03 Jan 2023 10:19:51.571 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2114:C 03 Jan 2023 10:19:51.571 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=2114, just started
2114:C 03 Jan 2023 10:19:51.571 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
2114:M 03 Jan 2023 10:19:51.572 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.16 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 2114
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

2114:M 03 Jan 2023 10:19:51.575 # Server initialized
2114:M 03 Jan 2023 10:19:51.575 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
2114:M 03 Jan 2023 10:19:51.576 * Loading RDB produced by version 6.0.16
2114:M 03 Jan 2023 10:19:51.577 * RDB age 1017261 seconds
2114:M 03 Jan 2023 10:19:51.577 * RDB memory usage when created 0.77 Mb
2114:M 03 Jan 2023 10:19:51.577 * DB loaded from disk: 0.001 seconds
2114:M 03 Jan 2023 10:19:51.577 * Ready to accept connection

2. Celery 실행

# shell 환경에서 입력해주면 됩니다.

celery -A [파일이름] worker --loglevel=info
celery -A config worker --loglevel=info

[tasks] - 비동기로 작업할 수 있는 목록들이 표시됩니다.

3. 서버 실행

웹서버와 WSGI로 구성되어 있으면 그렇게 실행해주면 되고, 그게 아니라면 그냥 runserver로 실행해주면 됩니다.

python manage.py runserver

or 

gunicorn --bind 0:8000 config.wsgi:appication

5. Celery WorkFlow

  1. @app_task로 처리하고 싶은 일에 딱지를 붙인다.
  2. task_send_email 작업에 delay를 붙이면 Redis Backend에 기록이 저장되고
  3. Redis는 Celery에게 일을 전달해줍니다.
  4. 일을 받은 Celery는 task_send_email 작업을 시작합니다.

참고자료 📩
Celery 공식문서 - Django settings 방법
Celery 공식문서 - Django에서의 응용 방법들

profile
삽질의 기록들🐥
post-custom-banner

0개의 댓글