한 줄 핵심

Redis와 Celery는 완전히 다른 소프트웨어다.

  • Redis = 인메모리 데이터 서버(메시지를 보관하는 장소)
  • Celery = 분산 작업 큐 프레임워크(메시지를 꺼내서 실행하는 주체)

Redis는 단독으로도 쓸 수 있지만, Celery는 반드시 Redis나 RabbitMQ 같은 **Message Broker(메시지 중개자)**를 필요로 한다. 즉 Celery가 Redis를 빌려 쓰는 관계임.


포함 관계와 전체 구도

┌──────────────────────────────────────────────────────┐
│                     전체 구조                          │
│                                                       │
│    FastAPI          Redis             Celery          │
│    (웹 서버)        (메시지 보관함)      (작업 실행기)     │
│                                                       │
│    "주문 접수"   →  "주문서 보관"    →   "요리 실행"      │
│                                                       │
│    Python           C 언어 기반        Python          │
│    웹 프레임워크      인메모리 DB         작업 큐         │
│                                       프레임워크        │
└──────────────────────────────────────────────────────┘
관점RedisCelery
정체인메모리 데이터 서버분산 작업 큐 프레임워크
구현 언어C로 작성된 독립 서버Python 라이브러리
역할메시지를 보관하는 장소메시지를 꺼내서 실행하는 주체
비유우체통우체부
단독 사용가능(캐시/DB/잠금 등)불가. 반드시 Redis/RabbitMQ 같은 Broker 필요

Redis의 본질

Redis는 **인메모리 데이터 구조 서버(In-Memory Data Structure Server)**다. “캐시”는 Redis의 여러 기능 중 하나일 뿐임.

Redis = 초고속 인메모리 데이터 구조 서버
        │
        ├─ 1. 캐시(Cache)
        │     "API 응답 24시간 저장해두고 재사용"
        │
        ├─ 2. 메시지 큐(Message Queue)        ← Celery가 쓰는 기능
        │     "할 일 목록을 줄 세워서 Worker에게 전달"
        │
        ├─ 3. Pub/Sub(실시간 메시징)
        │     "채팅방처럼 구독자에게 메시지 브로드캐스트"
        │
        └─ 4. 상태 저장소(State Store)
              "세션, 잠금(Lock), 카운터 등"

1. 메시지 큐(Message Queue)로서의 Redis

Redis에는 LPUSH / BRPOP이라는 리스트 자료구조(List Data Structure) 명령이 있다. 이게 곧 큐(Queue, 선입선출 대기열) 역할을 함.

[생산자 - FastAPI]                 [Redis 리스트]             [소비자 - Worker]

사용자 A 요청 ──LPUSH──→  ┌─────────────────┐
                          │ 작업 C (대기)    │
사용자 B 요청 ──LPUSH──→  │ 작업 B (대기)    │  ──BRPOP──→  Worker가 꺼내서 실행
                          │ 작업 A (대기)    │
사용자 C 요청 ──LPUSH──→  └─────────────────┘
                           번호표 대기열과 동일
명령의미비유
LPUSH리스트 왼쪽에 작업 넣음번호표 발급
BRPOP리스트 오른쪽에서 작업 꺼냄 (Blocking, 작업 올 때까지 대기)다음 번호 호출

Celery와 Redis의 관계

Celery = 작업 분배 프레임워크 (매니저)
Redis  = 작업 대기열을 저장하는 장소 (게시판)

┌──────────┐       ┌──────────┐       ┌──────────┐
│  FastAPI │──→──→│  Redis   │──→──→│  Celery  │
│  "작업    │ 등록  │  "대기열 │ 꺼냄  │  Worker  │
│   요청"   │       │   보관"  │       │  "실행"  │
└──────────┘       └──────────┘       └──────────┘

Celery는 Redis 말고 RabbitMQ 같은 다른 Broker도 쓸 수 있다. Redis는 **교체 가능한 부품(Message Broker)**임. 다만 이미 캐시로 Redis를 쓰는 프로젝트가 많아 큐까지 Redis 하나로 해결하는 경우가 흔하다.


2. Pub/Sub (Publish / Subscribe, 발행-구독)

“방송국과 라디오” 모델이다.

