들어가며
이전에 작성한 글에서 최종적 일관성에 대해서 알아봤습니다. 분산 시스템에서는 각 시스템간의 디커플링을 지원하기 위해 이벤트 기반으로 동작합니다. 각각의 서비스에서 이벤트를 발행하고 다른 서비스에서 해당 이벤트를 받아서 처리하죠. 그렇다면 여기서 이 이벤트는 어떻게 다른 서비스로 전달이 되는 걸까요?
분산 시스템을 공부해보셨다면 메시지 큐라는 단어를 들어보셨을 겁니다. 이런 메시지 큐와 같은 메시징 시스템을 통해 이벤트(메시지)를 주고 받을 수 있는 것 입니다.
메시징 시스템은 세 가지 주요 패턴을 가지고 있습니다.
- 메시지 큐: 1:1 통신이며 메시지를 1번만 소비합니다.
- Pub/Sub(발행/구독): 1:N 통신이며 하나의 메시지를 여러 구독자가 소비합니다.
- Event Stream(이벤트 스트림): 순서가 보장되는 이벤트 시퀀스로 영구 저장되고 재처리가 가능합니다.
각 패턴들은 각각의 트레이드 오프가 있습니다. 그렇기에 실제 시스템에서는 위 패턴들을 조합하여 사용할 때가 많습니다.
이번 글에서는 각각의 패턴들을 자세히 살펴보고, 대표적인 메시징 시스템인 Kafka와 RabbitMQ에 대해서 간단히 소개하겠습니다.

핵심 개념
우선 각 패턴들에 대해 살펴보기 전에 먼저 알아두고 가면 좋을 개념들을 설명하겠습니다.
메시징 시스템의 단계
메시징 시스템의 동작 방식을 간단하게 추상화 해보면 아래와 같은 흐름을 가집니다.
- 생산자가 메시지를 생성
- 생산자가 메시지를 큐에 전송
- 메시지들이 큐에 저장됨
- 소비자가 큐에서 메시지를 가져옴
- 소비자가 큐에 승인을 보냄
Producer(생산자), Consumer(소비자), Broker(브로커)
생산자는 메시지를 생성하는 구성 요소입니다. 비즈니스 로직을 실행 후 결과를 메시지를 생성하여 브로커로 전송합니다.
소비자는 브로커에서 메시지를 읽어와 처리합니다. 처리 결과를 DB에 저장하거나 알림을 보내는 등의 비즈니스 로직을 실행합니다.
브로커는 메시지 수신, 저장, 전달을 담당하는 중개자의 역할을 합니다. 위의 메시징 시스템의 단계에선 큐가 브로커에 해당됩니다.
Pub/Sub패턴에서는 생산자, 소비자 대신 Publisher(발행자), Subscriber(구독자)라는 단어를 사용합니다.
큐, 토픽, 파티션
메시지는 보통 메시징 모델에 따라 큐나 토픽으로 구성됩니다. 큐는 단일 소비 라인을 의미하며 하나의 소비자가 메시지를 받습니다.
토픽은 여러 구독자에게 메시지를 수신하며 각각이 메시지의 복사본을 받습니다.
파티션은 토픽을 물리적으로 나눈 단위입니다. 이는 병렬 처리와 확장성을 위한 방식으로 각 파티션은 독립적인 큐처럼 동작합니다.

At-most-once, At-least-once, Exactly-once
메시지는 전송 도중 네트워크 중단, 서비스 장애 등의 이유로 전달되지 못할 수 있습니다. 이때 얼마나 전달을 보장할 수 있냐를 나타내는 개념입니다.
- At-most-once: 메시지는 최대 한번만 전달됩니다. 즉, 장애로 인해 메시지가 유실될 수 있다는 의미입니다. 이 방식은 빠르긴 하지만 위험합니다.
- At-least-once: 메시지는 최소 한번 전달됩니다. 그렇기에 중복이 발생할 수는 있지만 유실이 발생하진 않습니다.
- Exactly-once: 메시지는 정확히 한 번만 전달됩니다. 중복과 유실의 걱정이 없지만 굉장히 이상적인 상황으로 구현이 어렵습니다.
대부분의 시스템은 At-least-once를 지원합니다. 그렇기에 멱등성을 지원하여 중복으로 인한 문제가 발생하지 않도록 해야합니다.
메시지 큐

