Message Brokers & Stream Processing
비동기 메시지 전달을 위한 미들웨어의 물리적 저장 방식과, 실시간으로 쏟아지는 데이터 파이프라인을 처리하는 스트림 프로세싱 물리학을 다루는 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
system-architecture-distributed-systemssystem-architecturedistributed-systemsevent-drivenreactive-systemsmessage-brokersstream-processingcqrs9 min read
1. Overview
메시지 브로커와 스트림 프로세싱(Message Brokers & Stream Processing)은 수백 개의 마이크로서비스가 서로에게 직접 API를 쏘다가 연쇄 폭발(Cascading Failure)하는 참사를 막고, 중간에 거대한 완충재(Buffer)를 두어 시스템을 decoupling(결합 분리)하는 비동기 인프라의 심장을 해부합니다.
학습자는 메시지를 쏘고 잊어버리는(Fire and Forget) 비동기 통신의 우아함을 배우고, 다 쓴 메시지를 즉시 지워버리는 전통적인 **메시지 큐(RabbitMQ, SQS)**와, 메시지를 지우지 않고 무한히 쌓아두며 빅데이터 파이프라인의 척추가 된 **스트림 브로커(Kafka, Kinesis)**의 철학적 차이를 뜯어봅니다. 나아가 메시지가 유실되지 않음을 보장하는 3가지 전송 수준(At-most-once, At-least-once, Exactly-once)의 물리적 비용을 저울질합니다. 마지막으로, 끝없이 쏟아지는 무한한 데이터의 강물(Stream) 속에서 실시간으로 필터링하고 집계하는 **스트림 프로세싱(Flink, Kafka Streams)**의 아키텍처를 설계하는 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- Message Brokers (메시지 브로커): RabbitMQ, AWS SQS, ActiveMQ (AMQP 기반).
- Stream Platforms (스트림 플랫폼): Apache Kafka, AWS Kinesis (Append-only Log 기반).
- Delivery Guarantees (전송 보장): At-most-once(최대 1번), At-least-once(최소 1번), Exactly-once(정확히 1번).
- Stream Processing (스트림 처리): Windowing(윈도우 쪼개기), Watermark(지연 데이터 처리), 실시간 집계.
Out-of-Scope
- Pub/Sub 디자인 패턴 원리: 소프트웨어 레벨의 Observer 패턴 07-04-02 Pub-Sub & Event-driven Flows 영역.
- 분산 트랜잭션 (Saga Pattern): 메시지 큐를 이용한 데이터 정합성 보장 07-02-04 Distributed Transactions 영역으로 위임.
Boundaries
- Message Queue vs Event Stream: RabbitMQ는 "주문이 들어왔으니 배송을 시작해"라는 '명령(Command)'을 전달하기 위한 '우체국'입니다. 수신자가 택배를 받으면(ACK) 큐에서 즉시 삭제됩니다. 반면 Kafka는 "12시 5분에 주문이 들어왔음"이라는 '과거의 사실(Event)'을 영구히 기록하는 '도서관(Append-only Log)'입니다. 데이터가 삭제되지 않으므로, 장애가 났던 다른 서비스가 3일 뒤에 살아나서 과거의 데이터를 다시 읽어들일(Replay) 수 있습니다. 이 두 인프라를 "둘 다 메시지를 보내는 거니까 아무거나 쓰자"고 섞어 쓰는 것은 대규모 비동기 아키텍처를 완전히 망치는 안티 패턴임을 명확히 구분합니다.
3. Counterexample
- 동기 호출(Sync)의 연쇄 폭발: 블랙프라이데이에 트래픽이 몰렸습니다. 주문 서비스가 결제 서비스로 REST API(동기)를 호출했는데, 결제 서비스가 느려져 3초간 응답을 안 합니다. 주문 서비스의 스레드가 3초간 블로킹(대기)되고, 10초 만에 주문 서비스의 모든 스레드가 고갈되어 전체 쇼핑몰이 다운되었습니다. 중간에 메시지 큐(Kafka/RabbitMQ)를 두어 "주문받았음(200 OK)"을 유저에게 바로 던지고 결제는 비동기로 처리하게 하는 Decoupling 아키텍처를 무시한 참사입니다.
- At-least-once와 멱등성(Idempotency)의 결여: Kafka에서 메시지를 읽어 결제를 승인하는 컨슈머(Consumer)를 만들었습니다. 결제를 승인(DB 저장 완료)하고 Kafka에 "나 이거 처리했음(Commit)"이라고 알려주려는 찰나에 컨슈머가 죽었습니다. 다시 살아난 컨슈머는 이전에 커밋되지 않은 동일한 결제 메시지를 또 읽어서 2중 결제를 내버립니다. At-least-once(최소 한 번) 환경에서는 하나의 메시지가 무조건 두 번 이상 들어올 수 있다고 가정해야 하며, 수신 측(DB)에 '멱등성(Idempotent) 키' 방어막을 씌우지 않은 백엔드 설계의 치명적 결함입니다.
4. Prerequisites
- 분산 로그 원리 (Basic): Append-only 로그의 구조. (07-02-02 Consensus & Distributed Log)
- 비동기 프로그래밍 기초 (Basic): 콜백과 논블로킹 I/O. (04-06 Functional & Async)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 결합의 단절과 버퍼, 메시지 큐 (The Decoupling Power)
- Why to Learn: MSA 환경에서 서버끼리 API를 직접 호출할 때 겪게 되는 병목과 장애 전파를, 중간에 '거대한 우체통'을 세워 완벽히 단절(Decoupling)시키기 위함입니다.
- What to Learn:
- Concepts: Message Broker, Point-to-Point, Producer/Consumer, Asynchronous Communication, Buffer (완충재).
- Skills: 피크 트래픽 발생 시 트래픽 스파이크(Spike)를 큐(Queue)를 통해 평탄화(Leveling)하는 아키텍처 설계.
- How to Learn:
- 1단계: 동기 통신의 파국: 주문(10ms) 재고(20ms) 배송(50ms). 배송 서버가 죽으면 주문 서버도 에러를 내뱉는 미친 강결합(Coupling)을 해부합니다.
- 2단계: 비동기 완충재(Buffer): 주문 서버는 그저 큐(Queue)에 "배송해라"라는 메모지만 꽂아놓고 유저에게 "주문 완료!"를 때립니다(10ms). 배송 서버가 터져서 메모지가 수만 장 쌓여도, 주문 서버는 전혀 타격받지 않고 계속 돈을 벌어들이는 비동기의 위력을 뜯어봅니다.
- Implement: 트래픽 스파이크(Spike) 흡수 시뮬레이터. Producer가 1초에 1,000개의 이벤트를 발생시키는데, Consumer는 1초에 10개밖에 처리 못 함. 중간에 큐(Queue) 리스트를 두어, Producer는 전혀 블로킹되지 않고 이벤트를 쏘며, Consumer는 자기 속도에 맞춰 큐에서 뽑아 먹는(Rate Limiting) 디커플링 로직 렌더링.
Recommended
Core Topic 02: 우체국과 도서관, MQ vs Kafka (Queue vs Stream)
- Why to Learn: 대용량 비동기 처리를 도입할 때, 일회성 워커(Worker) 분산이 목적인지 과거 데이터의 영속성(Persistence) 보장이 목적인지에 따라 RabbitMQ와 Kafka를 정확히 취사선택하기 위함입니다.
- What to Learn:
- Concepts: Smart Broker / Dumb Consumer (RabbitMQ), Dumb Broker / Smart Consumer (Kafka), Append-only Log, Retention Period.
- Skills: 비즈니스 요구사항에 따른 브로커 타입 결정 및 인프라 설계.
- How to Learn:
- 1단계: RabbitMQ (Smart Broker): 큐에 100개의 메일이 있습니다. 컨슈머 3명이 붙으면 브로커가 "너 이거 가져가, 너는 저거 가져가" 하고 골고루 나눠줍니다(Round-Robin). 다 읽은 메일은 브로커가 똑똑하게 즉각 삭제(Pop)하여 큐를 텅 비우는 작업을 해부합니다.
- 2단계: Kafka (Dumb Broker): 카프카는 자기가 뭘 나눠주지 않습니다. 그냥 디스크(Log)에 데이터를 순서대로 무식하게 쌓아둡니다. 컨슈머들이 알아서 "나 5번 줄까지 읽었어(Offset)" 하고 기억합니다. 데이터가 안 지워지므로, 새로운 AI 분석 시스템이 들어와도 "0번 줄부터 다시 다 읽어!"가 가능한 마법을 뜯어봅니다.
- Implement: Kafka식 Append-only 로그와 Offset 읽기 스크립트. 배열(Log)에 계속 이벤트를
append(). Consumer A는offset_A변수를, Consumer B는offset_B변수를 따로 관리하여 같은 배열(Log)을 서로 완전히 독립적인 진도(Speed)로 재생(Replay)하는 과정 콘솔 출력.
Practical
Core Topic 03: 배달의 딜레마와 멱등성 (Delivery Guarantees & Idempotency)
- Why to Learn: 네트워크라는 불확실한 통로를 통해 메시지를 보낼 때 발생하는 유실과 중복의 물리학을 깨닫고, DB의 유니크 키(Unique Key)로 이를 완벽히 틀어막는 백엔드 방어막을 세우기 위함입니다.
- What to Learn:
- Concepts: At-most-once(유실 가능성), At-least-once(중복 가능성), Exactly-once(완벽한 1번), Idempotency(멱등성), ACK/NACK.
- Skills: At-least-once 환경에서 중복 메시지가 들어와도 상태가 변하지 않는 멱등한(Idempotent) Consumer 설계.
- How to Learn:
- 1단계: Exactly-once의 허상: 브로커가 컨슈머에게 메시지를 줍니다. 컨슈머가 잘 받았다는 ACK를 브로커에게 쏘려다 네트워크가 끊겼습니다. 브로커는 "안 갔나 보네?" 하고 다시 보냅니다. 결국 메시지는 두 번 들어갑니다. 완벽한 1번 전송은 분산 시스템에서 엄청난 오버헤드를 동반함을 해부합니다.
- 2단계: 멱등성(Idempotency) 방어선: 세상의 모든 브로커는 '최소 1번(중복 가능)'을 기본값으로 씁니다. 대신 백엔드 컨슈머가 똑똑해집니다. 메시지 안에
UUID(주문번호)를 박아놓고, DB에INSERT할 때주문번호를 유니크 인덱스로 겁니다. 똑같은 메시지가 두 번 들어오면 두 번째는 DB 에러(Duplicate)로 무시해 버려 멱등성을 달성하는 구조를 뜯어봅니다.
- Implement: 멱등성(Idempotency) 컨슈머 데모.
[MSG_001: 결제 승인],[MSG_001: 결제 승인 (중복)]리스트. 컨슈머가 메시지를 받아 처리할 때, 로컬Processed_Set에 ID가 있으면 "중복 무시됨"을 출력하고, 없으면 잔액을 차감하는 방어적 프로그래밍 패턴.
Advanced
Core Topic 04: 시간의 통제, 스트림 프로세싱 (Stream Processing)
- Why to Learn: 데이터가 디스크에 쌓이기(Batch)를 기다리지 않고, 카프카(Kafka)로 흘러가는 물결(Stream) 속에서 1초 단위로 매출액을 집계해 내는 실시간 빅데이터 아키텍처를 장악하기 위함입니다.
- What to Learn:
- Concepts: Stream Processing (Apache Flink, Kafka Streams), Windowing (Tumbling, Sliding), Event Time vs Processing Time, Watermark.
- Skills: 무한한 데이터 스트림을 특정 시간 조각(Window)으로 잘라(Slice) 상태(Stateful) 집계하기.
- How to Learn:
- 1단계: Windowing (시간 자르기): 데이터가 끝이 없습니다. "어제 매출액"을 구할 수가 없습니다. 그래서 흐르는 강물을 1분 단위로 툭툭 끊어냅니다(Tumbling Window). "12<00>00>~12<01>01> 사이의 결제 건수"를 실시간 메모리에서 더해버리는 스트림 집계의 마법을 해부합니다.
- 2단계: 지연 데이터와 워터마크(Watermark): 12시 5분에 발생한 모바일 결제 데이터가 폰이 터널에 들어가서 12시 10분에 서버에 도착했습니다(Event Time Processing Time). 12시 5분 집계를 마감해야 할지 말아야 할지 알려주는 기준선인 워터마크 기술을 뜯어봅니다.
- Implement: 실시간 윈도우(Window) 집계 스크립트. 파이썬
yield제너레이터로 0.1초마다 이벤트를 방출(Stream). 컨슈머 쪽에서 이벤트를 큐에 담다가, 1초(Tumbling Window)가 찰 때마다 큐 안의가격을 모두sum()해서 터미널에 뱉어내고 큐를 비우는 실시간 집계 시뮬레이션.
7. Terminology
8. References
Primary
- [P1] CS2023 - Software Engineering (SE) - Distributed Systems (Message Brokers)
- [P5] SFIA - Data Engineering (DATD) - Stream Processing
Secondary
- [Designing Data-Intensive Applications] Martin Kleppmann - Message Brokers and Event Streams
- [Kafka: The Definitive Guide] Neha Narkhede - Kafka Architecture
Industry
- [Confluent Blog] - Exactly-once Semantics in Apache Kafka
- [Uber Engineering Blog] - How Uber Uses Kafka for Streaming Architecture
9. Final Checklist
Primary
- 수백 개의 마이크로서비스가 서로 동기식(REST API)으로 강하게 얽혀 있을 때 단일 서비스 장애가 전체 시스템 붕괴로 이어지는 현상을, 메시지 큐(Message Queue)가 어떻게 물리적으로 단절(Decoupling) 시키는지 설명할 수 있는가?
- 메시지를 소비하면 즉시 지워버리는 RabbitMQ(Smart Broker)와, 메시지를 디스크에 영구히 남기고 컨슈머가 오프셋(Offset)을 관리하는 Kafka(Dumb Broker)의 아키텍처적 차이를 비교할 수 있는가?
Secondary
- 분산 네트워크에서 브로커와 컨슈머 간의 통신 시 완벽한 'Exactly-once(정확히 한 번 전송)'를 보장하는 것이 왜 엄청난 지연(Latency)을 동반하며, 대신 'At-least-once(최소 한 번)' 모델과 컨슈머의 '멱등성(Idempotency)' 결합으로 우회하는지 논증할 수 있는가?
- 컨슈머 그룹(Consumer Group)을 설정하여, 1번 토픽(Topic)으로 들어오는 초당 10,000개의 이벤트를 5대의 서버가 2,000개씩 골고루 나누어(Scale-out) 병렬 처리하는 카프카의 큐잉 역학을 해부할 수 있는가?
Industry
- Flink나 Kafka Streams 같은 스트림 프로세싱에서 끝없는 데이터 흐름을 계산하기 위해 도입한 텀블링 윈도우(Tumbling Window)와 슬라이딩 윈도우(Sliding Window)의 차이를 시간 쪼개기 관점으로 설계할 수 있는가?
- 이벤트가 발생한 시간(Event Time)과 서버에 도착한 시간(Processing Time)이 어긋났을 때(Late Events), 워터마크(Watermark)를 설정하여 어디까지 과거 데이터를 기다려줄 것인지 실시간 파이프라인의 기준선을 튜닝할 수 있는가?