Publisher (방송국)                    Subscriber (라디오)
  │                                    │
  │   "대치동 보고서 완성됨!"            │
  │──── PUBLISH ────→ [Redis 채널] ────→ 듣고 있는 모든 구독자에게 전달
                      "report_done"     │
                                        ├─ 웹소켓 서버 → 사용자 브라우저에 알림
                                        ├─ 이메일 서버 → 발송 트리거
                                        └─ 로그 서버 → 기록

메시지 큐와 뭐가 다른가

둘 다 “메시지를 보낸다”는 점은 같지만 배달 방식이 완전히 다르다.

[메시지 큐 - 택배]
                                    Worker 1 ← 작업 A (1명만 받음)
FastAPI → Redis 리스트 → BRPOP →   Worker 2 ← 작업 B (1명만 받음)
                                    Worker 3 ← 작업 C (1명만 받음)

→ 작업 1개를 Worker 1명만 가져감 (1:1 배달)


[Pub/Sub - 라디오 방송]
                                    구독자 1 ← "보고서 완성!" (전원 수신)
Publisher → Redis 채널 → PUBLISH → 구독자 2 ← "보고서 완성!" (전원 수신)
                                    구독자 3 ← "보고서 완성!" (전원 수신)

→ 메시지 1개를 모든 구독자가 동시에 받음 (1:N 방송)
관점메시지 큐(List)Pub/Sub
비유택배(수령인 1명)라디오 방송(청취자 전원)
수신자Worker 1개만 꺼내감구독자 전부 수신
보관꺼낼 때까지 Redis에 남아 있음보관 안 함. 그 순간 안 듣고 있으면 유실
용도작업 분배(무거운 처리)이벤트 알림(가벼운 통지)

실제 프로젝트 적용 예

현재: 사용자가 GET /status/{job_id}로 "끝났나?" 반복 조회 (Polling)

  브라우저:  끝났나? → 아니오
             끝났나? → 아니오
             끝났나? → 아니오      ← 30분간 반복... 서버 부담
             끝났나? → 네!

Pub/Sub 적용 후: 끝나면 서버가 알려줌

  브라우저:  "report_done" 채널 구독 후 대기
  서버:      보고서 완성 → PUBLISH "report_done" "작업 완료!"
  브라우저:  알림 수신 → 결과 표시  ← 불필요한 반복 조회 제거

한 줄 정리

Pub/Sub은 “작업을 시키는 것”이 아니라 **“일어난 일을 알리는 것”**이다. 메시지 큐가 택배(1명 배달)라면, Pub/Sub은 라디오 방송(전원 전달)임.


3. Redis의 상태 저장소(State Store) 기능

세션, 잠금, 카운터처럼 **“잠깐 기억해둬야 하는 것들”**을 처리한다.

3-1. 세션(Session)

“이 사용자가 로그인한 상태인지” 기억하는 것.

사용자 로그인 → 서버가 세션 토큰 발급 → 어딘가에 저장해야 함

[저장 방식 비교]
DB에 저장:     로그인 확인할 때마다 DB 조회 → 느림
Redis에 저장:  메모리에서 즉시 조회 → 빠름 (만료 시간 설정 가능)

Redis:  { "session:abc123": "user_id=42, login_time=..." }  ← TTL 30분 후 자동 삭제

3-2. 잠금(Lock)

**“지금 누가 쓰고 있으니 건드리지 말라”**는 표시다.

사용자 A: "강남구 대치동" 보고서 생성 요청
사용자 B: "강남구 대치동" 보고서 생성 요청 (동시에)

Lock 없이:
  Worker 1 → 대치동 보고서 생성 시작 (30분)
  Worker 2 → 대치동 보고서 생성 시작 (30분)  ← 동일 작업 중복 실행. 자원 낭비

Lock 있으면:
  Worker 1 → Redis에 "대치동_보고서" Lock 획득 → 실행
  Worker 2 → Lock 확인 → "이미 누가 하고 있네" → 대기 or 스킵
Redis:  SET "lock:대치동_보고서" "worker1" EX 1800  ← 30분간 잠금, 자동 해제

3-3. 카운터(Counter)

**“몇 번 일어났는지”**를 세는 것. Rate Limit(요청 횟수 제한)에 자주 쓰인다.

[Rate Limit 예시]
사용자가 API를 1분에 10번까지만 호출 가능하게 제한할 때:

