Celery Broker — Redis vs RabbitMQ, 메시지 보장의 기초

Celery 브로커가 하는 일, Redis와 RabbitMQ의 차이, AMQP 메시지 보장(Durability, Acknowledgment)이 태스크 손실을 어떻게 방지하는지 설명한다.

Seobway · · 12분

브로커란 무엇인가

브로커(Message Broker)는 Producer(Django 뷰, Beat)가 발행한 태스크 메시지를 저장하고,
Worker가 요청할 때까지 보관하는 중간 저장소다.[1]

%% desc: 브로커의 역할 — Producer와 Worker를 분리하는 중간 메시지 저장소
flowchart LR
  subgraph PROD["Producer"]
    DJ["Django 뷰"]
    BEAT["Celery Beat"]
  end

  subgraph BROKER["메시지 브로커"]
    Q1["큐: default"]
    Q2["큐: email"]
    Q3["큐: reports"]
  end

  subgraph CONSUMER["Consumer"]
    W1["Worker 1"]
    W2["Worker 2"]
    W3["Worker 3"]
  end

  DJ --> Q1 & Q2
  BEAT --> Q1 & Q3
  Q1 --> W1
  Q2 --> W2
  Q3 --> W3

브로커 덕분에 Producer와 Worker가 완전히 분리된다.
Worker가 잠시 다운돼도 메시지는 큐에 보관되고, Worker가 재시작하면 처리된다.

Redis 브로커

Redis는 Celery에서 가장 많이 쓰이는 브로커다.[2]

# settings.py
CELERY_BROKER_URL = "redis://localhost:6379/0"
# 비밀번호 사용
CELERY_BROKER_URL = "redis://:mypassword@localhost:6379/0"
# Redis Sentinel (고가용성)
CELERY_BROKER_URL = "sentinel://localhost:26379;sentinel://localhost:26380/0"

Redis 브로커의 동작

Redis는 리스트(List) 자료구조를 큐로 사용한다.

%% desc: Redis 브로커의 LPUSH/BRPOP 메커니즘 — 태스크 발행과 소비
sequenceDiagram
  participant PROD as Producer
  participant REDIS as Redis
  participant WORKER as Worker

  PROD->>REDIS: LPUSH celery {task_message}
  Note over REDIS: 리스트에 메시지 추가

  WORKER->>REDIS: BRPOP celery 0 (블로킹 대기)
  REDIS-->>WORKER: 메시지 반환
  Note over WORKER: 태스크 실행

  alt ACK Late 설정 시
    WORKER->>REDIS: 완료 후 별도 처리
  end

Redis 브로커의 주의점

Redis는 기본적으로 인메모리 저장소다.
서버가 재시작되면 큐의 메시지가 사라질 수 있다.

# Redis 영속성 설정 (redis.conf)
# AOF (Append Only File) — 모든 명령 로그
appendonly yes
appendfsync everysec

# RDB 스냅샷
save 900 1
save 300 10

또한 Redis는 ACK를 Celery 레벨에서 처리한다.
Worker가 태스크를 가져간 후 프로세스가 죽으면 메시지가 손실될 수 있다.

# Worker가 태스크 완료 후 ACK를 전송하도록 설정 (기본: 시작 후 즉시 ACK)
CELERY_TASK_ACKS_LATE = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1

RabbitMQ 브로커

RabbitMQ는 AMQP(Advanced Message Queuing Protocol) 기반의 전용 메시지 브로커다.[3]

# settings.py
CELERY_BROKER_URL = "amqp://guest:guest@localhost:5672//"
# vhost 지정
CELERY_BROKER_URL = "amqp://myuser:mypassword@localhost:5672/myvhost"

AMQP 구조

RabbitMQ는 Exchange → Binding → Queue의 라우팅 구조를 갖는다.

