목록으로

프로그래밍 · Python

Python Celery 실전 가이드: 비동기 작업 큐 구축과 운영 전략

BeanCon
Python Celery 비동기 작업 큐와 Worker 운영 구조를 표현한 대표 이미지

Python Celery의 개념과 필요성, Broker·Worker·Result Backend 구조, 설치, Redis 연동, 기본 예제, delay와 apply_async 차이, FastAPI 연동, Docker Compose 구성, Celery Beat, Queue 분리, Retry, Timeout, Flower 모니터링과 실무 운영 전략을 정리했습니다.

1. Celery란?

Celery는 Python에서 가장 널리 사용되는 분산 작업 큐, Distributed Task Queue 프레임워크입니다. 웹 애플리케이션에서 시간이 오래 걸리는 작업을 즉시 처리하지 않고, 별도의 Worker 프로세스에게 맡겨 백그라운드에서 실행하도록 도와줍니다.

공식 문서에서도 Celery를 “실시간 처리에 초점을 둔 Task Queue이며, 스케줄링도 지원하는 분산 시스템”으로 설명합니다.

예를 들어 다음과 같은 작업에 Celery를 사용할 수 있습니다.

회원가입 이메일 발송
대용량 엑셀 파일 생성
이미지 리사이징
PDF 생성
외부 API 호출
크롤링 작업
AI 모델 추론
정기 배치 작업
로그 분석
알림 발송

즉, Celery는 웹 서버 옆에서 묵묵히 일하는 “백그라운드 작업 공장”입니다. 웹 서버가 손님을 응대하는 카운터라면, Celery Worker는 주방 안쪽에서 오래 걸리는 요리를 처리하는 셰프에 가깝습니다.

2. Celery가 필요한 이유

일반적인 웹 요청은 빠르게 응답해야 합니다.

예를 들어 사용자가 회원가입 버튼을 눌렀을 때 서버가 다음 작업을 모두 직접 처리한다고 가정해 보겠습니다.

1. 사용자 정보 저장
2. 이메일 인증 메일 발송
3. 가입 쿠폰 생성
4. 추천 시스템 업데이트
5. 관리자 알림 발송

이 모든 작업을 한 번에 처리하면 사용자는 응답을 오래 기다려야 합니다. 이때 Celery를 사용하면 핵심 작업만 즉시 처리하고, 나머지는 Worker에게 넘길 수 있습니다.

웹 서버:
사용자 정보 저장 후 즉시 응답

Celery Worker:
이메일 발송
쿠폰 생성
알림 발송
로그 처리

이렇게 하면 서비스 응답 속도, 안정성, 확장성이 크게 좋아집니다.

3. Celery의 핵심 구조

Celery는 보통 다음 구성 요소로 동작합니다.

Client / Web App
      ↓
Message Broker
      ↓
Celery Worker
      ↓
Result Backend

3.1 Client 또는 Producer

작업을 요청하는 애플리케이션입니다. Django, Flask, FastAPI, 일반 Python 스크립트 등이 될 수 있습니다.

예를 들어 FastAPI 서버에서 “이메일 보내기 작업을 실행해줘”라고 Celery에 요청할 수 있습니다.

3.2 Broker

Broker는 작업 메시지를 임시로 저장하고 Worker에게 전달하는 중간 큐입니다.

대표적으로 다음 도구를 사용합니다.

Redis
RabbitMQ
Amazon SQS

Celery 공식 문서에서는 RabbitMQ와 Redis 조합이 흔히 사용되며, Redis는 빠른 메시지 처리에 적합하고 RabbitMQ는 메시지 브로커로 널리 사용된다고 설명합니다.

실무에서는 간단한 프로젝트나 내부 시스템에서는 Redis를 많이 사용하고, 메시지 안정성과 라우팅이 중요한 시스템에서는 RabbitMQ를 선택하는 경우가 많습니다.

3.3 Worker

Worker는 실제 작업을 수행하는 프로세스입니다.