메시지 큐는 애플리케이션 간 비동기 통신을 위한 미들웨어로 FIFO 방식으로 메시지를 저장하고 전달합니다. 메시지 큐는 Point-to-Point 통신을 지원하는데 이는 1:1 통신 패턴이고 메시지가 한 번만 소비된다는 것을 의미합니다. 즉 메시지가 처리되고 승인되면 큐에서 삭제되어 다른 소비자는 이 메시지를 받지 않습니다.
생산자는 메시지를 큐에 push하고 소비자는 큐에서 해당 메시지를 가져와서 처리합니다. 완료되면 해당 메시지가 처리됐다는 확인을 보냅니다. 이러한 메시지는 PDF 생성, 이미지 처리와 같은 개별적인 작업 단위를 나타냅니다.
또한 메시지가 여러번 재시도에도 계속 실패한다면 DLQ로 이동하여 장애를 격리합니다.
트레이드 오프
소비자 확장의 함정
소비자 확장이란 메시지 처리량을 늘리기 위해 동일한 큐에서 메시지를 처리하는 Consumer 인스턴스의 수를 증가시키는 것을 의미합니다. 하지만 현실적 제약 사항으로 인해 문제점이 발생할 수 있습니다. 이상적으로 모든 메시지의 처리 시간이 동일하고 메시지간 의존성이 없다면 정상적으로 작동할 것입니다. 하지만 각 Consumer마다 상황에 따라 메시지 처리 시간이 다르고 메시지에 순서간 의존성이 존재할 수 있습니다. 이 경우 무결성에 문제가 발생할 수도 있습니다.

그림과 같은 상황에서 메시지가 순서대로 처리되면 문제가 없지만 Consumer간의 처리속도 차이로 인해 2, 3번 메시지가 먼저 처리되면 계좌 잔액이 0원 미만일 수 없다는 무결성에 문제가 발생해 메시지가 제대로 처리되지 못할 것입니다.
해결 방법으로는 파티션 내에서는 메시지의 순서가 보장된다는 점을 통해 순서가 중요한 메시지는 같은 파티션에 저장하여 순서를 보장하는 방식이 있습니다. 또는 각 메시지에 순차적인 번호를 부여하고 Consumer에서 번호 순서대로 처리하도록 하는 방식이 있습니다.
재시도의 위험성
메시지의 유실을 막기위해 At-least-once를 지원하기 때문에 계속된 실패는 지속적인 재시도를 유발합니다. 만약 이것이 일시적인 문제라면 괜찮겠지만 영구적인 장애라면 이러한 재시도는 시스템의 리소스를 과하게 소모할 수 있습니다.
그렇기에 DLQ와 같은 기능을 통해 일정 횟수 이상으로 재시도를 한 메시지를 따로 격리해 과하게 리소스를 소모하지 않도록 격리해야 합니다.
정체된 소비자 문제
정체된 소비자는 메시지 큐에서 메시지를 수신했지만 정상적인 처리 흐름을 완료하지 못하고 중간에 멈춰버린 Consumer를 의미합니다. 이는 무한 루프나 데드락과 같은 프로세스 레벨의 문제나 외부 의존성 문제 등 다양한 이유로 발생할 수 있습니다. 이는 메시지의 처리량을 감소시키고 latency를 늘리며 장기적으로 메모리가 누수되거나 리소스 고갈로 인해 시스템의 불안정을 가져올 수 있습니다.
그렇기에 메시지 처리에 대한 타임아웃을 지정하여 타임아웃 초과 시 강제로 처리를 중단하거나 Circuit Breaker 패턴을 사용하는 등의 방식으로 문제를 예방하고 모니터링을 통해 빠르게 알아챌 수 있는게 중요합니다.
Pub/Sub

