비동기 작업 큐(queue)를 python에서 활용할 수 있는 celery를 소개한다. 웹서버가 처리하기엔 무거운 연산(e.g. pdf 변환, 과학적 계산 프로세스 등)을 그냥 서버에 집어넣으면 사용자는 웹서버의 처리가 다 끝날 때 까지 빙빙 돌고있는 웹브라우저를 하염없이 바라보고만 있어야 할 것이다. 왜냐하면 웹서버의 작업은 동기적(synchronous)이기 때문이다. 반대로 비동기적 (asynchronous)인 작업이라는 것은 어딘가에 작업을 던져주고 그 작업이 끝나길 기다리지 않고 다른 일을 할 수 있다는 것이다. 그 예로 태생부터 비동기적인 javascript가 있다. 그리고 작업이 끝났는 지 지속적인 체크를 하다가 끝나면 그 결과물을 받으면 된다. celery는 메시지 브로커(message broker)와 python 작업 프로세스를 연결해서 이러한 비동기 작업을 수행할 수 있는 시스템을 제공한다. 그리고 추가적으로 (python과는 독립적으로) celery 밑을 바쳐줄 메시지 브로커가 필요하다. 뜬금없이 언급한 메시지 브로커는 다음 예를 통해서 설명하려고 한다.

0. 비동기 작업의 사례, 메시지 브로커의 필요성

비동기 작업이 필수적인 곳이 있는데, 바로 은행 전산 시스템이다. 전국의 은행 지점에서 돈이 오고 가는 것이 전산적으로 어딘가의 중앙 시스템에 기록이 된다. 동시다발적으로 거래가 일어날 텐데, 전산 작업 중 일부가 충돌해서 문제가 발생한다면 은행 입장에서는 재앙과 같은 일이 된다. 이런 위험을 해소해주는 것이 메시지 브로커를 이용한 비동기 작업 큐를 구축하는 것이다.

queue라는 단어 자체가 컨베이어 벨트처럼 작업을 기다리는 줄을 의미한다. 모든 은행 지점에서 거래에 대한 전산 입력을 할 때마다 중앙 시스템의 메시지 큐에 순차적으로 작업이 등록시키고, 중앙 시스템은 큐에 등록된 작업을 차례대로 수행하면 충돌의 위험을 많이 줄일 수 있는 것이다. 이러한 메시지 큐 시스템과 여기에 작업을 전달해주는 시스템을 브로커라고 부르는 것이다.

1. 브로커 redis 설치

그래서 일단 celery보다 먼저 메시지 브로커를 설치하겠다. 사실 redis가 단지 메시지 브로커인 것은 아니고, (in-memory) 데이터 저장 장치인 redis를 메시지 브로커 용도로 사용하는 것이라고 생각하면 된다. redis 설치에 대한 글을 따로 써두었다. 이 곳을 참조하자.

2. celery 설치

pip를 이용해 celery 모듈과 redis와의 연동을 위한 dependency를 한 번에 설치한다. 별거 없다.


$ pip install --upgrade celery[redis]

3. 기본적인 설정으로 celery 시작

redis를 기본 설정으로 설치했다면, 다음과 같이 브로커의 주소와, 작업을 저장할 backend 주소를 아래와 같이 python 스크립트에 입력할 것이다. redis 주소의 공식은 redis://:password@hostname:port/db_number임을 참고하자.


BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'

이제 본격적이지만 매우 단순한 celery 사용 코드를 보겠다.


# tasks.py
from celery import Celery
BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'

app = Celery('tasks', broker=BROKER_URL, backend=CELERY_RESULT_BACKEND)
@app.task
def add(x, y):
    return x + y

app이라는 celery 객체를 실행시키고, 여기에 add라는 엄청난 계산을 하는 작업을 등록했다. 이제 우리는 celery 프로세스를 실행해서 redis와 연동해서 작업을 받을 준비를 하겠다.


$ celery -A tasks worker --loglevel=info

-------------- celery@yourmachine v3.1 (Cipater) ---- **** ----- --- * *** * -- [Configuration] -- * - **** --- . broker: redis://localhost:6739/0/ - ** ---------- . app: __main__:0x1012d8590 - ** ---------- . concurrency: 8 (processes) - ** ---------- . events: OFF (enable -E to monitor this worker) - ** ---------- - *** --- * --- [Queues] -- ******* ---- . celery: exchange:celery(direct) binding:celery --- ***** ----- [2016-09-25 13:44:51,078: WARNING/MainProcess] celery@yourmachine has started.

celery 로고와 함께 설정 스펙이 뜨고, 조금 기다리면 작업을 받을 준비가 됨을 볼 수 있다. 이제 직접 코드를 입력해서 작업을 넣어보자.


$ ipython
>>> from tasks import add

>>> result = add.delay(4,4)

여기까지 실행시키고 celery 프로세스가 실행 중인 터미널을 띄워 위 작업이 등록된 것을 확인해보자. 계산이 간단하기 때문에 순식간에 완료됨을 알 수 있다. 작업이 완료되었는지 직접 확인해보자. 참고로 CELERY_RESULT_BACKEND를 지정해서 저장할 수 있는 설정을 해두었기 때문에 add.delay(4,4)의 작업을 result에 담아서 이후의 작업 상황을 확인할 수 있음을 알아두자.


>>> result.ready()
True ### False일 경우 아직 계산 중임을 뜻한다
>>> result.get()
8 ### add(4,4)의 계산 결과

4. 마무리 정리

다시 한 번 비동기 작업 큐의 스토리를 정리해보겠다.

  • celery 프로세스를 띄워서 작업을 받을 준비를 마쳤다.
  • 내가 add 작업을 (delay를 통해) 해 달라고 추가했고 redis backend에 그 기록이 저장된다.
  • 현재 하고 있는 일이 없는 redis는 새로 온 작업을 저장 후 celery 프로세스에 전달한다.
  • 작업을 전달받은 celery는 add를 계산하기 시작하고
  • 나는 redis backend를 통해 그 계산이 완료되었는지 확인하고 완료되었다면 그 결과값을 받을 수 있다.

다음 글에서는 celery 프로세스를 django 위에 얹어서 실제 웹에서 비동기 작업 큐를 이용하는 방법을 설명할 예정이다.


참조링크

이 글은 매우 간단한 예제를 통해서 비동기 작업을 이해하는 것에 목적을 두고 있으니, 자세한 사용방법은 아래 공식 문서를 통해 알아보자.
http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html
http://docs.celeryproject.org/en/latest/getting-started/next-steps.html