celery -A app worker --loglevel=info

Worker는 Broker에서 작업을 가져와 실행합니다.

예를 들어 다음 작업을 처리할 수 있습니다.

send_email()
generate_report()
resize_image()
sync_external_api()

Worker는 여러 대로 확장할 수 있습니다.

Worker 1
Worker 2
Worker 3
Worker 4

작업량이 많아지면 Worker 수를 늘려 처리량을 높일 수 있습니다.

3.4 Result Backend

Result Backend는 작업 실행 결과를 저장하는 공간입니다.

예를 들어 다음 정보를 저장합니다.

작업 성공 여부
작업 결과값
실패 에러 메시지
작업 상태

Redis, Database, RPC Backend 등을 사용할 수 있습니다. 단순히 작업만 실행하고 결과가 필요 없다면 Result Backend를 사용하지 않을 수도 있습니다.

4. Celery 설치 방법

4.1 Python 가상환경 생성

mkdir celery-demo
cd celery-demo

python -m venv venv
source venv/bin/activate

Windows에서는 다음 명령을 사용합니다.

venv\Scripts\activate

4.2 Celery 설치

pip install celery

Redis를 Broker로 사용할 경우 다음 패키지도 설치합니다.

pip install redis

또는 한 번에 설치할 수 있습니다.

pip install "celery[redis]"

Celery 공식 Getting Started 문서에서도 Celery 설치 후 Broker 선택, 애플리케이션 생성, Worker 실행, Task 호출 순서로 기본 흐름을 안내합니다.

4.3 Redis 실행

Docker를 사용하면 Redis를 쉽게 실행할 수 있습니다.

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:7

Redis 실행 확인:

docker ps

5. 가장 간단한 Celery 예제

5.1 프로젝트 구조

celery-demo/
├── tasks.py
└── run_task.py

5.2 tasks.py 작성

from celery import Celery

app = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1"
)

@app.task
def add(x, y):
    return x + y

여기서 중요한 부분은 다음과 같습니다.

broker="redis://localhost:6379/0"

작업 메시지를 Redis 0번 DB에 저장합니다.

backend="redis://localhost:6379/1"

작업 결과를 Redis 1번 DB에 저장합니다.

5.3 Worker 실행

터미널에서 다음 명령을 실행합니다.

celery -A tasks worker --loglevel=info

정상 실행되면 Worker가 작업을 기다리는 상태가 됩니다.

5.4 작업 호출

새 터미널을 열고 Python Shell을 실행합니다.

python

다음 코드를 입력합니다.

from tasks import add

result = add.delay(10, 20)
print(result.id)
print(result.get(timeout=10))

결과:

30

delay()는 Celery 작업을 비동기로 요청하는 가장 기본적인 방법입니다. 즉시 결과를 계산하는 것이 아니라 Broker에 작업을 등록하고 Worker가 처리하도록 넘깁니다.

6. delay()와 apply_async() 차이

Celery 작업 호출에는 대표적으로 두 가지 방식이 있습니다.

6.1 delay()

가장 간단한 비동기 실행 방식입니다.

send_email.delay("user@example.com")

내부적으로는 apply_async()의 축약형입니다.

6.2 apply_async()

더 세밀한 제어가 필요할 때 사용합니다.

send_email.apply_async(
    args=["user@example.com"],
    countdown=10
)

10초 뒤 실행:

task.apply_async(countdown=10)

특정 시간에 실행:

from datetime import datetime, timedelta

task.apply_async(
    eta=datetime.utcnow() + timedelta(minutes=5)
)

Queue 지정:

task.apply_async(queue="email")

7. 실무형 프로젝트 구조

간단한 예제에서는 tasks.py 하나만 사용해도 됩니다. 하지만 실무에서는 구조를 분리하는 것이 좋습니다.

my_project/
├── app/
│   ├── __init__.py
│   ├── celery_app.py
│   ├── tasks/
│   │   ├── __init__.py
│   │   ├── email_tasks.py
│   │   └── report_tasks.py
│   └── main.py
├── requirements.txt
└── docker-compose.yml

