> For the complete documentation index, see [llms.txt](https://leehyokyun.gitbook.io/openbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leehyokyun.gitbook.io/openbook/4-3.-rabbitmq-and-kafka.md).

# 4-3. 메시지 브로커 (RabbitMQ & Kafka)

대규모 트래픽을 비동기 외부 체계를 통해 처리하기 위한 방안, 메시지 브로커(RabbitMQ & Kafka)에 대하여

### 메시지 전달 보장 방식 (Delivery Semantics) 및 브로커별 메커니즘 비교

데이터 동기화 및 트랜잭션을 메시징 큐 체계를 기반으로 SAGA 패턴화할 경우, 전달 보장(Delivery Semantics)의 개념까지 접목하여 메시징 전달에 대한 보장을 고려해야 한다. 아무리 동일한 메시징 체계라도 설정 및 커밋(Commit) 방법 등에 따라 전달 보장 수준이 판이하게 달라질 수 있다.

**메시지 전달 보장의 3가지 수준**

* **At Most Once (최대 1번)**: 메시지를 딱 1번만 보내고 끝내는 방식이다. Consumer의 ACK 응답을 기다리지도 않고 확인하지도 않는다. 따라서 메시지 유실 가능성이 존재하지만, 중복 메시지가 발생할 염려가 전혀 없기에 처리가 매우 가볍다.
* **At Least Once (최소 1번)**: Consumer가 메시지를 정상 처리한 후 보낸 ACK 응답이 확인되어야만 발행 및 전달을 완료하는 방식이다. 메시지 유실은 절대 발생하지 않음을 보장하지만, 네트워크 장애 등으로 ACK가 소실될 경우 동일한 메시지가 재전송되어 중복 수신이 발생할 수 있다. 따라서 반드시 비즈니스 레이어에 멱등성(Idempotency) 조치가 수반되어야 한다.
* **Exactly Once (정확히 1번)**: 메시지가 유실되지도 않고 중복되도 한 번만 처리되도록 완벽히 보장하는 이상적인 방식이다. 프로듀서와 브로커, 컨슈머 간의 정교한 트랜잭션 API 연동 및 오프셋 제어를 통해 하드웨어 레벨에서 정확히 1번만 실행됨을 담보하지만 성능 오버헤드가 크다.

**RabbitMQ vs Kafka vs Redis Pub/Sub 비교 구조**

* **RabbitMQ**: 기본적으로 At Least Once 수준을 지향한다. Consumer 측의 ACK 존재 여부를 필히 확인하며, 메시지를 메모리 혹은 디스크 큐(Queue)에 안전하게 저장한다. ACK 응답이 유실되거나 전달되지 않으면 브로커가 이를 감지하여 해당 메시지를 컨슈머에게 재전송(ack 기반 재전송)하는 메커니즘으로 구동된다.
* **Apache Kafka**: 기본적으로 At Least Once 방식을 근간으로 삼는다. Consumer 그룹의 파티션별 오프셋 커밋(Offset Commit) 상태를 추적하여 전달을 보장한다. 유입된 이벤트는 물리적인 디스크 커밋 로그에 영구 저장되므로 유실 위험이 없다. 컨슈머가 오프셋 커밋을 누락하면 해당 시점부터 데이터를 다시 당겨와 읽는(Pull + Offset 방식) 형태로 재전송 및 재처리를 수행한다.
* **Redis Pub/Sub**: 철저하게 At Most Once 방식으로 동작한다. 별도의 ACK 확인 절차가 전혀 존재하지 않으며, 메시지를 보관하는 저장소(Queue)나 버퍼 개념이 아예 없다. 메시지를 발행하는 순간 즉시 던지고 잊어버리는 'Fire-and-forget' 구조이므로, 발행 시점에 구독(Subscribe) 중인 컨슈머가 없다면 해당 메시지는 그 즉시 영구 유실된다.

### Kafka의 메시지 처리 실패 대응 및 내결함성(Fault Tolerance) 전략

카프카 기반의 아키텍처에서 비즈니스 로직 처리 중 예외(Exception)나 시스템 다운이 발생했을 때, 이를 무조건 Blocking하여 파티션 전체의 처리를 마비시키지 않고 부드럽게 격리 및 재처리하기 위한 고도화된 대응 체계이다.

**Non-Blocking Retry Topic (재처리 토픽) 메커니즘** 컨슈머가 메인 토픽으로부터 메시지를 수신하여 비즈니스 로직을 수행하던 중 데이터베이스 타임아웃이나 일시적인 네트워크 순단 등의 재시도 가능한(Retriable) 예외에 직면했을 때 사용한다. 이 경우 현재 파티션의 오프셋 커밋을 붙잡고 무한 대기(Blocking)하는 것이 아니라, 해당 메시지를 별도로 구축된 `메인토픽-retry` 토픽으로 양도 발행하고 메인 파티션은 다음 오프셋 메시지를 즉시 처리하도록 통제한다.

**DLQ (Dead Letter Queue, 데드 레터 큐) 격리 전략** 재처리 토픽을 통해 지정된 최대 재시도 횟수(Max Retry Count)를 초과했거나, 혹은 최초부터 데이터 포맷 오류, 널 포인터 예외 등 재시도 가치가 없는 비정상적(Non-retriable) 에러로 판명된 이벤트를 최종적으로 격리하는 특수 토픽이다. 실패한 메시지를 `메인토픽-dlq`로 적재함으로써 정상적인 트래픽 흐름을 완벽히 보호하고, DLQ에 적재된 장애 이벤트들은 소실되지 않은 상태로 유지되어 추후 관리자가 수동으로 원인을 분석하거나 보정 스크립트를 통해 안전하게 재발행 및 사후 조치를 취할 수 있도록 돕는다.

**Transactional Outbox 패턴 기반의 발행 보장** 메시지 큐로 이벤트를 발행하는 행위와 비즈니스 데이터를 RDBMS 테이블에 반영하는 행위는 물리적으로 완전히 다른 데이터소스를 사용하므로 단일 트랜잭션 분산 무결성(XA)을 보장하기 매우 어렵다. 이를 해결하기 위해 메시지를 카프카로 직접 즉시 발행하지 않고, 동일한 RDBMS 트랜잭션 범위 내에 있는 로컬 데이터베이스의 `OUTBOX` 테이블에 발행할 메시지 내용을 엔티티 상태로 함께 INSERT(저장) 처리한다. 이후 별도의 전용 로그 수집기(Debezium 등)나 스케줄러 폴링 프로세스가 Outbox 테이블의 커밋된 데이터를 읽어 카프카로 유기적으로 발행하게 함으로써, 메시지 발행 실패 리스크를 원천 제거하고 서비스 붕괴 시점에도 데이터 정합성을 완벽하게 사수한다.

### Apache Kafka 코디네이션 아키텍처의 진화 (ZooKeeper vs KRaft)

카프카 클러스터의 고가용성(HA)과 메타데이터 동기화를 제어하는 컨트롤러(Controller) 선출 구조는 분산 시스템 설계 사상의 고도화에 따라 극적인 변화를 맞이했다.

**ASIS: ZooKeeper 분산 코디네이션의 한계** 과거 카프카 아키텍처는 클러스터의 메타데이터 관리, 토픽 및 파티션 정보 유지, 브로커의 생존 주기(Heartbeat) 모니터링 및 컨트롤러 리더 선출을 전적으로 외부 분산 코디네이션 시스템인 주키퍼(ZooKeeper)에 위임하여 동기화했다. 이 구조에서는 카프카 브로커들과 주키퍼 앙상블 간의 잦은 네트워크 통신 및 데이터 동기화 오버헤드가 상시 유발되었다. 특히 대규모 클러스터 환경에서 파티션 개수가 수만 개 이상으로 급증할 경우, 주키퍼의 메타데이터 쓰기 성능 병목으로 인해 카프카 전체의 확장성이 심각하게 제약받았으며 주키퍼 장애 시 복구 시간이 매우 오래 걸리는 구조적 결함을 안고 있었다.

**TOBE: KRaft (Raft 알고리즘) 기반 내재화 아키텍처** 외부 시스템인 주키퍼를 아키텍처에서 완벽히 제거하고, 카프카 브로커 내부 자체적으로 메타데이터를 직접 관리하고 제어하는 KRaft(Kafka Raft Metadata Mode) 체계로 완전히 전환되었다. 메타데이터 자체가 카프카 내부의 특수 로그 토픽(`@metadata`)으로 직접 관리되며, 브로커들 중 선출된 컨트롤러 쿼럼(Quorum)이 Raft 합의 알고리즘에 기반하여 리더 및 팔로워 간의 상태 무결성을 초고속으로 동기화한다.

### AMQP 구현체(RabbitMQ)와 Apache Kafka의 사상적 차이

두 시스템은 분산 이벤트 처리를 담당한다는 공통점을 지니나, 메시지를 다루고 영속화하는 아키텍처 관점의 설계 철학은 완벽히 상반된다.

**AMQP와 RabbitMQ의 관계** AMQP(Advanced Message Queuing Protocol)는 메시지 브로커와 클라이언트 간의 무결성 통신을 위해 정립된 유선 레벨의 글로벌 표준 프로토콜이자 메시지 전달 규약이다. RabbitMQ는 이 AMQP 프로토콜 체계를 완벽하게 준수하여 메시지 라우팅 및 큐잉 레이어를 엔터프라이즈 레벨로 구현해 낸 가장 대표적인 'AMQP 프로토콜의 구현체'이다. Apache ActiveMQ나 Azure Service Bus 같은 시스템들도 이와 동일한 AMQP 규약 패러다임 상에서 구동된다.

**브로커 중심(RabbitMQ) vs 컨슈머 중심(Kafka) 사상 비교**

* **RabbitMQ**: 전통적인 메시지 브로커 사상을 고수한다. 핵심 초점이 메시지의 '전달 보장'과 '브로커 및 컨슈머 간의 기민한 상태 관리'에 맞춰져 있다. 브로커가 큐 내부 메시지의 전달 상태와 ACK 여부를 실시간으로 꼼꼼하게 추적하며, 컨슈머가 메시지를 가져가서 정상 ACK를 반환하는 즉시 해당 메시지를 큐에서 물리적으로 완전 삭제하여 소거한다. 즉, 메시지를 장기 보존하는 목적에는 부합하지 않는다.
* **Apache Kafka**: 분산 커밋 로그 기반의 스트리밍 플랫폼 사상을 취한다. 브로커는 컨슈머가 메시지를 읽어갔는지 여부에 전혀 관심을 두지 않으며, 유입된 모든 이벤트를 디스크 로그 파일에 순차적으로 묵묵히 기록(영속화)하는 역할만 수행한다. 메시지의 소비 상태를 나타내는 오프셋(Offset) 관리 책임은 전적으로 컨슈머(Consumer)에게 위임된다. 컨슈머가 메시지를 소비해도 데이터는 절대 삭제되지 않고 설정된 보존 주기(Retention Period) 동안 디스크에 안전하게 유지되므로, 컨슈머가 필요에 따라 과거 오프셋으로 커서를 돌려 데이터를 언제든 재수신하고 재생(Replay)할 수 있는 압도적인 데이터 가용성을 제공한다.

### 중복 메시지 유실 방지 및 소비자 멱등성(Idempotency) 보장 전략

At Least Once(최소 1번 전달) 메커니즘을 사용하는 분산 메시징 환경에서는 네트워크 유실 등의 사유로 동일 메시지가 컨슈머 워커 서버에 중복 도달하는 현상이 필연적으로 발생한다. 시스템 장애를 막고 비즈니스 데이터의 정합성을 수호하기 위해 소비자 레이어에서의 멱등성 보장은 필수 자격 요건이다. 중복 실행은 허용될 수 있으나 그 최종 결과물은 반드시 단 1번만 반영된 것과 같은 상태를 유지해야 한다. 특히 원장 데이터를 기준으로 이전을 조회한 뒤 차액을 가산하는 증분 업데이트(Incremental Update) 연산 등은 기본적으로 멱등적이지 않으므로 아래의 특수 방어 아키텍처를 도입해야 한다.

**Event ID 기반의 데두프 (Dedup, 중복 제거)** Consumer가 이벤트를 수신하는 즉시 비즈니스 연산을 처리하기에 앞서, 이벤트가 고유하게 내포하고 있는 고유 식별자인 'Event ID'를 추출한다. 그리고 멱등성 저장소 및 RDBMS의 Outbox 패턴 테이블을 조회하여 해당 Event ID의 성공 처리 내역이 이미 존재하는지 여부를 일차적으로 확인한다.&#x20;

만약 이미 성공 완료된 기록이 존재한다면 실제 비즈니스 로직 및 하부 영속성 레이어 쓰기 연산을 시도하지 않고 즉시 스킵(Skip) 처리하며, 존재하지 않는 최초의 유일한 ID인 경우에만 로직을 안전하게 실행하고 성공 내역에 해당 ID를 영속화하여 중복 처리를 원천 방어(Dedup)한다.

**상태 전이 기반 Update (State-Driven Update) 전략** 주문 시스템의 상태 생명주기(주문 완료 -> 결제 대기 -> 배송 중 -> 배송 완료 등)와 같이 명확한 비즈니스 워크플로우 상태 체계를 지니는 엔티티 객체를 다룰 때 활용하는 매우 강력한 멱등성 기법이다.&#x20;

단순 조회를 통한 가공 처리를 배제하고, 엔티티를 추출 및 업데이트하는 조건절 자체에 ID 값과 현재 기대하는 고유 상태(Status) 값을 명확히 결합하여 쿼리를 수행한다. 예를 들어 `UPDATE ORDER SET status = 'PAID' WHERE id = :id AND status = 'READY'`와 같은 형태로 상태 전이 조건을 SQL 조건절에 직접 명시하는 방식이다. 이 구조 하에서는 동일한 결제 완료 메시지가 수백 번 중복 인입되더라도, 최초 1번만 조건에 부합하여 데이터 변경에 성공하고 두 번째 요청부터는 쿼리의 영향받은 row 수가 0건이 되므로 자연스럽게 비즈니스 멱등성이 완벽하게 보장된다.&#x20;

다만 다수의 스레드가 경합하는 극심한 동시성(Concurrency) 병목 문제는 별개의 영역이므로, 데이터의 완전한 안정성을 극대화해야 하는 임계 경로상에서는 테이블 구조 고도화 혹은 필요에 따른 락(Lock) 메커니즘의 결합이 동반되어야 한다.

### Kafka의 설계 사상

Redis도 그렇고, Kafka 역시 처리 속도가 빠른 이유는 시스템에 기반한 설계, 그 시스템 패턴을 구조적으로 적절하게 사용하는 것, 이 구성을 기반으로 하여 빠른 연산을 유도한 결과이다.

#### Kafka의 메시지 저장소는 디스크인데도 빠르다.

Kafka의 핵심은 디스크를 메시지 저장소로 사용하지만, 헤드 이동을 유도하지 않고 Producer 측에서 offset 기반으로 메시지를 항상 끝에 저장한다는 점(append-only)이다. 이를 통해 Random I/O가 아닌, Sequential I/O를 유도하며 이에 따라 (오래된 offset 조회가 아닌 이상) 디스크의 빠른 연산을 유도해내는 것이다.

마찬가지로 Consumer 역시, 본인이 일전에 읽은 offset을 기억하여 이를 휘발하지 않고 조회에 이용하여, 메시지 유실을 방지하고 Kafka 설계 사상에 맞는 효율적인 읽기 및 처리가 가능한 것이다.