요청 1  → Redis: INCR "rate:user42" → 1   ✅ 통과
요청 2  → Redis: INCR "rate:user42" → 2   ✅ 통과
  ...
요청 11 → Redis: INCR "rate:user42" → 11  ❌ 차단

1분 후 → 키 자동 만료 → 카운터 리셋

INCR은 Redis의 원자적(Atomic, 중간에 끊기지 않는) 증가 명령이다. 동시에 100개 요청이 와도 숫자가 꼬이지 않음.

왜 DB가 아니라 Redis에 넣나

세 가지의 공통점: 수명이 짧고, 빈번하게 읽고 쓰며, 정확해야 함.

관점DB(PostgreSQL 등)Redis
저장 매체디스크(Disk)메모리(RAM)
속도ms(밀리초) 단위μs(마이크로초) 단위, 약 1000배 빠름
자동 만료(TTL)직접 구현해야 함TTL 설정 한 줄
원자적 연산트랜잭션 필요INCR, SETNX 등 내장
적합한 데이터영구 보관(회원정보, 보고서)휘발성(세션, 잠금, 카운터)

한 줄 정리

세션/잠금/카운터 모두 “잠깐만 빠르게 기억해야 하는 것”이다. DB에 넣기엔 과하고 변수에 넣기엔 서버 재시작하면 사라지니, 그 중간 지점인 Redis에 저장함.


Celery 기본 구조

구성 요소

[FastAPI / Django]           [Broker]            [Celery Worker]
     │                        (Redis)                 │
     │                          │                     │
     │  ── send_task() ──→      │                     │
     │                      작업 대기열                 │
     │                          │  ── 꺼냄 ──→        │
     │                          │                     │  작업 실행
     │                          │                     │  (예: 보고서 생성)
     │                          │                     │
     │                     [Result Backend]           │
     │  ← 결과 조회 ──        (Redis)   ← 결과 저장 ── │
  • Broker: 작업 메시지를 대기열에 보관(Redis 리스트)
  • Worker: 별도 프로세스로 상주하면서 BRPOP으로 작업을 꺼내 실행한다
  • Result Backend: 실행 결과를 저장하는 곳(Redis 또는 DB). 웹 서버가 나중에 결과를 조회할 때 사용

왜 Celery가 필요한가

FastAPI/Django는 요청-응답 주기가 짧아야 한다 (보통 수백 ms 이내). 보고서 생성 같은 30분짜리 작업을 동기로 처리하면 HTTP 타임아웃이 나고 다른 사용자도 못 받는다.

[동기 처리 - 나쁨]
요청 → 30분 대기 → 응답        ← 브라우저 타임아웃, 서버 블로킹

[Celery 비동기 처리 - 좋음]
요청 → job_id 즉시 반환        ← 응답 0.01초
       │
       └─ 백그라운드 Worker가 30분짜리 작업 처리
       └─ 완료 후 결과 저장 or Pub/Sub으로 알림

최소 코드

# celery_app.py
from celery import Celery
 
app = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
)
 
@app.task
def generate_report(region: str) -> str:
    # 무거운 작업
    return f"{region} 보고서 완료"
 
# producer (FastAPI 쪽)
job = generate_report.delay("대치동")   # Redis 큐에 등록
print(job.id)                           # 사용자에게 job_id 반환

큐잉에 대한 흔한 오해

  • “큐잉 = 느려지는 것”이 아니다
  • 큐잉은 Worker 수로 동시 처리량(Throughput)을 제어하는 장치임
  • Worker 1개면 순차 처리, 10개 띄우면 10배 동시 처리

직접 확인

# Redis 서버 띄우기 (Docker)
docker run -p 6379:6379 redis:7
 
# redis-cli 접속해서 리스트 동작 확인
docker exec -it <container_id> redis-cli
> LPUSH queue "job1"
> LPUSH queue "job2"
> BRPOP queue 0   # 오른쪽 끝에서 꺼냄, 없으면 무한 대기

한마디 요약

Redis는 “캐시 전용”이 아니라 인메모리 데이터 구조 서버다. 그 자료구조 중 리스트(List)가 곧 메시지 큐 역할을 함. Celery는 그 큐에서 작업을 꺼내 Worker에 분배하는 작업 실행 프레임워크이고, 둘은 완전히 별개이며, Celery가 Redis를 우체통처럼 빌려 쓰는 관계다.

관련 노트