8. Celery 설정 분리하기

app/celery_app.py

from celery import Celery

celery_app = Celery(
    "my_project",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
    include=[
        "app.tasks.email_tasks",
        "app.tasks.report_tasks"
    ]
)

celery_app.conf.update(
    task_serializer="json",
    result_serializer="json",
    accept_content=["json"],
    timezone="Asia/Seoul",
    enable_utc=False,
)

app/tasks/email_tasks.py

import time
from app.celery_app import celery_app

@celery_app.task
def send_email_task(email: str, subject: str):
    time.sleep(3)
    print(f"Send email to {email}: {subject}")
    return {
        "email": email,
        "status": "sent"
    }

app/tasks/report_tasks.py

import time
from app.celery_app import celery_app

@celery_app.task
def generate_report_task(user_id: int):
    time.sleep(5)
    return {
        "user_id": user_id,
        "file_path": f"/reports/{user_id}.pdf"
    }

Worker 실행

celery -A app.celery_app.celery_app worker --loglevel=info

9. FastAPI와 Celery 연동 예제

Celery는 Flask, Django, FastAPI 같은 웹 프레임워크와 함께 많이 사용됩니다. Flask 공식 문서에서도 Celery를 단순 백그라운드 작업부터 복잡한 다단계 프로그램과 스케줄 작업까지 사용할 수 있는 강력한 Task Queue로 소개합니다.

FastAPI 예제는 다음과 같습니다.

app/main.py

from fastapi import FastAPI
from app.tasks.email_tasks import send_email_task
from app.tasks.report_tasks import generate_report_task
from app.celery_app import celery_app

api = FastAPI()

@api.post("/emails")
def send_email(email: str, subject: str):
    task = send_email_task.delay(email, subject)

    return {
        "message": "Email task submitted",
        "task_id": task.id
    }

@api.post("/reports/{user_id}")
def create_report(user_id: int):
    task = generate_report_task.delay(user_id)

    return {
        "message": "Report generation started",
        "task_id": task.id
    }

@api.get("/tasks/{task_id}")
def get_task_status(task_id: str):
    result = celery_app.AsyncResult(task_id)

    return {
        "task_id": task_id,
        "status": result.status,
        "result": result.result if result.ready() else None
    }

FastAPI 실행:

uvicorn app.main:api --reload

Celery Worker 실행:

celery -A app.celery_app.celery_app worker --loglevel=info

10. Docker Compose로 Celery 구축하기

실무에서는 Redis, API 서버, Celery Worker를 함께 구성하는 경우가 많습니다.

docker-compose.yml

version: "3.9"

services:
  redis:
    image: redis:7
    container_name: celery-redis
    ports:
      - "6379:6379"

  api:
    build: .
    container_name: celery-api
    command: uvicorn app.main:api --host 0.0.0.0 --port 8000
    volumes:
      - .:/code
    working_dir: /code
    ports:
      - "8000:8000"
    depends_on:
      - redis

  worker:
    build: .
    container_name: celery-worker
    command: celery -A app.celery_app.celery_app worker --loglevel=info
    volumes:
      - .:/code
    working_dir: /code
    depends_on:
      - redis

Dockerfile

FROM python:3.12-slim

WORKDIR /code

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

requirements.txt

fastapi
uvicorn
celery[redis]
redis

실행:

docker compose up --build

11. Celery Beat로 정기 작업 실행하기

Celery는 단순 비동기 작업뿐 아니라 스케줄링도 지원합니다. 정기적으로 실행해야 하는 작업에는 Celery Beat를 사용합니다.

예시:

매일 새벽 1시 로그 정리
매시간 뉴스 수집
5분마다 외부 API 동기화
매주 월요일 리포트 생성

celery_app.py에 Beat 설정 추가

from celery.schedules import crontab

