문제점
아래의 함수는 emails가 많을수록 문제가 아주 많아진다. 왜냐? 서버 프로세스를 점유(Block)하기 때문이다.
만약 Gunicorn Worker가 3개 있다고 가정할때, 3명의 유저가 동시에 send_emails를 수행하면 모든 프로세스는 Block되기 때문에 더이상 서비스가 불가능해진다. 아주 문제가 심각하다.
이 문제를 async Celery task로 변경해 웹프로세스 Block을 막아보자
def send_emails(request):
emails: list[str] = request.data.get('emails')
for email in emails:
requests.post(URL, data=email)
return Response(status=200)
WorkFlow
1. send_emails에서 async celery task 호출
2. 메시지 브로커가 방금 호출한 task를 받아서 celery worker로 넘긴다.
3. celery worker가 일을 처리하고 result backend에 결과를 넣어준다.
메시지브로커, result backend로 Redis, rabbitmq 등을 많이 사용한다.
Celery 사용
from typing import List
import requests
from celery import shared_task
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
@shared_task()
def task(emails: List[str]) -> None:
for _ in emails:
requests.post('https://httpbin.org/delay/1')
@csrf_exempt
def send_emails(request):
emails: List[str] = request.POST.get('emails')
task_id = task.delay(['1.naver.com', '2.naver.com', '3.naver.com'])
return JsonResponse({'task_id': task_id.id})
def task_status(request):
task_id = request.GET.get('task_id')
if task_id:
task: AsyncResult = AsyncResult(task_id)
if task.state == 'FAILURE':
error = str(task.result)
response = {
'state': task.state,
'error': error,
}
else:
response = {
'state': task.state,
}
return JsonResponse(response)
위와 같이 view에서 .delay 함수를 사용해서 호출해 주면 celery에 의해서 async하게 작동한다.
즉 email들이 다 처리되는걸 기다리지 않고(non-block) 일단 Response를 반환한다.
그럼 해당 Task가 성공적으로 처리 됐는지 어떻게 판단하냐?
Result Backend에서 해당 Task가 지금 처리됐는지 아닌지 확인하면 된다. 그러므로 반환 시 해당 Task의 id를 던져주고 프론트단에서 해당 task_status를 지속적으로 호출해 해당 작업이 완료됐는지 확인한다.
결론
시간이 오래걸리거나, 네트워크 io 작업이 있는 작업은 view에서 바로 처리하지말자. 반환 속도도 느려지고, gunicorn worker도 block되기 때문에 좋지 않다.
코드 github : https://github.com/seunwoolee/celery_test
'django' 카테고리의 다른 글
Django prefetch_related, select_related 예제(N+1 Problem) (0) | 2022.07.07 |
---|---|
github action을 이용한 django 테스트 자동화(with docker) (0) | 2022.07.03 |
Django workers process 메모리 공유 (0) | 2021.02.19 |
Django queryset to json (0) | 2019.12.31 |
Django related_name(reverse 역참조 ) (0) | 2019.11.13 |