Contents
1. 왜 필요한가
2. 핵심 개념
3. Result Backend
4. 도구 비교
1. 왜 필요한가
1.1 동기 처리의 문제

블로킹으로 인해 성능, 확장성, 자원 효율 측면에서 한계가 있다.
- Blocking(대기 발생)
- 작업이 끝날 때 까지 다음 작업 수행 불가
- I/O 작업(DB, API)에서 심각
- 성능 저하
- CP가 남아있으나 스레드는 대기
- 낮은 확장성
- 요청 수↑ → 스레드 수↑
- 스레드가 막히면 처리량 한계 도달
- Latency 증가
- 순차 처리 구조
- 하나의 느린 작업이 전체 지연 유발
- 자원 비효율
- 대기 중인 스레드도 메모리 점유
- 컨텍스트 스위칭 비용 증가
- 사용자 경험 저하
- 응답 지연 → 서비스 멈춘 느낌
- 사용자 이탈 가능성 증가
비동기로 백그라운드로 돌리면 되지않나?
1.2 인메모리 비동기

빠르지만, 데이터 유실과 분산 환경에서의 신뢰성 문제가 크다.
- 데이터 유실 위험
- 메모리에만 존재(비영속성)
- 서버 장애/재시작 시 데이터 유실
- 단일 서버 의존성
- 서버 내부에서만 동작
- 다른 서버와 작업 공유 불가
- 분산 환경에 부적합
- 여러 서버 간 작업 동기화 어려움
- 동일 작업 중복 실행 가능성 존재
- 장애 복구 어려움
- 실패한 작업 재처리 불가
- Retry / DLQ 같은 구조 없음
- 처리 보장 없음
- At-least-once / Exactly-once 보장 불가
- 작업 누락 가능
- 확장성 한계
- 트래픽 증가 시 서버 메모리에만 의존
- 수평 확장 구조와 맞지 않음
1.3 왜 Message Broker가 필요한가

인메모리
- 메모리에만 존재하므로, 작업 유실
- 각 서버가 자기 메모리에 존재하는 작업만 처리함
- 작업이 폭주하면 메모리가 가득 차므로 크래시 발생
- 웹 서버와 워커가 같은 프로세스이므로 분리가 불가능함
메시지 브로커
- 브로커에 저장되므로 보존
- 공유 큐 이므로 아무 워커나 처리할 수 있음
- 디스크에 저장하고, 버퍼 역할을 하므로 안정적임
- Producer / Broker / Worker가 분리되어 있으므로 독립적으로 확장 가능하다.
2. 핵심 개념
2.1 메시지 흐름과 용어
Producer
- 작업을 생성해 Broker에 전달(요청) Broker(Queue)
- 작업을 저장하고 Worker에 전달 Worker(Consumer)
- Broker에서 작업을 가져와 실제 처리
2.2 큐 분리와 메시지 라우팅

- 큐 분리
- 작업 종류 별로 큐를 분리하여 독립적으로 처리할 수 있다.
- 이메일 컨슈머가 느려 이메일이 지연되더라도 알림, 인코딩은 빠르게 처리한다.
- 프로듀서가 메시지를 보내면 브로커가 받아서 보관하고, 워커가 가져가서 처리한다.
- 메시지 라우팅
- Producer가 메시지에 힌트(Routing Key)를 보낸다.
review.asset.generatemenu.poster.generate
- Broker가 이를 보고 적절한 큐로 전달(Exchange)한다.
- Producer가 메시지에 힌트(Routing Key)를 보낸다.
- 브로커가 필요한 이유
- 컨슈머가 바쁘면 브로커에 쌓아둔다.
- 컨슈머가 죽으면 브로커에 쌓여있는걸 다시 처리한다.
- 시스템 간 결합도를 감소시켜 독립적인 확장이 가능하다.
2.3 신뢰성 보장 3요소