celery_app.conf.beat_schedule = {
    "run-every-minute": {
        "task": "app.tasks.report_tasks.generate_report_task",
        "schedule": 60.0,
        "args": (1,)
    },
    "run-daily-report": {
        "task": "app.tasks.report_tasks.generate_report_task",
        "schedule": crontab(hour=1, minute=0),
        "args": (100,)
    },
}

Beat 실행:

celery -A app.celery_app.celery_app beat --loglevel=info

Worker와 Beat를 함께 실행해야 합니다.

celery -A app.celery_app.celery_app worker --loglevel=info
celery -A app.celery_app.celery_app beat --loglevel=info

12. Queue 분리 전략

작업 종류에 따라 Queue를 분리하면 운영이 쉬워집니다.

email queue
report queue
ai queue
crawler queue

예를 들어 이메일 작업과 AI 추론 작업을 같은 Worker에서 처리하면, 무거운 AI 작업 때문에 이메일 발송이 지연될 수 있습니다.

작업별 Queue 지정

send_email_task.apply_async(
    args=["user@example.com", "Welcome"],
    queue="email"
)

generate_report_task.apply_async(
    args=[10],
    queue="report"
)

Queue별 Worker 실행

celery -A app.celery_app.celery_app worker \
  -Q email \
  --loglevel=info
celery -A app.celery_app.celery_app worker \
  -Q report \
  --loglevel=info

이렇게 하면 작업 성격에 따라 Worker를 독립적으로 운영할 수 있습니다.

13. Retry 처리

외부 API 호출, 이메일 발송, 파일 업로드 같은 작업은 실패할 수 있습니다. Celery에서는 Retry를 통해 자동 재시도를 구현할 수 있습니다.

from app.celery_app import celery_app

@celery_app.task(
    bind=True,
    max_retries=3,
    default_retry_delay=10
)
def call_external_api(self, url: str):
    try:
        # 외부 API 호출 로직
        raise Exception("Temporary API error")
    except Exception as exc:
        raise self.retry(exc=exc)

설명:

max_retries=3
최대 3번 재시도

default_retry_delay=10
10초 후 재시도

bind=True
Task 자기 자신 self에 접근 가능

실무에서는 Retry 설정이 매우 중요합니다. 특히 외부 API, 네트워크, 결제, 메일 발송처럼 실패 가능성이 있는 작업은 반드시 재시도 전략을 설계해야 합니다.

14. Timeout 설정

작업이 무한정 실행되면 Worker 자원을 잡아먹을 수 있습니다. 따라서 Time Limit을 설정하는 것이 좋습니다.

@celery_app.task(
    time_limit=300,
    soft_time_limit=240
)
def long_running_task():
    pass
soft_time_limit
작업에 종료 신호를 보냄

time_limit
강제 종료

15. 작업 상태 확인

Celery 작업은 다음과 같은 상태를 가질 수 있습니다.

PENDING
STARTED
SUCCESS
FAILURE
RETRY
REVOKED

상태 확인 예제:

result = celery_app.AsyncResult(task_id)

print(result.status)
print(result.ready())
print(result.successful())
print(result.result)

API로 제공하면 프론트엔드에서 작업 진행 상태를 조회할 수 있습니다.

POST /reports/1
→ task_id 반환

GET /tasks/{task_id}
→ 현재 상태 조회

16. Flower로 Celery 모니터링하기

Flower는 Celery 작업을 웹 UI로 모니터링할 수 있는 도구입니다.

설치:

pip install flower

실행:

celery -A app.celery_app.celery_app flower

접속:

http://localhost:5555

Flower에서 확인할 수 있는 정보:

Worker 상태
Task 성공 / 실패 내역
실행 중인 작업
작업 소요 시간
Queue 상태
Retry 정보

운영 환경에서는 Flower에 인증을 붙이는 것이 좋습니다.

celery -A app.celery_app.celery_app flower \
  --basic_auth=admin:password

17. Celery와 Redis 사용 시 주의점

