1. 시작점: 우리가 쓰는 것
CGV 대기열 시스템의 실시간 통계 전달 구조 (→ websocket 8절 참고):
QueueProcessor (Leader Pod, 2초마다)
│
├─ Redis ZCARD × 2 → totalWaiting, totalActive
│
└─ Redis PUBLISH "queue:notifications" ← Redis Pub/Sub (Pod 간 전파)
│
├─ Pod 1: MessageListener → SimpleBroker → Pod 1의 브라우저들
├─ Pod 2: MessageListener → SimpleBroker → Pod 2의 브라우저들
└─ Pod 3: MessageListener → SimpleBroker → Pod 3의 브라우저들
↑
SimpleBroker (Pod 내부 → 브라우저 전달)
여기서 Redis Pub/Sub과 SimpleBroker — 이 두 가지가 메시징 시스템이다.
그런데 세상에는 RabbitMQ, Kafka, Kinesis 같은 것들도 있다.
이것들이 뭐고, 왜 우리는 안 쓰는지, 어떤 상황에서 쓰는지를 이 문서에서 정리한다.
2. 메시징 시스템이란
한 줄 정의
서비스가 직접 호출하지 않고, 중간 시스템을 통해 메시지를 주고받는 구조.
[직접 호출]
서비스 A → 서비스 B ← B가 죽으면 A도 실패. A가 B를 기다려야 함.
[메시징 시스템]
서비스 A → [중간 시스템] → 서비스 B
↓
B가 죽어도 메시지 보관 가능
A는 넣기만 하고 바로 다음 일
3가지 패턴
"메시징 시스템"은 하나가 아니다. 동작 방식이 근본적으로 다른 3가지 패턴이 있다.
메시징 시스템
├── ① Pub/Sub — 라이브 방송: 지금 보는 사람만 봄
├── ② Message Queue — 택배: 한 명이 받으면 끝
└── ③ Event Streaming — 유튜브: 녹화됨, 나중에 봐도 됨
| Pub/Sub | Message Queue | Event Streaming | |
|---|---|---|---|
| 비유 | 라이브 방송 | 택배 | 유튜브 |
| 메시지 보관 | ❌ 즉시 소멸 | 소비하면 삭제 | ✅ 보관 (일/주 단위) |
| 수신자 | 지금 연결된 모든 구독자 | 1명만 받음 | 독립적 Consumer Group들 |
| 전달 보장 | ❌ fire-and-forget | ✅ ACK + 재전송 | ✅ offset 기반 |
| 다시 읽기 | ❌ | ❌ | ✅ offset 되감기 |
| 대표 | Redis Pub/Sub | RabbitMQ, SQS | Kafka, Kinesis |
| CGV | ✅ 사용 중 | ❌ | ❌ |
"메시지큐"라는 말을 전체 메시징 시스템의 이름으로 쓰는 경우가 많은데, 정확히는 3가지 중 하나일 뿐이다.
"Kafka는 메시지큐가 아니라 이벤트 스트리밍"이다.
각 패턴에 속하는 기술들
| 패턴 | 기술 | 특징 |
|---|---|---|
| Pub/Sub | Redis Pub/Sub | Redis 내장. 가장 가벼움. CGV 사용 |
| SimpleBroker | Spring Boot 내장. Pod 내부 전용. CGV 사용 | |
| MQTT | IoT 센서 데이터 전송에 주로 사용 | |
| Socket.IO broadcast | Node.js 실시간 채팅에 많이 사용 | |
| Message Queue | RabbitMQ | 가장 대표적. Exchange 라우팅, ACK, DLQ |
| Amazon SQS | AWS 매니지드 큐. 설치 불필요 | |
| ActiveMQ | Java 전통 MQ. 레거시 시스템에서 많이 봄 | |
| Celery | Python 비동기 작업 큐 (Redis/RabbitMQ를 백엔드로 사용) | |
| Event Streaming | Apache Kafka | 가장 대표적. 직접 운영. 자유도 최고 |
| Amazon Kinesis | Kafka의 AWS 버전. 운영 불필요 | |
| Apache Pulsar | Kafka 경쟁자. MQ + Streaming 둘 다 지원 | |
| Redis Streams | Redis 내장 경량 스트리밍. Pub/Sub과 별개 기능 |
3가지 패턴의 경계 — 완전히 칼로 자르듯 나뉘진 않는다
핵심 설계 의도는 확실히 다르다. 하지만 실제 도구는 경계를 넘나드는 기능이 있다:
RabbitMQ의 fanout exchange → 모든 큐에 복사 → Pub/Sub처럼 동작
Kafka의 단일 Consumer Group → Partition 나눠 처리 → MQ처럼 동작
Redis Streams (Pub/Sub 아님) → 메시지 보관 + offset → 경량 Kafka처럼 동작
그래서 "이 도구 = 이 패턴" 공식이 아니라, 주된 설계 목적으로 구분하는 것이다:
Pub/Sub의 본질: "지금 전달하고 잊어버려"
Message Queue의 본질: "반드시 한 번 전달해"
Event Streaming의 본질: "일어난 일을 기록하고, 누구든 읽게 해"
3. 패턴 1: Pub/Sub — Redis Pub/Sub, SimpleBroker
핵심 개념
[라이브 방송]
방송국 → 전파 송출
→ 지금 TV 켜놓은 사람: 봄 ✅
→ TV 꺼놓은 사람: 못 봄 ❌
→ 녹화? 안 됨. 다시 보기? 없음.
"지금 연결된 사람에게 즉시 전달. 보관 없음. 재전송 없음."
가장 가볍고 빠른 메시징 패턴.
Redis Pub/Sub — 내부 동작 (파드-> 레디스 구독, 파드 간 브로드캐스트)
1. 구독 등록
SUBSCRIBE queue:notifications
→ Redis 내부에 기록: { "queue:notifications" → [Pod1, Pod2, Pod3] }
2. 메시지 발행
PUBLISH queue:notifications '{"totalWaiting":95000,"totalActive":4800}'
→ Redis가 해당 채널의 모든 구독자에게 TCP로 즉시 전송
→ 보낸 즉시 Redis 메모리에서 사라짐 (저장 안 함)
3. 구독자가 없으면?
→ 메시지는 그냥 버려짐. 아무 일도 안 일어남.
[왜 이렇게 가벼운가]
- 메시지 저장 안 함 → 디스크 I/O 없음
- ACK 안 함 → 왕복 통신 없음
- offset 추적 안 함 → 상태 관리 없음
- 그냥 "연결된 클라이언트에 TCP로 쏘기" → 끝
→ 지연시간: 수십 마이크로초(μs)
→ Redis가 이미 있으면 추가 비용 $0
SimpleBroker — 내부 동작 (클라이언트 -> 파드 구독, 파드-클라이언트들 간 푸시)
- STOMP = WebSocket 위에서 돌아가는 메시징 프로토콜 (Simple Text Oriented Messaging Protocol)
- SimpleBroker = Spring Boot 내장 경량 Pub/Sub 브로커, STOMP 프로토콜의 구현체
Spring Boot가 내장하고 있는 초경량 Pub/Sub 브로커.
별도 설치 없음. @EnableWebSocketMessageBroker로 활성화.
내부 구조:
Pod 메모리 안에 Map<String, List<WebSocketSession>>
{ "/topic/queue/1" → [브라우저A, 브라우저B, 브라우저C] }
{ "/topic/queue/2" → [브라우저D] }
코드:
simpMessagingTemplate.convertAndSend("/topic/queue/1", data)
→ Map에서 "/topic/queue/1"의 세션 목록 찾기
→ 각 세션에 WebSocket으로 전송
제한: 같은 Pod 안의 WebSocket 세션만 관리.
→ 다른 Pod의 브라우저에는 전달 불가.
왜 2개를 같이 쓰나
SimpleBroker만 쓰면:
Pod 1의 브라우저들만 업데이트됨. Pod 2~10은 못 받음. (❌)
Redis Pub/Sub만 쓰면:
Pod 간 전파는 되지만, 브라우저에 직접 전달하는 건 Redis가 못 함. (❌)
(Redis → Pod까지만. Pod → 브라우저는 별개.)
둘을 합치면:
Redis Pub/Sub: Pod 간 전파 (서버 ↔ 서버)
SimpleBroker: Pod → 브라우저 (서버 → 클라이언트)
→ 모든 Pod의 모든 브라우저가 업데이트됨. (✅)
Pub/Sub의 특성 정리
장점:
- 가장 가볍고 빠름 (μs 단위)
- 구현 단순 (PUBLISH/SUBSCRIBE 두 명령)
- Redis가 이미 있으면 추가 인프라 $0
한계:
- 메시지 보관 안 됨 → 유실되면 끝
- 전달 보장 안 됨 → 구독자가 받았는지 모름
- 다시 읽기 불가 → 과거 메시지 조회 불가
적합한 상황:
- 주기적으로 갱신되는 실시간 데이터 (유실돼도 곧 새 데이터가 옴)
- CGV 대기열 통계: 2초마다 갱신, 유실돼도 2초 후 복구, API 폴링 fallback도 있음
4. 패턴 2: Message Queue — RabbitMQ
핵심 개념
[택배]
보내는 사람 → 택배 상자에 넣음 → 택배 기사가 꺼내서 배달
→ 상자에서 사라짐
→ 한 명만 받음
→ 배달 확인 (수취인 서명)
Producer → [Queue] → Consumer
↓
한 메시지를 한 Consumer만 받음
받으면 큐에서 삭제
Consumer가 ACK 안 보내면? → 재전송
"작업을 정확히 한 번 전달하고, 처리 확인까지 받는다."
Pub/Sub과 근본적으로 다른 점: 전달 보장. -> Consumer에서부터 Queue로 Ack 응답(잘 받았습니다.)
RabbitMQ — 내부 구조
Producer → Exchange → Binding Rule → Queue → Consumer → ACK
↓
"이 메시지를 어떤 큐에 넣을지" 라우팅 결정
Exchange 종류:
┌──────────┬─────────────────────────────────────────┐
│ Direct │ routing key 정확히 일치하는 큐에 전달 │
│ │ key="payment" → payment-queue에만 │
├──────────┼─────────────────────────────────────────┤
│ Fanout │ 바인딩된 모든 큐에 복사 (1:N) │
│ │ → 사실상 Pub/Sub처럼 동작 │
├──────────┼─────────────────────────────────────────┤
│ Topic │ 패턴 매칭 (order.* → order.created, │
│ │ order.cancelled 모두 매칭) │
└──────────┴─────────────────────────────────────────┘
3단계 전달 보장 — RabbitMQ가 다른 이유
메시지가 유실될 수 있는 모든 구간에 안전장치가 있다:
구간 1: Producer → Exchange
Publisher Confirm: Exchange가 "잘 받았다" 응답
→ 네트워크 유실 방지
구간 2: Exchange → Queue
persistent 설정: 디스크에 저장
→ RabbitMQ 서버 재시작해도 메시지 보존
구간 3: Queue → Consumer
Manual ACK: Consumer가 처리 완료 후 "잘 처리했다" ACK 전송
→ ACK 안 오면? → 다른 Consumer에게 재전송
→ 재전송 반복 실패? → Dead Letter Queue(DLQ)에 보관
Pub/Sub과 비교: Redis Pub/Sub은 이 3가지 중 하나도 없다. 보내면 끝. 받았는지 모름.
CGV에서 RabbitMQ를 안 쓰는 이유
RabbitMQ의 핵심 가치: "메시지를 정확히 한 번 전달 보장"
CGV 상황:
- 2초마다 새 통계가 옴 → 이전 메시지를 재전송할 이유가 없음
- 유실돼도 2초 후 복구 → 전달 보장 불필요
- ACK 주고받기? → 2초 뒤면 어차피 새 데이터인데 확인할 이유 없음
→ RabbitMQ의 "전달 보장"이라는 핵심 기능을 쓸 일이 없음
→ 별도 서버 운영 부담만 추가됨
→ 안 쓰는 게 맞음
RabbitMQ가 적합한 상황
✅ 결제 처리: "이 결제를 정확히 한 번 처리해야 한다"
→ 유실 = 돈 유실. ACK + 재전송 필수.
✅ 이메일 발송: "이 이메일을 반드시 보내야 한다"
→ 이메일 서버가 느려도 큐에 쌓아두고 천천히 처리 (백프레셔)
✅ 주문 확인: "주문 데이터가 절대 유실되면 안 된다"
→ 실패 시 DLQ에 보관 → 나중에 수동 처리
5. 패턴 3: Event Streaming — Kafka
핵심 개념
[유튜브]
유튜버 → 영상 업로드 → 서버에 저장됨
→ 구독자 A가 시청 (재생바: 3분까지 봄)
→ 구독자 B가 시청 (재생바: 1분까지 봄)
→ 새 구독자 C가 가입 → 처음부터 시청 가능
→ 7일 후 자동 삭제 (retention)
영상은 시청해도 사라지지 않는다. 재생바(offset)만 움직인다.
Producer → [Topic] → 메시지 저장됨 (로그 파일처럼 쌓임)
→ Consumer Group A: offset 5까지 읽음
→ Consumer Group B: offset 2까지 읽음
→ 새 Consumer Group C: offset 0부터 시작 가능
→ 메시지는 보관 기간(기본 7일) 동안 삭제되지 않음
Kafka는 메시지큐가 아니다
많은 사람이 Kafka를 "빠른 메시지큐"로 이해함 → 틀렸음.
Kafka의 정체: 분산 커밋 로그 (Distributed Commit Log)
메시지큐(RabbitMQ): "이 택배 배달해줘" → 배달 완료 → 택배 사라짐
Kafka: "이 사건이 일어났다" → 기록됨 → 누구든 돌려볼 수 있음
RabbitMQ는 "작업 분배" 도구.
Kafka는 "이벤트 기록 + 전파" 도구.
같은 카테고리가 아니다.
RabbitMQ vs Kafka — 직접 비교
같은 "주문 생성" 이벤트가 발생했을 때:
RabbitMQ:
Producer → [Queue] → 결제 서비스가 꺼냄 → ACK → 큐에서 삭제
→ 재고 서비스는? 못 봄. 메시지가 이미 사라짐.
Kafka:
Producer → [order-topic]
→ 결제 서비스가 읽음 (Consumer Group A, offset 0→1)
→ 재고 서비스가 읽음 (Consumer Group B, offset 0→1)
→ 알림 서비스가 읽음 (Consumer Group C, offset 0→1)
→ 분석 서비스가 읽음 (Consumer Group D, offset 0→1)
→ 메시지는 안 사라짐. 각자 자기 offset만 관리.
→ 서비스를 10개 추가해도 Kafka는 상관없음.
| RabbitMQ | Kafka | |
|---|---|---|
| 소비하면 | 삭제됨 | 남아있음 |
| 받는 사람 | 1명 | 여러 Group이 각자 |
| 다시 읽기 | 불가 | 가능 (offset 되감기) |
| 전달 보장 | ACK + 재전송 | offset 기반 |
| 핵심 가치 | "정확히 전달" | "기록 + 독립 소비" |
| 비유 | 등기우편 (배달 확인) | 유튜브 (각자 재생바) |
Kafka만 할 수 있는 것
위 비교에서 본 독립 소비 외에 2가지가 더 있다:
1. 과거 이벤트 다시 읽기 (Replay)
[상황 1] 결제 서비스에 버그 → 어제부터 금액이 틀렸음
RabbitMQ: 메시지 이미 삭제됨 → 재처리 불가 → DB에서 수동으로 고치기
Kafka: offset을 어제로 되감기 → 어제 이벤트부터 다시 처리 → 자동 복구
[상황 2] 새로운 "추천 서비스"를 추가 → 과거 주문 데이터 필요
RabbitMQ: 과거 메시지 없음 → DB에서 별도 마이그레이션
Kafka: Consumer Group 새로 만들고 offset 0부터 → 과거 전체 소급 처리
2. 이벤트 순서 보장
같은 Key(예: orderId)의 이벤트는 항상 같은 Partition에 들어감
→ Partition 안에서는 순서가 보장됨
Partition 0: [주문1 생성] → [주문1 결제] → [주문1 배송] ← 순서 보장
Partition 1: [주문2 생성] → [주문2 취소] ← 순서 보장
→ "주문 생성 전에 결제가 처리되는" 사고 방지
CGV에서 Kafka를 안 쓰는 이유
Kafka의 3대 강점 vs CGV 상황:
1. 여러 서비스가 독립 소비?
→ CGV: 읽는 서비스가 1개뿐 (같은 Spring Boot 앱) → 불필요
2. 과거 이벤트 replay?
→ CGV: 2초마다 새 통계. 과거 통계는 의미 없음. → 불필요
3. 이벤트 순서 보장?
→ CGV: 최신 통계만 보여주면 됨. 순서 무관. → 불필요
추가:
- Kafka 클러스터 운영 필요 (최소 2~3대)
- 2초마다 오는 메시지를 7일간 보관? → 의미 없는 디스크 사용
- Kafka의 강점을 하나도 활용하지 않으면서 운영 부담만 추가
→ 안 쓰는 게 맞음
6. Kafka Deep Dive — 내부 구조와 원리
6.1 Topic과 Partition
Topic = 이벤트의 카테고리 (예: order-events)
order-events Topic:
├── Partition 0: [msg0] [msg1] [msg2] [msg3] ...
├── Partition 1: [msg0] [msg1] [msg2] ...
└── Partition 2: [msg0] [msg1] ...
왜 Partition으로 쪼개나?
Partition 1개: Consumer 1개만 읽을 수 있음 → 병목
Partition 3개: Consumer 3개가 동시에 읽음 → 3배 처리량
→ 처리량이 부족하면 Partition을 늘리면 됨
→ 이게 Kafka가 "수평 확장"으로 처리량을 늘리는 방식
메시지가 어떤 Partition에 들어가나?
Producer가 Key를 지정하면:
kafka.send("order-events", key="order-123", value={...})
→ hash("order-123") % partition_count = Partition 번호
→ 같은 Key = 항상 같은 Partition = 순서 보장
Key를 지정하지 않으면:
→ Round-robin 분배 → 순서 보장 없음
6.2 Offset
Partition 0의 내용 (append-only 로그):
offset 0: {orderId: 1, action: "CREATED"}
offset 1: {orderId: 2, action: "CREATED"}
offset 2: {orderId: 1, action: "PAID"}
offset 3: {orderId: 3, action: "CREATED"}
Consumer Group A (결제): "offset 3까지 읽었다" → 다음은 4부터
Consumer Group B (알림): "offset 1까지 읽었다" → 다음은 2부터
핵심:
RabbitMQ: 메시지를 꺼내면 큐에서 사라짐
Kafka: 메시지는 그대로. 읽는 위치(offset)만 움직임.
→ 이게 "여러 서비스가 같은 메시지를 독립적으로 읽는" 원리
6.3 Consumer Group
Topic: order-events (Partition 3개)
Consumer Group A (결제 서비스, Pod 3개):
Pod A-1 ← Partition 0
Pod A-2 ← Partition 1
Pod A-3 ← Partition 2
→ 3개가 나눠서 병렬 처리
Consumer Group B (알림 서비스, Pod 2개):
Pod B-1 ← Partition 0, 1
Pod B-2 ← Partition 2
→ Pod가 적으면 하나가 여러 Partition 담당
Consumer Group C (분석 서비스, Pod 1개):
Pod C-1 ← Partition 0, 1, 2
→ 혼자 전부 처리 (느려도 A, B에 영향 없음)
같은 Group 안에서: Partition을 나눠서 처리 (작업 분배)
다른 Group끼리: 같은 메시지를 각자 읽음 (이벤트 전파)
→ "작업 분배"와 "이벤트 전파"가 동시에 되는 게 Kafka의 핵심 설계
→ RabbitMQ는 "작업 분배"만, Redis Pub/Sub은 "전파"만
[Consumer 수 vs Partition 수]
Partition 3개:
Consumer 3개 → 1:1 배정 (최적)
Consumer 2개 → 1개가 2개 담당 (좀 느림)
Consumer 5개 → 2개는 할 일 없음 (낭비)
→ Consumer 수 > Partition 수 = 의미 없음
→ 처리량 늘리려면 Partition과 Consumer를 같이 늘려야 함
"파티션 = 컨슈머 수" 규칙은 Consumer Group 내부에만 적용!!!
파티션 개수가 정해져있으면 각 Consumer Group 내의 Consumer 개수는 파티션 개수보다 적게 있어야 함!!
6.4 Replication — 데이터 안전
Broker 2대, Replication Factor 2:
Broker 0 (AZ-a): Broker 1 (AZ-c):
Partition 0: Leader ◄──쓰기── Partition 0: Follower (복사본)
Partition 1: Follower Partition 1: Leader ◄──쓰기──
Producer는 항상 Leader에 씀 → Follower가 자동 복사
Broker 0이 죽으면?
→ Partition 0의 Follower(Broker 1)가 Leader로 승격
→ 데이터 유실 없이 서비스 계속
[acks 설정 — "얼마나 안전하게 쓸 것인가"]
acks=0: 보내기만. Broker 확인 안 함. 유실 가능. 가장 빠름.
acks=1: Leader만 저장 확인. Leader 죽으면 유실 가능.
acks=all: Leader + 모든 Follower 저장 확인. 가장 안전. 가장 느림.
개발: acks=1 (속도 우선)
운영: acks=all + min.insync.replicas=2 (안전 우선)
6.5 왜 Kafka가 빠른가
비밀 1: Sequential I/O (순차 쓰기)
일반 DB/MQ:
메시지 도착 → 인덱스 업데이트 → 랜덤 디스크 쓰기
→ HDD: ~10ms, SSD: ~0.1ms
Kafka:
메시지 도착 → Partition 로그 파일 끝에 추가 (append-only)
→ HDD: ~0.03ms, SSD: ~0.01ms
순차 쓰기가 랜덤 쓰기보다 100~300배 빠름
→ HDD에서도 Kafka는 빠르다
비밀 2: Zero-Copy (복사 없는 전송)
일반 전송:
디스크 → 커널 버퍼 → 앱 메모리 → 커널 버퍼 → 네트워크 = 4번 복사
Kafka (sendfile 시스템콜):
디스크 → 커널 버퍼 → 네트워크 = 2번 복사 (앱 메모리 안 거침)
→ Consumer가 대량으로 읽을 때 CPU/메모리 절약
6.6 Kafka가 적합한 상황
✅ 하나의 이벤트를 5개+ 서비스가 각자 독립 소비해야 할 때
→ 쿠팡: 주문 이벤트 → 결제, 재고, 배송, 알림, 분석이 각자 Consumer Group
✅ 과거 이벤트를 다시 처리해야 할 때
→ 버그 수정 후 offset 되감기, 새 서비스 추가 시 과거 소급
✅ 이벤트 순서가 비즈니스에 중요할 때
→ 같은 Key → 같은 Partition → 순서 보장
✅ 초당 수십만 이벤트 이상 처리해야 할 때
→ Sequential I/O + Partition 확장 = 선형 처리량 증가
❌ Kafka가 과한 상황:
서비스 2개가 1:1 통신 → RabbitMQ나 @Async로 충분
하루 수백 건 처리 → DB Job Queue로 충분
보관/재처리 불필요 → Redis Pub/Sub로 충분
Kafka 운영 인력 없음 → Kinesis(매니지드) 또는 더 가벼운 대안
7. Kinesis — Kafka의 AWS 매니지드 버전
Kafka와 같은 개념, 용어만 다르다
| Kafka | Kinesis | 설명 |
|---|---|---|
| Topic | Stream | 이벤트 카테고리 |
| Partition | Shard | 병렬 처리 단위 |
| Offset | Sequence Number | "어디까지 읽었는지" |
| Consumer Group | KCL Application | 독립 소비 단위 |
| Retention 7일 | 기본 24시간 (최대 365일) | 보관 기간 |
차이점
Kafka:
- 직접 Broker 클러스터 설치/운영
- Partition 무제한 확장
- 운영 인력 필요 (모니터링, 리밸런싱, 버전 업그레이드)
- 자유도 높음, 비용은 서버 비용
Kinesis:
- AWS가 전부 관리 (설치/운영 불필요)
- Shard당 초당 1MB/1000건 제한 → 수동 Shard 분할
- 운영 부담 거의 없음
- 사용량 과금 (Shard 수 × 시간)
CGV에서 Kinesis를 안 쓰는 이유
Kafka를 안 쓰는 이유(섹션 5 참고)와 동일하다. 거기에 Shard당 과금까지 추가되므로, 쓰지 않는 기능에 돈을 내는 구조가 된다.
8. 전체 비교와 판단 기준
한 눈에 비교
| SimpleBroker | Redis Pub/Sub | RabbitMQ | Kafka | Kinesis | |
|---|---|---|---|---|---|
| 패턴 | Pub/Sub | Pub/Sub | Message Queue | Event Streaming | Event Streaming |
| 메시지 보관 | ❌ | ❌ | 소비 시 삭제 | ✅ 7일 | ✅ 24h~365일 |
| 전달 보장 | ❌ | ❌ | ✅ ACK+재전송 | ✅ offset | ✅ checkpoint |
| 다시 읽기 | ❌ | ❌ | ❌ | ✅ | ✅ |
| 전달 범위 | 같은 Pod | 모든 Pod | 모든 서비스 | 모든 서비스 | 모든 서비스 |
| 설치 | 없음 | 이미 있음 | 별도 서버 | 별도 클러스터 | AWS 매니지드 |
| 처리량 | ~수천/초 | ~수만/초 | ~수만/초 | ~수십만/초 | ~수천/Shard |
| 운영 부담 | 없음 | 낮음 | 중간 | 높음 | 낮음 |
| 비유 | 교내 방송 | 라디오 | 등기우편 | 유튜브 | AWS 유튜브 |
의사결정 기준
메시지를 나중에 다시 읽어야 하나?
│
├── YES → Event Streaming
│ ├── 직접 운영 가능? → Kafka
│ └── AWS 매니지드? → Kinesis
│
└── NO → 메시지 유실이 치명적인가?
│
├── YES → Message Queue
│ ├── 직접 운영? → RabbitMQ
│ └── AWS 매니지드? → SQS
│
└── NO → 주기적 실시간 데이터?
│
├── YES
│ ├── Multi-Pod 전파 필요? → Redis Pub/Sub
│ └── Pod 내부만? → SimpleBroker
│
└── NO → @Async 또는 DB Job Queue
한 줄 판단
"이 택배 배달해줘" → RabbitMQ (정확히 한 번 전달)
"이런 일이 있었다 (기록해둬)" → Kafka (보관 + 재생 + 독립 소비)
"지금 이 소식 전해" → Redis Pub/Sub (빠르고 가벼움)
"이 Pod의 브라우저들에게 전해" → SimpleBroker (Pod 내부)
"비동기 하고 싶은데 인프라 싫어" → @Async (코드 레벨)
9. 면접 대비 Q&A
Q. 메시징 시스템의 3가지 패턴을 설명해주세요
Pub/Sub은 라이브 방송입니다. 지금 연결된 구독자에게 즉시 전달하고 메시지를 보관하지 않습니다. Message Queue는 택배입니다. 메시지를 한 Consumer에게 전달하고, ACK으로 처리 확인 후 삭제합니다. Event Streaming은 유튜브입니다. 메시지가 소비 후에도 보관되어 여러 서비스가 독립적으로 읽고, offset을 되감아 재처리할 수 있습니다.
Q. Kafka는 메시지큐인가요?
아닙니다. Kafka는 분산 커밋 로그, 즉 이벤트 스트리밍 플랫폼입니다. 메시지큐(RabbitMQ)는 메시지가 소비되면 삭제되지만, Kafka는 보관 기간 동안 메시지가 남아있습니다. Consumer Group별로 독립적으로 offset을 관리하여 같은 이벤트를 여러 서비스가 각자 읽을 수 있습니다. 용도도 다릅니다. 메시지큐는 "작업 분배", Kafka는 "이벤트 기록과 전파"입니다.
Q. Kafka가 빠른 이유는?
두 가지입니다. 첫째, Sequential I/O입니다. Partition을 append-only 로그로 관리하여 순차 쓰기만 합니다. 순차 쓰기는 랜덤 쓰기보다 100~300배 빠릅니다. 둘째, Zero-Copy입니다. Consumer가 읽을 때 sendfile 시스템콜로 디스크에서 네트워크로 직접 전송하여 앱 메모리를 거치지 않습니다.
Q. CGV에서 왜 Kafka 대신 Redis Pub/Sub을 쓰나요?
Kafka의 핵심 강점은 메시지 보관, 독립 Consumer Group, replay, 대량 처리입니다. CGV 대기열 통계는 2초마다 갱신되어 보관이 불필요하고, 소비하는 서비스가 하나뿐이라 독립 소비도 불필요합니다. 유실돼도 2초 후 새 데이터가 오고 API 폴링 fallback도 있습니다. 이미 Redis를 Primary Store로 사용 중이므로 Pub/Sub을 추가 비용 없이 활용하는 것이 이 규모에 최적입니다.
Q. SimpleBroker와 Redis Pub/Sub을 왜 같이 쓰나요?
SimpleBroker는 Spring Boot 내장 브로커로 같은 Pod의 WebSocket 클라이언트에게만 메시지를 전달합니다. Multi-Pod 환경에서는 Pod 간 전파가 안 됩니다. Redis Pub/Sub으로 모든 Pod에 메시지를 전파한 뒤, 각 Pod의 SimpleBroker가 자기 Pod의 브라우저에 전달하는 2-Layer 구조를 사용합니다.
Q. Kafka의 Consumer Group을 설명해주세요
Consumer Group은 같은 역할을 하는 Consumer의 묶음입니다.
Group 안에서는 Partition을 나눠서 병렬 처리하고(작업 분배),
다른 Group끼리는 같은 메시지를 독립적으로 읽습니다(이벤트 전파).
이 두 가지가 동시에 가능한 게 Kafka의 핵심 설계입니다.
Consumer 수는 Partition 수를 넘기면 놀게 되므로, 처리량을 늘리려면 Partition과 Consumer를 같이 늘려야 합니다.
'Cloudwave > cgv-project' 카테고리의 다른 글
| 테라폼 (1) | 2026.02.18 |
|---|---|
| CI/CD (0) | 2026.02.18 |
| WebSocket (0) | 2026.02.12 |
| 3. CI/CD 파이프라인 구축과 자동화 (0) | 2026.02.02 |
| 2. CGV 대기열 시스템 (0) | 2026.02.01 |