메시지 큐를 사용하더라도, 유실 / 중복 / 장애 상황을 고려하지 않으면 안정성을 보장할 수 없다.
메시지 큐 + ACK + 멱등성 + 영속성을 만족해야 안정적인 비동기 시스템이 된다.
- ACK(처리 보장)
- Consumer가 처리 완료 후 ACK를 전송한다.
- Broker는 ACK를 받은 후에만 메시지를 삭제한다.
- ACK가 오지 않으면 다른 컨슈머에게 다시 전달한다.
- 따라서 컨슈머가 죽어도 메시지가 유실되지 않음
- 멱등성(중복 처리 방지)
- 같은 메시지를 여러 번 처리해도 결과가 동일하다.
- 중복 메시지를 수신할 수 있지만, 처리 전에 이미 처리했는지를 확인 한다.
- 영속성(데이터 유실 방지)
- 브로커가 메시지를 디스크에 저장한다.
- 브로커가 재시작되더라도 메시지가 살아있음
2.4 멱등성

이미 처리한 요청인지 먼저 확인한다.
- 적용 방법
- Redis에 처리 완료한 ID를 저장한다.
- DB에 unique key / 상태값 체크한다.
- 멱등키(idempotency key)를 활용한다.
- 효과
- 중복 방지
2.5 실패 처리: 재시도와 DLQ

메시지는 실패할 수 있기 때문에,
재시도 전략과 최종 실패 처리(DLQ)가 필요하다.
- 재시도
- 바로 재시도 하지 않고, 지수 백오프 방식으로 간격을 늘려간다.
- 외부 시스템 장애(API, DB), 일시적인 오류(네트워크, 타임아웃) 발생 시 빠르게 재시도 하면 실패할 확률이 높음
- 간격을 늘려 회복 시간을 확보한다.
- DLQ
- 재시도 한도 초과 → 별도 큐로 이동
- 자동 처리 중단
- 실패 메시지 보관
- 개발자가 로그 확인하고 분석 후 재처리
Redis Streams를 브로커로 사용했고, AbstractRedisStreamMessage 메시지에
retyrCount: 재시도 횟수nextRetryAt: 다음 재시도 시각expireAt: TTL(만료 시각)retyrFailReason: 실패 사유 정보를 추가하여 지수 백오프 재시도 로직과 DLQ를 애플리케이션 레벨에서 직접 구현해 신뢰성을 확보 했다.(작업 별로 TTL, MAXLEN, retryCount를 다르게 가져감 / 구현 복잡도, 운영 부답 증가)
3. Result Backend
3.1 Result Backend

비동기 작업은 요청과 처리가 분리되기 때문에,
작업의 상태와 결과를 별도로 저장해야 한다.
- 비동기 구조의 한계
- 요청 시 바로 결과를 알 수 없음
- 작업 완료 여부 확인이 필요함
- 상태 추적
- PENDING: 작업 큐에 들어감
- STARTED: 워커가 작업을 처리 중
- SUCCESS: 작업 완료(성공)
- FAILURE: 작업 실패
- 동작 흐름
- Client: 작업 요청
- Producer: 메시지 큐 전달
- Worker: 작업 수행
- Result Backend: 상태/결과 저장
- Client: 상태 조회
3.3 Task 상태 조회 - AsyncResult

- taksID 기반 조회
- 현재 상태 + 결과 반환
- Polling 방식으로 결과 확인
3.4 결과 저장과 TTL

Result Backend는 작업의 상태와 결과를 저장하지만,
모든 결과를 영구적으로 보관할 필요는 없다.
- Redis
- 빠른 조회(Low Latency)
- 상태 조회는 빈번하게 발생
- ms 단위 응답 필요
- 결과 재조회 필요 없음
- key 단위 만료 자동 처리(TTL)
- 별도 정리 로직 불필요
- 빠른 조회(Low Latency)
- DB
- 결과를 장기 보관해야 할 때
- 영상, 주문, 결제 등 중요한 데이터
3.5 Celery 설정 - Broker + Backend + TTL