Pub/Sub 패턴은 같은 메시지를 여러 구독자들에게 전달하는 패턴을 의미합니다. 메시지 큐가 단일 Consumer에게 push하는 것과 다르게 말이죠. 생산자가 토픽에 메시지를 발행(Publish)하게 되면 해당 토픽을 구독(Subscribe)한 구독자들은 모두 동일한 메시지의 복사본을 받게됩니다. 각 구독자는 메시지의 복사본을 각자의 방식으로 처리합니다. 이러한 방식은 분리와 확장성의 측면에서 이점을 가집니다. 발행자는 누가 메시지를 처리하는지 알 필요가 없고 발행자나 브로커의 변경 없이 구독자를 추가할 수 있기 때문입니다.
이러한 Pub/Sub 패턴은 여러 시스템이 동일한 이벤트를 처리해야 되는 이벤트 브로드 캐스팅이나 알림 시스템과 같은 곳에서 사용할 수 있습니다.
트레이드 오프
백프레셔
백프레셔는 Downstream의 처리 속도가 Upstream의 생산 속도를 따라가지 못 할 때 발생하는 압력을 의미합니다. Pub/Sub 패턴에서는 발행자의 메시지 발행 속도가 구독자의 처리 속도를 초과할 때 나타나는 현상입니다. 이렇게 백프레셔가 발생하면 처리 되지 못한 메시지들이 지속적으로 누적되어 메모리를 소모할 수 있습니다.
일반적으로 Message Droping을 통해 메시지를 의도적으로 폐기하거나 Buffering을 통해 일시적인 속도 차이를 메모리나 디스크에 저장하여 흡수합니다. 또는 발행자의 발행 속도를 제한하는 방식으로도 해결할 수 있습니다.
이벤트 스트림

이벤트 스트림은 메시지 전달이라기보단 시간 순서대로 발생하는 이벤트들을 불변의 로그 형태로 젖아하고 처리하는 분산 시스템 패턴입니다. 소비자들은 현재 발생한 이벤트만 처리하는 것이 아닌 이전에 발생한 이벤트들을 되돌아가고 재생할 수 있습니다. 이는 과거 데이터를 재처리하여 버그를 수정하거나 상태를 재구축하는 등의 작업을 할 수 있습니다.
패턴 비교
| 기준 | 메시지 큐 | Pub/Sub | 이벤트 스트림 |
| 전달 모델 | Point-to-Point (1:1) | Broadcast (1:N) | Log-based Streaming (1:N) |
| 순서 보장 | - 큐 내 FIFO - 재시도로 인해 깨질 수 있음 |
일반적으로 전역 순서를 제공하진 않음 | 파티션 내 강력한 순서 |
| 재생 가능성 | 불가능 (소비 후 삭제) | 제한적 (구현에 따라) | 완전 지원 (오프셋 기반) |
| 지연시간 | 낮음 | 낮음 | 버퍼링, 배치, 파티셔닝에 따라 높아질 수 있음 |
| 처리량 | 병렬 소비자의 수에 따라 확장 | 높음 | 매우 높음 |
메시징 시스템 Tool
Apache Kafka