Redis는 빠르고 설정이 간단하지만 모든 상황에 만능은 아닙니다.

주의할 점:

Redis 장애 시 작업 유실 가능성 검토 필요
메모리 사용량 모니터링 필요
대량 작업 적재 시 메모리 압박 발생 가능
중요 작업은 ACK, Retry, Visibility Timeout 고려 필요
결과 Backend를 무기한 보관하지 않도록 설정 필요

결과 만료 시간 설정:

celery_app.conf.result_expires = 3600

1시간 후 결과를 만료시킵니다.

18. RabbitMQ를 사용하는 경우

RabbitMQ는 메시지 브로커로서 기능이 풍부합니다.

장점:

안정적인 메시지 브로커
라우팅 기능 우수
Exchange / Queue / Routing Key 구조 제공
복잡한 메시징 패턴에 적합

RabbitMQ Broker URL 예시:

broker="amqp://guest:guest@localhost:5672//"

Docker 실행:

docker run -d \
  --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  rabbitmq:3-management

관리 콘솔:

http://localhost:15672

기본 계정:

guest / guest

19. Celery 실무 활용 시나리오

19.1 이메일 발송 시스템

회원가입
비밀번호 재설정
결제 완료 알림
공지 메일 발송

웹 요청에서 직접 SMTP를 호출하지 않고 Celery로 넘기면 응답 속도가 좋아집니다.

19.2 대용량 파일 생성

엑셀 다운로드
PDF 보고서 생성
CSV Export
정산 파일 생성

사용자는 요청만 보내고, 서버는 작업 ID를 반환합니다. 작업이 완료되면 다운로드 링크를 제공할 수 있습니다.

19.3 크롤링 / 뉴스 수집

RSS 수집
외부 뉴스 API 호출
본문 파싱
요약 생성
DB 저장

뉴스 수집 시스템에서는 Celery Beat와 Worker 조합이 매우 유용합니다.

Celery Beat:
매 5분마다 수집 작업 생성

Celery Worker:
뉴스 수집, 중복 검사, 저장, 요약 처리

19.4 AI / RAG 시스템

AI 서비스에서도 Celery는 자주 사용됩니다.

문서 임베딩
벡터 DB 저장
대용량 PDF 파싱
LLM 요약
외부 지식 동기화
비동기 질의 처리

특히 RAG 시스템에서는 문서 업로드 후 다음 작업을 백그라운드로 넘기는 구조가 자연스럽습니다.

파일 업로드
→ Celery 작업 생성
→ PDF 파싱
→ Chunk 분리
→ Embedding 생성
→ Vector DB 저장
→ 완료 상태 업데이트

20. 운영 환경에서 고려해야 할 설정

20.1 Worker 동시성

celery -A app.celery_app.celery_app worker \
  --concurrency=4 \
  --loglevel=info

CPU 작업이 많다면 CPU 코어 수를 고려해야 합니다. I/O 작업이 많다면 더 많은 동시성을 줄 수 있습니다.

20.2 Prefetch 설정

Worker가 한 번에 너무 많은 작업을 가져가면 특정 Worker에 작업이 몰릴 수 있습니다.

celery_app.conf.worker_prefetch_multiplier = 1

무거운 작업이 많은 경우 유용합니다.

20.3 작업 ACK 설정

작업이 완료된 뒤 ACK를 보내도록 설정할 수 있습니다.

celery_app.conf.task_acks_late = True

Worker가 작업 도중 죽었을 때 작업을 다시 처리하게 만들 수 있습니다.

20.4 결과 저장 비활성화

결과가 필요 없는 작업은 Backend 저장을 끄는 것이 좋습니다.

@celery_app.task(ignore_result=True)
def send_log():
    pass

21. Celery 사용 시 흔한 문제

21.1 Task가 등록되지 않음

증상:

Received unregistered task

원인:

include 설정 누락
import 경로 오류
Worker 재시작 누락
패키지 구조 문제

해결:

include=[
    "app.tasks.email_tasks",
    "app.tasks.report_tasks"
]

21.2 Redis 연결 실패

증상:

Error 111 connecting to localhost:6379. Connection refused.

확인:

docker ps
redis-cli ping

정상 응답:

PONG

Docker 환경에서는 localhost 대신 서비스명을 사용해야 합니다.

broker="redis://redis:6379/0"

21.3 작업 결과가 안 나옴

원인:

backend 미설정
ignore_result=True
작업 실패
Result Backend 연결 오류

확인:

result.status
result.result

21.4 Worker가 작업을 처리하지 않음

확인할 것:

Worker 실행 여부
Broker 연결 여부
Queue 이름 일치 여부
Task import 여부
로그 에러 여부

22. Celery vs APScheduler vs RQ

구분CeleryAPSchedulerRQ
주요 목적분산 작업 큐스케줄러Redis 기반 간단 작업 큐
BrokerRedis, RabbitMQ 등필요 없음Redis
Worker 분산강함약함보통
정기 작업Celery Beat강함별도 구성 필요
난이도중간쉬움쉬움
실무 확장성높음제한적중간

간단한 스케줄만 필요하다면 APScheduler도 충분합니다. 하지만 대량 비동기 작업, Worker 확장, Retry, Queue 분리, 모니터링이 필요하다면 Celery가 더 적합합니다.

23. Celery를 도입하면 좋은 경우

Celery는 다음 상황에서 특히 유용합니다.

요청 시간이 오래 걸리는 작업이 있다
외부 API 호출이 많다
이메일 / 알림 발송이 많다
파일 생성 작업이 있다
정기 배치 작업이 필요하다
작업 실패 시 재시도가 필요하다
Worker를 여러 대로 확장해야 한다
AI, RAG, 크롤링, 로그 처리 작업이 있다

반대로 다음 경우에는 과할 수 있습니다.

단순 CRUD 서비스
작업량이 매우 적은 서비스
서버 1대에서 간단한 스케줄만 필요한 경우
운영 복잡도를 감당하기 어려운 경우

24. 실무 권장 아키텍처

소규모 서비스:

FastAPI / Django
+ Redis
+ Celery Worker
+ Celery Beat

중규모 서비스:

API Server
+ Redis or RabbitMQ
+ Worker Group
+ Flower
+ Database
+ Object Storage

대규모 서비스:

API Gateway
+ Multiple Queues
+ RabbitMQ / Redis Cluster / SQS
+ Worker Auto Scaling
+ Observability Stack
+ Dead Letter Queue
+ Retry Policy
+ Result Store

25. 결론

Celery는 Python 백엔드 개발에서 비동기 작업과 분산 처리를 구현할 때 매우 강력한 도구입니다.

웹 서버가 모든 일을 직접 처리하면 응답 속도와 안정성이 떨어질 수 있습니다. Celery를 도입하면 오래 걸리는 작업을 Worker에게 위임하고, 웹 서버는 빠르게 응답할 수 있습니다.

핵심은 다음과 같습니다.

Celery는 Python의 대표적인 분산 작업 큐이다.
Redis나 RabbitMQ 같은 Broker가 필요하다.
Worker가 실제 작업을 수행한다.
Result Backend를 통해 작업 결과를 조회할 수 있다.
Celery Beat로 정기 작업을 실행할 수 있다.
Retry, Queue 분리, Timeout 설정이 실무 운영의 핵심이다.

Celery는 단순한 백그라운드 작업 도구를 넘어, 서비스 내부의 작업 흐름을 분리하고 확장 가능한 구조로 만드는 비동기 처리 엔진입니다.

이메일 발송, 파일 생성, 뉴스 수집, AI 문서 처리, RAG 파이프라인, 배치 작업까지 Python 기반 서비스에서 “오래 걸리는 일”이 등장한다면 Celery는 매우 좋은 선택지가 될 수 있습니다.

댓글

0

댓글을 불러오는 중입니다.