Celery 를 이용한 비동기 태스크 큐 활용하기

고희원
타운컴퍼니 기술블로그
6 min readMay 4, 2018

타운어스 플랫폼에서는 카카오 알림톡을 발송하는 작업, 공동구매방의 마감일에 자동으로 공동구매방을 마감하는 작업 등에서 Celery를 사용하고 있습니다. Celery 는 비동기 태스크 큐를 python 에서 사용할 수 있는 모듈입니다.

Celery 는 왜 필요한가?

기본적으로 웹 서버는 프로세스를 동기적(Synchronous)으로 처리하기 때문에 다소 무거운 연산이나 오래 걸리는 작업의 경우 사용자는 웹 서버의 처리가 모두 마무리될 때까지 기다려야 합니다. 이 때, 오래 걸리는 작업은 비동기 처리 방식을 사용해 사용자가 해당 작업을 기다리지 않고 다른 작업을 진행할 수 있도록 사용자 측면에서의 속도 개선을 유도하기 위해 Celery 를 이용한 비동기 태스크 큐를 사용하였습니다.

보다 직관적인 파악을 위해 Celery 를 사용할 때와 사용하지 않을 때의 프로세스 Flow 를 비교해보도록 하겠습니다.

먼저 Celery 를 사용하지 않는 기존의 Flow 를 보면

유저는 수행 시간이 오래 걸리는 작업을 요청합니다. 서버는 해당 작업을 처리하기 시작하고, 유저는 API 가 반환될 때까지 플랫폼을 사용하지 못하고 작업 시간만큼 대기해야 합니다.

이제 Celery 를 사용하여 이를 개선할 경우의 예상 Flow 를 보도록 합니다.

마찬가지로, 유저(클라이언트)는 수행 시간이 오래 걸리는 작업을 요청합니다. 요청을 받은 뷰에서는 Broker 에게 해당 작업 실행을 위임하고 각 작업을 구분할 수 있는 Task ID 를 발급받게 됩니다. 해당 작업은 Broker 가 놀고있는 Worker 에게 넘겨서 Worker 가 비동기로 수행하도록 합니다.

타운어스 플랫폼 내에서는 유저와 내부 관리자의 액션에 따라 여러 API 에서 카카오 알림톡을 발송하는데, 이 때 외부 API Kakao APIStore 를 연동합니다. 이 카카오 알림톡은 사용자가 웹으로부터 전달받아야 하는 데이터가 아니라 카카오톡 메시지를 통해 전달받기 때문에, 서버가 알림톡의 전송을 기다리지 않고 알림톡을 전송하는 루틴은 Worker에게 위임하여 비동기로 처리할 경우 API 응답 속도를 단축시킬 수 있습니다.

Celery 의 정의와 아키텍처

Celery 는 Python 으로 작성된 분산 메시지 전달을 기반으로 한 비동기 작업 큐(Asynchronous task queue/Job queue) 로, Worker 의 한 종류입니다. 전반적인 아키텍처는 다음과 같습니다.

Django 서버에서 Task 를 Message Borker 를 통해 전달을 하면 하나 이상의 Celery Worker 가 Message Broker Queue 에 있는 Task 를 받아서 이를 처리합니다. 저희는 Message Broker 로 Redis Server 를 사용하였습니다. Redis 는 컴퓨터 메모리를 이용한(in-memory) Cache 서버로, Key/Value 를 이용해 Celery 가 처리할 작업을 Celery 에 보낸 후 Cache 에서 해당 Key 를 제거하는 방식으로 작동합니다. Redis 는 데이터 검색을 위해 Database 에 접근하기 전 메모리에서 Cache 를 가져다 쓴다는 점에서 속도가 빠른 장점이 있습니다.

Celery 사용하기 1 .카카오 알림톡 발송 비동기 처리

야구잠바 공동구매를 위해 타운어스에서 공동구매방을 개설하고 디자인을 완료하면, 사용자의 디자인을 바탕으로 타운어스에서 디자인 시안을 업로드합니다. 이 때, 사용자에게는 아래와 같은 알림톡이 발송됩니다.

이제 Celery 를 이용하여 이 알림톡 발송을 어떻게 비동기적으로 처리하는지 보도록 하겠습니다.

먼저 Django 프로젝트의 settings 부분에 다음과 같이 Celery 와 관련된 설정을 추가합니다.

위 코드는 Celery 가 처리할 실제 로직입니다. 비동기로 처리하고자 하는 함수 위에 @app.task 또는 @shared_task 와 같은 decorator 를 붙여주면, 해당 함수에 대한 API Call 이 들어오는 순간 Redis 의 Queue 에 해당 작업이 할당됩니다. Celery 는 할당된 task 들의 순서에 따라 Queue 에 있는 Task 들을 순차적으로 처리합니다.

예를 들어, 위 코드에서 send_kakao_notification() 은 실제 알림톡을 발송하는 메서드를 호출하는 간단한 로직으로 구성된 함수로 Worker 가 수행했으면 하는 작업을 담았습니다. 플랫폼 서비스 코드 내의 알림톡 발송이 필요한 특정 위치에서 위 함수를 호출하면 Redis Backend 에 이 작업이 추가되며, Celery(worker) 에 전달하여 send_kakao_notification() 을 수행하게 됩니다. send_kakao_notification() 함수 내에 알림톡을 전송하는 로직을 직접 포함할 수도 있지만, 재사용성을 위해 모듈화하여 별도의 클래스를 이용해 따로 분리하였습니다.

KakaoNotificationManager 클래스의 msg_talk() 함수 내에서 알림톡 전송에 필요한 파라미터 값들을 지정한 후 POST 요청을 통해 카카오 알림톡을 발송합니다. 이 때의 POST 요청은 외부 API (Kakao APIStore)로의 POST 요청입니다.

해당 코드에 대한 Unit Test 를 실행할 때에는 실제로 알림톡을 발송하지 않도록 다음과 같이 APITest 를 셋업하는 시점에서 알림톡을 발송하는 POST 요청을 patch 하였습니다.

Celery 는 카카오 알림톡 발송 이외에도 공동구매방 마감일에 맞게 자동 마감 처리작업, 공동구매방의 생성과 각 공동구매방의 단계 등의 정보 reporting 등 다양한 작업에 사용되고 있습니다. Celery 가 적용된 다른 예시를 보도록 하겠습니다.

Celery 사용하기 2.공동구매방 자동 마감 처리

타운어스에서 공동구매방을 개설하고 마감일을 설정하면, 해당 마감 시간에 맞추어 공동구매방이 자동으로 마감됩니다.

이 작업 역시 마찬가지로 worker 에게 위임할 작업을 담고 있는 함수에 @shared_task decorator를 이용하여 Broker 에게 전달하고, 해당 작업을 받은 Celery (worker)는 set_is_ended() 내부에서 마감 시간을 체크하여 공동구매방 상태를 ‘마감’으로 변경합니다.

마무리하며..

Celery 는 장고에서 분산 태스크 큐의 표준으로 가장 많이 사용되는 모듈로 대용량 데이터 처리도 수월하며 기능도 풍부하기 때문에 이를 잘 활용한다면 서비스 최적화에 많은 도움을 줄 수 있습니다. Celery 를 이용한 작업 처리를 충분히 경험해보았다면 더 나아가 작성한 task 가 순차적으로 잘 실행되고 있는지 태스크 함수를 디버깅하고 task 와 worker 를 시각적으로 모니터링 해볼 수 있는 flower, django-redisboard, django-rq 와 같은 패키지가 있으니 이를 사용해보는 것도 좋을 것입니다.

감사합니다.

--

--