Kafka는 단순한 메시징 시스템을 넘어선 분산 이벤트 스트리밍 플랫폼 입니다. Kafka는 높은 처리량, 확장성, 재생 가능성을 목표로 설계되었습니다. Kafka는 이벤트를 일급 시민으로 취급되어 불변성을 보장하고 순서를 유지하며 지속성을 제공해줍니다.
Kafka는 아래의 주요 특성을 가집니다.
- Partitioned Log: 토픽이 여러 파티션으로 분할되어 있으며 각 파티션은 순서가 보장된 추가 전용 로그입니다. 이를 통해 수평 확장과 높은 읽기/쓰기 처리량을 실현시킵니다.
- Offset-based Consumption: 소비자가 로그에서 자기의 위치를 추적하여 Replay, Backfill, 시간 기반 소비를 가능하게 하여 장애 복구 시 정확한 지점부터 재시작을 가능하게 합니다.
- Built-in Durability: 메시지가 디스크에 영구 저장되고 브로커들 간에 복제를 가능하게 합니다. 또한 데이터 보존 기간을 시간/주/영구 등 설정이 가능합니다.
- High Throughput: 초당 수백만 개의 메시지를 처리하며 텔레메트리, 메트릭, 클릭스트림 분석에 최적화가 가능합니다.
이러한 특징을 가지고 있는 Kafka의 핵심 사용 사례는 다음과 같습니다.
- 변경 데이터 캡처(CDC): 데이터베이스 변경사항을 실시간으로 캡처하며 여러 시스템 간 데이터를 동기화하고 감사 로그 및 규정 준수로 활용 가능합니다.
- 실시간 스트림 처리: Kafka Streams나 Apache Flink를 활용하여 실시간 분석 및 알림 시스템을 가능하게 합니다.
- 감사 로그와 운영 텔레메트리: 시스템 로그를 중앙 집중화하고 메트릭 수집 및 모니터링을 통해 장애 추적 및 성능 분석이 가능합니다.
RabbitMQ
RabbitMQ는 이미 실전에서 충분히 검증된 전통적인 큐잉 의미론에 기반한 범용 메시지 브로커입니다. 여기서 전통적인 큐잉 의미론이란 FIFO 방식을 따르며 한 번 처리되면 대기열에서 삭제하는 것을 의미합니다.
RabbitMQ는 아래의 주요 특성을 가집니다.
- Queue-first design: 메시지가 큐에 안전하게 저장되어 한번만 소비되는 Point-to-Point 패턴을 가집니다. 또한 큐와 승인(ACK) 매커니즘으로 데이터 유실을 방지합니다.
- Routing flexibility: 다양한 Exchange 타입을 제공하여 복잡한 라우팅 로직을 지원합니다.
- Protocol support: AMQP, MQTT, STOMP, HTTP 등 다양한 프로토콜을 지원합니다.
- Low latency: 가벼운 아키텍처로 빠른 응답을 지원하여 작업 디스패칭이나 동기 워크플로우를 지원할 수 있습니다.
이러한 특징을 가지고 있는 RabbitMQ의 핵심 사용 사례는 다음과 같습니다.
- 엄격한 순서가 필요한 트랜잭션 시스템: 금융 거래 처리, 주문 처리 워크 플로우, 결제 승인, 재고 관리 등의 시스템에서 사용할 수 있습니다.
- Worker 큐와 Task Orchestration: 백그라운드 작업을 처리하거나 이메일 발송 큐, 배치 작업 관리 등에서 사용할 수 있습니다.
- 단기 연결 Pub/Sub: 실시간 알림 시스템이나 채팅 애플리케이션, 라이브 업데이트, 캐시 무효화 등에서 사용할 수 있습니다.
참고자료
Messaging Patterns Explained: Pub-Sub, Queues, and Event Streams
Messaging Patterns Explained: Pub-Sub, Queues, and Event Streams
What happens when the downstream service is overloaded? Or slow? Or down entirely?
blog.bytebytego.com
'System Architecture' 카테고리의 다른 글
| HA 아키텍처 (0) | 2025.07.12 |
|---|---|
| 엔지니어링의 트레이드오프: 실제 환경에서의 최종적 일관성 (Eventual Consistency) (0) | 2025.05.29 |
| Slack은 어떻게 하루 수십억 개의 메시지를 처리할까 (0) | 2025.05.22 |
| 마이크로서비스 설계 패턴 핵심 요약 (0) | 2025.05.10 |
| 당신이 알아야 할 시스템 설계의 핵심 개념 20가지 - 2. 캐싱 (0) | 2025.05.01 |