"일단 Kafka"가 늘 정답은 아닌 이유
메시지 큐를 고를 때 가장 흔한 실수는 필요한 보장 대신 가장 유명한 제품을 고르는 것이다. 일 100만 메시지는 많아 보이지만 초당 평균 약 11.6건(1,000,000 ÷ 86,400)이다. 이 규모라면 Kafka, RabbitMQ, NATS JetStream, Redis Streams 모두 처리량 자체는 문제가 되지 않는다. 선택을 가르는 것은 전달 모델, 재처리(리플레이), 순서 보장 단위, 운영 부담이다.
이 글은 Kafka Streams + Schema Registry 파이프라인 이전에 던졌어야 할 질문, 즉 "우리에게 무엇이 필요한가"를 판정표로 정리한다.
네 제품의 전달 모델부터 구분하기
| 항목 | Kafka | RabbitMQ | NATS (Core / JetStream) | Redis Streams |
|---|---|---|---|---|
| 기본 모델 | 파티션된 추가 전용 로그 | 익스체인지 → 큐 라우팅 (스트림 큐도 지원) | Core: pub/sub, JetStream: 영속 스트림 | Redis 자료구조인 추가 전용 로그 |
| 읽은 메시지 | 보존 기간 동안 남음 (컨슈머가 오프셋 관리) | 클래식·쿼럼 큐는 ack 후 삭제, 스트림은 남음 | Core: 저장 안 함, JetStream: 보존 정책에 따라 남음 | XTRIM/MAXLEN 등으로 자를 때까지 남음 |
| 리플레이 | 오프셋을 되돌려 재처리 | 스트림(3.9+)에서 가능, 일반 큐는 불가 | JetStream 컨슈머의 시작 위치 지정 | XRANGE/ID 지정 읽기 |
| 순서 보장 단위 | 파티션 내부 | 큐 단위(경쟁 소비자가 여럿이면 처리 순서는 깨질 수 있음, Single Active Consumer로 보완) | 스트림/주제 단위 | 스트림 단위 |
| 전달 보장 | at-least-once 기본, 멱등 프로듀서·트랜잭션으로 exactly-once 처리 구성 | publisher confirm + consumer ack로 at-least-once | Core: at-most-once, JetStream: at-least-once, 메시지 ID 중복 제거로 exactly-once 구성 | 컨슈머 그룹 + XACK으로 at-least-once |
| 팬아웃 | 컨슈머 그룹마다 전체를 독립적으로 읽음 | fanout/topic 익스체인지로 큐마다 복사 | 구독자마다 수신, JetStream은 컨슈머별 | 컨슈머 그룹마다 독립 읽기 |
| 고가용성 | 복제 계수(보통 3), KRaft 컨트롤러 | 쿼럼 큐(Raft) | JetStream 클러스터 복제(R1/R3/R5) | Redis 복제/클러스터 (비동기 복제) |
몇 가지는 최근 버전에서 바뀌었으니 오래된 글을 그대로 믿지 말자.
- Kafka 4.0(2025년 3월)부터 ZooKeeper 모드가 제거되어 KRaft만 지원한다. "Kafka = ZooKeeper 클러스터까지 운영"은 더 이상 사실이 아니다.
- RabbitMQ 4.0에서 클래식 큐 미러링이 제거되었다. 복제가 필요하면 쿼럼 큐나 스트림을 쓴다.
- Kafka 3.0부터 프로듀서는 기본으로
enable.idempotence=true,acks=all이다(충돌하는 설정이 없을 때). - NATS JetStream의 중복 제거는
Nats-Msg-Id헤더와 스트림의 duplicate window(기본 2분)로 동작한다. - Redis Streams에서 ack되지 않은 메시지는 PEL(pending entries list)에 남는다. 죽은 컨슈머의 메시지는
XPENDING으로 찾고XAUTOCLAIM(6.2+)으로 넘겨받는다.
30초 판정표: 6개 질문
- 과거 메시지를 다시 읽어 재처리해야 하는가? (새 서비스가 지난 7일치를 처음부터 소비 등)
- 순서가 필요하다면 어느 단위인가? 전역 / 키(주문 ID 등) / 큐
- 소비 후 메시지가 사라져야 하는 작업 큐인가, 여러 팀이 같은 이벤트를 각자 읽는 이벤트 로그인가?
- 라우팅 규칙이 복잡한가? (헤더·패턴별 분기, 우선순위, 지연 재시도, DLQ)
- 유실이 0이어야 하는가? 그렇다면 브로커 복제 + 프로듀서 확인 + 컨슈머 ack가 모두 필요하다
- 이 시스템을 맡을 사람이 있는가? 업그레이드, 디스크, 리밸런스, 모니터링을 누가 보나
| 상황 | 1순위 후보 | 이유 | 주의할 점 |
|---|---|---|---|
| 여러 소비자가 같은 이벤트를 독립적으로 읽고, 재처리·장기 보존이 핵심 | Kafka | 로그 + 컨슈머 그룹 + 오프셋 리셋이 기본 기능, 생태계(Connect, Streams, Schema Registry) | 파티션 수와 키 설계가 순서·확장성을 결정. 파티션 수는 늘릴 수 있지만 키→파티션 매핑이 바뀐다 |
| 작업 큐, 재시도·DLQ·우선순위·복잡한 라우팅 | RabbitMQ | 익스체인지·바인딩·DLX·TTL이 기본 기능, 메시지 단위 ack | 무제한 백로그 저장소로 쓰지 말 것. 복제는 쿼럼 큐로 |
| 가벼운 서비스 간 메시징, 요청-응답, 엣지/IoT, 운영 인력이 적음 | NATS (+JetStream) | 단일 바이너리, 낮은 운영 부담, 필요할 때만 JetStream 영속성 | Core NATS는 구독자가 없으면 메시지가 사라진다. 영속이 필요하면 JetStream 스트림을 명시적으로 만든다 |
| 이미 Redis를 운영 중이고 소규모 비동기 작업 | Redis Streams | 추가 인프라 없음, 컨슈머 그룹 지원 | 메모리에 저장되므로 보존량이 곧 메모리 비용. 비동기 복제라 장애 조치 시 최근 쓰기를 잃을 수 있다 |
비용 계산: 가격표보다 먼저 맞출 숫자
클라우드 단가는 리전과 시점에 따라 바뀌므로 이 글에서는 고정 금액을 쓰지 않는다. 대신 어떤 제품이든 같은 방식으로 견적을 내도록 입력값을 맞추자. 예시는 일 100만 메시지 × 평균 2KB × 보존 7일이다.
| 입력값 | 계산 | 결과 |
|---|---|---|
| 평균 처리량 | 1,000,000 ÷ 86,400 | 약 11.6 msg/s (피크는 별도로 측정) |
| 일 유입량 | 1,000,000 × 2KB | 약 2GB/일 |
| 월 유입량 | 2GB × 30 | 약 60GB/월 (전송량 계산에 사용) |
| 보존 데이터 | 2GB × 7일 | 약 14GB |
| 복제 3 적용 | 14GB × 3 | 약 42GB (+ 여유 공간, 인덱스·오버헤드) |
여기서 흔한 실수는 월 유입량(60GB)에 보존 일수를 다시 곱하는 것이다. 보존 7일이면 디스크에 남는 것은 7일치(약 14GB, 복제 포함 약 42GB)다. 이 규모에서는 스토리지보다 브로커 인스턴스의 최소 구성(보통 3노드)과 사람의 운영 시간이 비용을 좌우한다.
청구서에서 놓치기 쉬운 항목:
- AZ 간 전송료: 복제 계수 3을 서로 다른 AZ에 두면 프로듀서 → 리더, 리더 → 팔로워, 컨슈머 읽기 경로마다 AZ 간 트래픽이 생길 수 있다. 클라우드별 단가를 확인하고, Kafka는
client.rack으로 가까운 레플리카에서 읽도록(follower fetching) 줄일 수 있다. - 관리형 서비스의 과금 단위: 브로커 시간, 파티션 시간, 저장 GB, 처리 GB 등 서비스마다 다르다. 같은 워크로드 표를 각 서비스의 공식 가격 계산기에 넣어 비교한다.
- 자체 운영의 인건비: 인스턴스 비용이 가장 낮아도 업그레이드, 디스크 확장, 장애 대응을 맡는 시간이 월 몇 시간인지 같이 적는다.
선택 후 첫 주에 확인할 운영 지표
| 제품 | 꼭 볼 지표 | 이상 신호 |
|---|---|---|
| Kafka | 컨슈머 lag, under-replicated partitions, 디스크 사용률 | lag가 계속 증가, URP > 0 지속 |
| RabbitMQ | 큐 깊이(ready/unacked), 메모리·디스크 알람, 연결/채널 수 | unacked가 계속 증가(컨슈머 정지), 알람으로 publish 차단 |
| NATS JetStream | 컨슈머 pending/ack pending, 재전송 수, 스트림 크기 | ack pending 증가, max deliver 도달 |
| Redis Streams | 스트림 길이(XLEN), PEL 크기(XPENDING), 메모리 | PEL이 줄지 않음(죽은 컨슈머), maxmemory 근접 |
Kafka 컨슈머가 리밸런스를 반복한다면 Kafka Consumer 리밸런싱 실패 원인과 튜닝을 참고하자.
자주 묻는 질문
Q. RabbitMQ도 리플레이가 되나요? A. RabbitMQ Streams(3.9+)는 메시지를 남겨 두고 오프셋으로 다시 읽을 수 있습니다. 클래식·쿼럼 큐는 ack 후 메시지가 삭제되므로 리플레이 용도가 아닙니다.
Q. Kafka는 전역 순서를 보장하나요? A. 파티션 내부 순서만 보장합니다. 같은 키(예: 주문 ID)를 같은 파티션으로 보내면 키 단위 순서를 얻습니다. 전역 순서가 필요하면 파티션을 1개로 둬야 하고 그만큼 병렬성을 잃습니다.
Q. NATS만으로 메시지 유실이 없나요? A. Core NATS는 at-most-once라 구독자가 없거나 끊기면 메시지가 사라집니다. 유실 방지가 필요하면 JetStream 스트림과 명시적 ack를 사용하세요.
기준일: 2026-10-15. 각 제품의 공식 문서(Apache Kafka, RabbitMQ, NATS, Redis)를 기준으로 했으며 버전별 기본값은 사용 중인 버전의 문서로 다시 확인하자.
글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보
이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.
댓글
첫 번째 댓글을 남겨보세요.