%% desc: RabbitMQ AMQP 구조 — Exchange가 라우팅 키 기반으로 메시지를 큐에 분배
flowchart LR
  PROD["Producer\n(Django/Beat)"]

  subgraph RABBIT["RabbitMQ"]
    subgraph EXCHANGE["Exchange"]
      EX["celery\n(direct type)"]
    end

    subgraph QUEUES["Queues"]
      Q1["celery (default)"]
      Q2["email"]
      Q3["reports"]
    end
  end

  WORKER1["Worker 1\n(default 큐)"]
  WORKER2["Worker 2\n(email 큐)"]
  WORKER3["Worker 3\n(reports 큐)"]

  PROD -- "routing_key=celery" --> EX
  PROD -- "routing_key=email" --> EX
  EX -- "binding: celery→celery" --> Q1
  EX -- "binding: email→email" --> Q2
  EX -- "binding: reports→reports" --> Q3
  Q1 --> WORKER1
  Q2 --> WORKER2
  Q3 --> WORKER3

RabbitMQ 메시지 보장

RabbitMQ는 AMQP 수준에서 메시지 보장을 제공한다.

1. 메시지 영속성 (Durability)

# settings.py
CELERY_TASK_SERIALIZER = "json"
CELERY_BROKER_TRANSPORT_OPTIONS = {
    "delivery_mode": 2,  # PERSISTENT (재시작 후에도 보존)
}

2. Publisher Confirm

CELERY_BROKER_TRANSPORT_OPTIONS = {
    "confirm_publish": True,  # 브로커가 수신 확인 후 발행 완료
}

3. Consumer ACK

RabbitMQ는 Worker가 명시적으로 ACK를 보내야 메시지를 큐에서 제거한다.
ACK 전에 Worker가 죽으면 메시지가 자동으로 다른 Worker에 재분배된다.

%% desc: RabbitMQ의 Consumer ACK — Worker 장애 시 메시지 자동 재분배
sequenceDiagram
  participant RABBIT as RabbitMQ
  participant W1 as Worker 1 (장애)
  participant W2 as Worker 2

  RABBIT->>W1: 메시지 전달 (unacked 상태)
  Note over W1: 태스크 실행 중
  W1->>W1: 프로세스 크래시!

  Note over RABBIT: Heartbeat 실패 감지
  RABBIT->>W2: 메시지 재전달 (requeue)
  W2->>W2: 태스크 실행
  W2->>RABBIT: basic.ack
  Note over RABBIT: 메시지 큐에서 제거

Redis vs RabbitMQ 비교

항목 Redis RabbitMQ
프로토콜 Redis Protocol AMQP 0-9-1
설치 복잡도 낮음 중간
메시지 영속성 설정 필요 (AOF/RDB) 기본 지원
ACK 보장 Celery 레벨 AMQP 레벨 (더 강력)
성능 매우 높음 높음
라우팅 단순 Exchange/Binding으로 복잡한 라우팅
관리 UI Redis Commander RabbitMQ Management Plugin
동시 용도 캐시, 세션 등 다용도 전용 메시지 브로커
추천 케이스 빠른 개발, 단순 태스크 메시지 손실 불허, 복잡한 라우팅

Result Backend

브로커와 별도로 Result Backend는 태스크 실행 결과를 저장한다.

# Redis를 Result Backend로
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"

# Django DB를 Result Backend로 (django-celery-results)
CELERY_RESULT_BACKEND = "django-db"

# 결과 만료 시간 (기본 24시간)
CELERY_RESULT_EXPIRES = 3600  # 1시간

결과를 조회하지 않는다면 Result Backend 설정을 생략하거나 무시(ignore)할 수 있다.

@shared_task(ignore_result=True)
def fire_and_forget_task():
    do_something()

연결 풀과 재연결

# settings.py
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True   # 시작 시 연결 실패 재시도
CELERY_BROKER_CONNECTION_MAX_RETRIES = 10
CELERY_BROKER_POOL_LIMIT = 10                       # 연결 풀 크기

참고

  1. Celery Brokers, Celery Docs
  2. Using Redis with Celery, Celery Docs
  3. RabbitMQ, RabbitMQ Docs

관련 글