각 역할과 데이터 특성에 따라 TTL 전략을 다르게 가져간다.
같은 Redis, 다른 DB 번호
- DB 0 → Broker
- DB 1 → Result Backend
TTL 전략
- 불필요한 결과
- 짧은 TTL 설정
- 자동 삭제(TTL)
- 중요한 결과
- TTL을 길게 설정
- 필요 시 장기 조회 가능
- 반영구(진짜 중요한) 데이터
- 별도 DB에 저장
- Redis X
- 필요 없는 결과는 TTL로 지운다.
- 중요한 결과는 TTL을 길게 가져간다.
- 진짜 중요하면 별도 DB에 저장함
- Result Backend는 조회용으로만 사용
3.6 결과 조회 패턴

비동기 작업은 즉시 결과를 알 수 없기 때문에,
작업 상태를 조회하는 별도의 방식이 필요하다.
- Polling(대부분 이 방식 사용)
- 구현이 단순하지만, 불필요한 요청과 지연이 발생하는 방식
- 리소스 낭비(계속 요청 → Long Polling)
- Polling 간격 만큼 응답 지연
- 트래픽 증가
- 클라이언트 수 많아질수록 요청 폭증
- Callback(Webhook)
- 작업 완료 시 서버 → 클라이언트로 요청 전송
- 불필요한 요청 없음
- 즉시 결과 전달 가능
- 구현 복잡
- 클라이언트가 API 서버를 가져야 함
- 보안/인증 처리 필요
- WebSocket
- 클라이언트와 서버가 연결을 유지
- 작업 완료시 서버가 실시간으로 결과 ㅔush
- 연결 유지 비용 발생
- 서버 구현 복잡
3.7 EatDa에서의 Result Backend
실제 프로젝트에서는 다음과 같은 방식으로 비동기 결과를 처리했다.
- FastAPI → 영상의 임시 URL 반환
- Spring 서버 → 해당 URL에 접근하여 영상 다운로드
- EC2 내부에 영상 저장
- MySQL → 영상 접근 URL(public IP 기반) 저장
Result Backend로 MySQL를 선택한 이유:
- 영상 결과는 반영구적으로 보관해야 하는 데이터
- 영상은 한번 생성되면 지속적으로 조회됨
- TTL 기반으로 삭제되는 구조는 부적합함
- 따라서 Redis 보다 영구 저장이 가능한 DB가 적절하다고 판단함
한계:
- API 서버 부하 증가
- 영상 다운로드는 네트워크 I/O가 큰 작업
- Spring 서버가 요청 처리 + 파일 처리 모두 수행
- 확장성 한계
- 트래픽 증가 시 서버가 모든 작업을 직접 처리
- 수평 확장 시에도 동일한 문제 반복
- 저장 구조의 한계(영상 데이터 Ec2 내부 저장)
- 서버에 종속적인 구조
- 일반적으로는 S3와 같은 외부 스토리지 사용
개선 방향:
- 무거운 I/O 작업을 lambda로 분리
- Fast API → 임시 URL 반환
- Lambda(영상은 15초 내외 임)
- 영상 다운로드(Streaming 방식)
- S3 업로드
- MySQL → 최종 URL 저장
효과:
- 서버 부하 분리
- API 서버는 요청 처리에 집중
- 무거운 작업은 별도 실행 환경에서 처리
- 자동 확장
- 요청 증가 시 Lambda가 자동으로 확장
- 별도 인프라 관리 필요 없음
3.8 워크플로우: Chain & Chord

메시지 큐 기반 비동기 처리에서는
작업을 흐름(Workflow)으로 구성할 수 있다.
- Chain(순차 실행)
- 앞 작업의 결과를 다음 작업이 이어받아 실행
- 작업이 순서대로 실행됨
- 결과가 다음 단계의 입력으로 전달됨
- 데이터 가공 파이프라인 / 단계별 처리(검증 → 변환 → 저장)에 사용
- Chord(병렬 실행 + 집계)
- 여러 작업을 병렬로 실행하고, 모두 끝난 후 다음 단계 실행
- 여러 작업을 동시에 실행
- 하나라도 실패/지연 → 다음 단계 대기
- 여러 API 호출 결과 집계 / 이미지·영상 병렬 처리 후 결과 합치기에 사용
작업 상태를 추적해야 할 때 Result Backend를 사용한다.
4. 도구 비교
4.1 Broker, 어떤 도구를 쓸까

메시지 큐는 안전성, 라우팅, 확장성 요구사항에 따라 선택한다.
- Redis(List 기반 Queue)
- 캐시/데이터 저장소로 이미 사용 중이면 쉽게 도입할 수 있음
- Broker 방식: List(
LPUSH/BPROP) - 빠르고 단순한 구조
- 별도 인프라 없이 사용 가능
- 메시지 안전성 낮음
- 소비 시 즉시 제거 → 장애 시 유실 가능
- 라우팅 기능 부족
- 복잡한 워크플로우 처리 어려움
- Redis Streams
- Redis 기반 로그형 메시지 큐
- Broker 방식: Stream + Consumer Group
- 메시지가 삭제되지 않고 로그 형태로 저장
- Consumer Group 기반 병렬 처리
- ACK 기반 처리 및 재처리 가능
- List보다 높은 신뢰성 제공
- 라우팅 기능 제한적
- PENDING 메시지 관리 필요(운영 복잡도 증가)
- RabbitMQ/Kafka 수준의 메시징 기능은 아님
- RabbitMQ
- 메시지 큐 전용 시스템
- Broker 방식: Exchange + Queue 구조
- 메시지 안전성 높음
- ACK 기반 처리
- 유연한 라우팅 지원
- 다양한 메시징 패턴 제공
- 별도 운영 필요
- Result Backend 별도 구성 필요
4.2 Exchange 타입 비교

메시지를 어떻게 분배할 것인가
- Direct
- 라우팅 키 정확히 일치
review.asset.generatemenu.poster.generate
- 1:1 매칭
- 라우팅 키 정확히 일치
- Topic
- 패턴별로 매칭
user.*order.*
- 유연한 라우팅
- 패턴별로 매칭
- Fanout
- 모든 큐에 전달
- 브로드캐스트
- 모든 큐에 전달
4.3 시나리오별 추천

4.4 Redis 안전성 설정 - 3가지 핵심

Redis는 기본적으로 메시지 큐 기능이 제한적이기 때문에,
ACK, Retry, Durability를 직접 보완해야 한다.
- ACK(처리 보장)
- RPOPLPUSH 패턴으로 구현
- 메시지를 처리 전에 작업 중 큐로 이동
- 처리 완료 후 삭제
- 워커가 죽어도 메시지 복구 가능
- RPOPLPUSH 패턴으로 구현
- Retry
- 애플리케이션 레벨에서 구현
- 실패 시 재시도
- 일정 횟수 초과 시 DLQ로 이동
- 애플리케이션 레벨에서 구현
- Durabillity
- AOF / RDB 설정
- Redis 데이터를 디스크에 저장
- 재시작 시 데이터 복구
- AOF / RDB 설정
4.5 메시지 큐 vs Kafka

Kafka는 메시지 큐가 아니라
이벤트 스트리밍 플랫폼(Event Streaming Platform) 이다.
- 메시지 큐(작업 처리 중심)
- 메시지는 한 번 처리되면 사라짐
- Consumer가 처리하면 → 삭제
- 하나이 작업을 하나의 Consumer가 처리
- 비동기 작업 처리 및 시스템 간 결합도 감소가 목적임
- 이메일 발송 / 영상 인코딩 / 주문 처리
- 이벤트 스트리밍(데이터 흐름 저장 + 재사용)
- 메시지가 로그 형태로 계속 저장됨
- Consumer가 읽어도 데이터 유지
- 여러 Consumer가 독립적으로 소비 가능
- 데이터 수짐 및 분석, 이벤트 기반 아키텍처, 데이터 파이프라인 구축이 목적임
- 로그 수집 / 실시간 데이터 분석 / 추천 시스템