Skip to content

✍️ 필사 모드: 메시지 큐와 이벤트 스트리밍 완전 비교 — Kafka, RabbitMQ, NATS, Pulsar, SQS, Redis Streams, CDC, Schema Registry 심층 가이드 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

왜 메시징이 모든 아키텍처의 허리가 되었는가

2010년대엔 "마이크로서비스"가 유행이었다면, 2020년대 중반은 **"이벤트 주도(Event-Driven)"**의 시대다. 이유:

  1. 서비스가 더 많이 쪼개졌다 — REST API만으론 결합도가 관리 불가.
  2. 실시간 요구 증가 — "이메일 도착 즉시 AI 분류" 같은 기대치.
  3. 분석/ML 파이프라인 상시화 — 이벤트가 곧 데이터셋.
  4. 서버리스 도입 — Lambda/Workers는 이벤트가 자연스러운 트리거.
  5. Kafka가 '기본 인프라'로 — LinkedIn 실험에서 범용 인프라로 격상.

이 글에선 **"메시지를 어떻게 안정적으로 흘려보낼 것인가"**를 끝까지 파고든다. 이전 글(분산 시스템, DB 내부)에서 쌓은 개념이 여기서 결합된다.

Part 1 — 메시지 큐 vs 이벤트 스트림 — 근본 차이

메시지 큐(Message Queue)의 모델

  • 작업(Task) 중심.
  • 소비자가 메시지를 가져가면 사라진다(destructive read).
  • 다수 소비자가 "누가 이걸 처리할지" 경쟁(Competing Consumers).
  • 대표: RabbitMQ, ActiveMQ, SQS.
Producer[Queue]Consumer1 (가져가면 끝)
Consumer2
Consumer3

이벤트 스트림(Event Stream)의 모델

  • 사실(Fact) 중심.
  • 메시지가 보존된다(append-only log).
  • 다수 소비자가 각자의 오프셋을 가지고 자유롭게 읽음.
  • 대표: Kafka, Pulsar, Kinesis, Redis Streams.
Producer[Log (append-only)]
               (보존)
  Consumer1 (offset=50) ← 분석 파이프라인
  Consumer2 (offset=120) ← 실시간 알림
  Consumer3 (offset=130) ← 감사 로그

어떻게 고르는가

요구사항추천
작업 분산, 큐 길이 관리메시지 큐 (RabbitMQ, SQS)
복잡한 라우팅(exchange)RabbitMQ
감사·재처리·ML 파이프라인이벤트 스트림 (Kafka)
리플레이·이벤트 소싱이벤트 스트림
작은 규모·가벼움NATS, Redis Streams
완전 관리형·서버리스SQS, EventBridge, Kinesis

하이브리드가 흔하다 — RabbitMQ로 작업 큐, Kafka로 이벤트 로그. 각자 잘하는 일이 다르기 때문.

Part 2 — Kafka 내부 해부

구조

Topic
 └── Partition 0 (Segment.log + Segment.index)
 └── Partition 1 (...)
 └── Partition 2 (...)

Partition은 여러 Broker에 복제 (Replication Factor)
Leader Partition + Follower Partitions (ISR)

파티션이 왜 중요한가

  • 병렬성의 단위 — 한 컨슈머 그룹 내, 한 파티션은 한 컨슈머에게만 할당.
  • 순서 보장의 단위 — 같은 파티션 내에서만 순서 보장.
  • 리밸런싱의 단위 — 컨슈머가 추가/삭제되면 파티션이 재분배.

파티션 설계 규칙:

  • 처음에 넉넉히 잡아라 — 줄이기가 어렵다.
  • Key 기반 파티셔닝으로 순서 보장 — 같은 key → 같은 파티션.
  • 단, "뜨거운 key"는 불균형의 주범.

세그먼트와 리텐션

파티션은 물리적으로 여러 세그먼트 파일로 나뉜다 (00000000000000000000.log 같은 형태).

  • 세그먼트 크기/시간 기준으로 롤오버.
  • 리텐션(retention.ms, retention.bytes)에 따라 오래된 세그먼트 삭제.
  • 또는 Log Compaction — 같은 key의 최신 값만 남기고 정리.

ISR (In-Sync Replicas)

  • 리더의 로그와 "충분히 가까운" 팔로워들의 집합.
  • acks=all + min.insync.replicas=2 → 강한 내구성.
  • 리더 실패 시 ISR 중에서 새 리더 선출.

2012 LinkedIn 사건: min.insync.replicas=1로 설정되어 있어, ISR에서 이탈한 리더만 남아 데이터 손실. 이후 업계가 =2 이상을 표준화.

KRaft (2022 GA, 2024 기본)

  • ZooKeeper 제거, Kafka 자체 Raft로 메타데이터 관리.
  • 대규모 클러스터에서 메타데이터 업데이트가 10-100배 빠름.
  • Kafka 4.0(2025)부터 ZooKeeper 모드는 지원 종료 예정.

Transactional Producer

이전 글(분산 시스템)에서 언급한 Kafka Exactly-Once. 핵심:

producer.initTransactions();
producer.beginTransaction();
producer.send(record1);  // topic-A
producer.send(record2);  // topic-B
producer.sendOffsetsToTransaction(offsets, groupId);  // 소비 오프셋까지 원자적
producer.commitTransaction();

read-process-write 패턴에 필수. 단, 소비자의 isolation.level=read_committed 필요.

Kafka 생태계

  • Kafka Streams — JVM 기반 스트림 처리 라이브러리.
  • ksqlDB — SQL로 스트림 쿼리.
  • Kafka Connect — 소스/싱크 커넥터 프레임워크(JDBC, S3, Elasticsearch...).
  • Schema Registry — Avro/Protobuf/JSON Schema 중앙화.
  • MirrorMaker 2 — 멀티 리전 복제.

Part 3 — 강력한 대안들

RabbitMQ — 유연한 라우팅의 왕

2007년 출시, Erlang 기반. AMQP 0-9-1 표준 구현체.

Exchange 타입:

  • Direct — routing key 정확 매칭.
  • Fanout — 모든 바인딩 큐에 broadcast.
  • Topic — 패턴 매칭(예: order.us.*).
  • Headers — 헤더 기반 매칭.
ProducerExchange  (routing)Queue(s)Consumer

언제 RabbitMQ?

  • 복잡한 라우팅 규칙이 필요.
  • 메시지 수명이 짧고 가변적(큐 길이 관리).
  • 우선순위 큐, DLQ(Dead Letter Queue).
  • 수천 개의 논리적 큐가 필요.

2023년 RabbitMQ Streams — Kafka 스타일 append-only 로그 추가. 단, Kafka만큼 성숙하진 않음.

NATS + JetStream — 가볍지만 강력

2011년 시작, 2020년 JetStream 등장. 단일 Go 바이너리.

특징:

  • Core NATS: 초경량 pub/sub (at-most-once).
  • JetStream: 영속성·리플레이·exactly-once 지원.
  • 클러스터 10줄 설정으로 가능.
  • K8s 친화 — Operator, Leaf Node 연합.
  • Subject 기반events.order.created.us 같은 계층 네임스페이스.

언제 NATS?

  • Kafka는 과하고 RabbitMQ는 운영 부담되는 규모.
  • IoT·엣지 — 작은 풋프린트.
  • 멀티 클러스터 레플리케이션이 핵심.

2024년 NATS 2.10: 기본적인 K/V 스토어, Object Store 내장. Redis + Kafka의 축소판을 단일 바이너리로 제공.

Apache Pulsar — 분리의 철학

Yahoo 2016년 기증, 지금 Apache TLP.

핵심 아키텍처:

  • 브로커: stateless, 라우팅만 담당.
  • BookKeeper: 실제 로그 저장. 브로커와 분리.
  • ZooKeeper: 메타데이터.

장점:

  • 브로커 확장 ≠ 스토리지 확장 — 독립적.
  • 지오리플리케이션 1급 시민 — 클러스터 간 자동 복제.
  • 멀티테넌시 성숙 — namespace, tenant 개념 내장.

단점:

  • 3계층 구조라 운영 복잡.
  • 커뮤니티 Kafka보다 작음.

적합 사례: 금융기관, 통신사 등 멀티 리전 + 멀티테넌시가 필수인 곳.

Redpanda — Kafka 호환, C++ 재작성

2019년 Vectorized(지금 Redpanda Data) 창업.

  • Kafka 프로토콜 100% 호환 → 클라이언트·도구 그대로.
  • Seastar framework, thread-per-core, DPDK 활용.
  • ZooKeeper/KRaft 없이 내장 Raft.
  • fsync 없이도 안전 주장 — WAL + Raft.
  • 레이턴시가 Kafka의 1/10 수준 벤치.

2024년 Redpanda Cloud BYOC(Bring Your Own Cloud) 출시로 금융권 주목.

AWS 메시징 4대 서비스

서비스모델특징
SQS메시지 큐FIFO·Standard, 최대 14일 보존, 서버리스
SNSpub/subfan-out, 구독자별 필터, 이메일·SMS 지원
Kinesis Data Streams이벤트 스트림Kafka스러운 모델, 샤드 단위
EventBridge이벤트 버스스키마 레지스트리·규칙 기반 라우팅, 통합 허브

2023년 MSK Serverless + Kinesis On-Demand로 용량 관리 부담이 크게 줄었다. 스타트업의 시작점은 점점 "관리형"쪽으로 이동.

Redis Streams — 5.0부터 정식

XADD events * type order.created user 123
XREADGROUP GROUP workers consumer-1 COUNT 10 STREAMS events >
XACK events workers <id>
  • 메모리 기반이라 매우 빠름.
  • Consumer Group 지원.
  • PEL(Pending Entries List)로 ack 관리.
  • 단점: 디스크 내구성은 AOF에 의존 — 메모리 한계 = 데이터 한계.

언제 쓰나? 이미 Redis를 쓰고 있고, 규모가 수백만 msg/day 이하라면 추가 인프라 없이 충분.

Part 4 — CDC(Change Data Capture)

왜 CDC인가

DB에 발생한 변경을 이벤트로 외부에 전파하는 기술. 이전 글(DB 내부)의 WAL/logical replication이 CDC의 기반.

3가지 방식

1. Query 기반 (폴링)

SELECT * FROM orders WHERE updated_at > $last_checkpoint;
  • 간단하지만 DELETE 감지 불가.
  • 부하 발생.
  • 실시간성 낮음.

2. Trigger 기반

  • DB 트리거가 변경 시 보조 테이블에 기록.
  • 정확하지만 쓰기 성능에 영향.
  • DB 간 이식 어려움.

3. Log 기반 (최선)

  • DB의 WAL/binlog를 직접 파싱.
  • 성능 영향 최소.
  • DELETE까지 정확히 감지.

Debezium

log-based CDC의 표준. Kafka Connect 커넥터로 제공.

# Debezium PostgreSQL 커넥터
connector.class: io.debezium.connector.postgresql.PostgresConnector
database.hostname: pg.internal
plugin.name: pgoutput
publication.name: my_publication
slot.name: debezium_slot
topic.prefix: prod

지원 DB: PostgreSQL, MySQL, MongoDB, Oracle, SQL Server, DB2, Cassandra, Vitess.

Outbox Pattern + Debezium

이전 글(분산)에서 언급한 Outbox Pattern을 Debezium이 자동화.

BEGIN;
  INSERT INTO orders (...);
  INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload)
    VALUES ('Order', 123, 'OrderCreated', '{...}');
COMMIT;

Debezium이 outbox 테이블의 INSERT를 WAL에서 잡아 Kafka로 라우팅. 트랜잭션 보장 + 이벤트 발행을 원자적으로 달성.

Fivetran / Airbyte

완전 관리형 CDC + ELT. 엔지니어링 리소스가 적은 조직은 Debezium 대신 SaaS 선택.

Part 5 — 스키마 관리 — 만드는 순간 영원한 문제

왜 스키마가 중요한가

이벤트는 여러 팀이 공유한다. 누군가 필드를 제거하면, 3년 전 이벤트를 읽던 다른 팀이 깨진다. 이걸 막는 게 스키마 거버넌스.

직렬화 포맷

포맷크기속도스키마 진화인간 가독성
JSON크다느리다수동좋음
Avro작다빠르다강력바이너리
Protobuf작다매우 빠르다강력바이너리
MessagePack작다빠르다약함바이너리
JSON Schema크다보통검증 가능좋음

2024년 업계 기본: 내부 이벤트는 Avro or Protobuf, 외부 API는 JSON + JSON Schema.

Schema Registry

Confluent Schema Registry (Kafka 생태계 표준). 호환성 체크:

  • BACKWARD — 새 스키마로 과거 데이터 읽기 가능.
  • FORWARD — 과거 스키마로 새 데이터 읽기 가능.
  • FULL — 양방향 호환.
  • NONE — 검증 안 함(위험).

필드 추가 시: 기본값 포함해 BACKWARD 호환 유지. 필드 제거 시: 먼저 선택적(optional)으로 만들고, 나중에 제거.

Buf + Protobuf

Buf CLI가 2022년 이후 Protobuf 진화의 표준화를 주도.

  • buf lint — 스타일 검증.
  • buf breaking — 깨지는 변경 감지.
  • Buf Schema Registry — Confluent와 경쟁.

Part 6 — 이벤트 주도 설계 — 현실의 함정

Event-Driven의 이론과 현실

이론:

  • "느슨한 결합"
  • "독립적 배포"
  • "확장성"

현실에서 겪는 문제:

1. Eventual Consistency의 UX 문제

사용자가 "저장" 누르고 새로고침했는데 값이 안 보인다. Read-your-writes 보장 필요.

2. 이벤트 스키마 통제 실패

3년 차에 접어들면 "이 이벤트가 뭔지 아는 사람이 없다." 카탈로그 필수.

3. 디버깅의 어려움

REST 호출 스택 트레이스 1개 vs 이벤트 5개를 따라가는 분산 로그. 분산 추적 필수.

4. 순서·중복 가정 실수

컨슈머가 "같은 순서 + 중복 없음"을 가정하면 언젠가 무너진다.

5. 리플레이 실수

"지난 주 이벤트 재처리"가 프로덕션 상태를 덮어쓸 수 있다.

Event Storming

2013년 Alberto Brandolini가 제안. 포스트잇으로 도메인 이벤트를 빠르게 모델링.

  • 주황: Domain Event
  • 파랑: Command
  • 노랑: Aggregate
  • 분홍: External System
  • 보라: Policy

제품 설계 단계에서 EDA의 유일한 성공 비결이라는 평가가 나올 정도로 핵심.

Part 7 — 운영의 현실

관측성(Observability)

  • 메시지 레이턴시 — producer → broker → consumer 각 구간.
  • 컨슈머 랙(Lag) — 가장 중요한 단일 지표. Kafka의 __consumer_offsets 파싱.
  • 리밸런싱 빈도 — 너무 잦으면 튜닝 필요.
  • DLQ 증가율 — 처리 실패의 누적.

흔한 장애 패턴

  1. 컨슈머 랙 폭증 — 컨슈머 확장 또는 파티션 증설.
  2. 리밸런싱 폭주 — 세션 타임아웃/heartbeat 튜닝, cooperative rebalance.
  3. Producer 버퍼 가득linger.ms, batch.size 튜닝.
  4. Broker 디스크 폭증 — 리텐션·압축 설정 재검토.
  5. Zombie Producer — Transactional ID 미사용 시 중복 전송.

Exactly-Once의 현실

이전 글에서 말했듯, 최종 효과 관점에서만 Exactly-Once가 가능. 실무 패턴:

  • 멱등 UpsertINSERT ... ON CONFLICT DO NOTHING/UPDATE.
  • 중복 체크 테이블 — event_id 유일성 제약.
  • Transactional Outbox — DB 커밋과 이벤트 발행을 원자적으로.

Part 8 — 실무 체크리스트 (12항목)

  1. Queue vs Stream 선택을 설계 초기에 — 나중에 바꾸기 어렵다.
  2. Kafka 파티션 수는 넉넉히 — 줄이기 불가에 가깝다.
  3. Key 기반 파티셔닝으로 순서 보장 — 같은 엔티티는 같은 파티션.
  4. min.insync.replicas=2acks=all과 함께 설정.
  5. Schema Registry를 day-1부터 — 나중에 도입하면 이미 늦다.
  6. **Consumer Group의 max.poll.interval.ms**를 충분히 — 느린 처리 허용.
  7. DLQ를 반드시 설계 — 처리 실패는 100% 발생한다.
  8. CDC는 Debezium 우선 고려 — 폴링 방식은 부하 문제를 낳는다.
  9. 멱등성 있는 컨슈머 — Exactly-Once는 이걸로 달성.
  10. 관측성 3종 세트 — 랙, 처리량, 에러율.
  11. 리플레이 훈련 — 과거 이벤트 재처리 시나리오를 테스트.
  12. 이벤트 카탈로그 — 어떤 이벤트가 어디서 발행되는지 문서화.

Part 9 — 10대 안티패턴

  1. Kafka를 요청/응답 RPC로 사용 — gRPC나 REST가 적합.
  2. 파티션 수를 소극적으로 잡음 — 초기 용량 부족이 엔드리스 마이그레이션으로.
  3. 모든 이벤트를 하나의 토픽에 — 도메인별 분리가 건강한 시작점.
  4. JSON without Schema Registry — "나중에 정리하자"가 기술부채.
  5. 큰 페이로드(MB) — 메시징 시스템은 작은 메시지에 최적화. S3 링크로 대체.
  6. 순서 보장 없이 순서 기대 — 파티션 간 순서는 없다.
  7. 컨슈머가 DB 동기 호출 여러 번 — 처리량 붕괴.
  8. Outbox Pattern 없이 DB + Kafka 이중 쓰기 — 데이터 불일치 보장.
  9. 이벤트 명명 일관성 없음OrderCreated vs orderCreated 혼재.
  10. 모든 것을 이벤트로 — 사용자 프로필 조회까지 이벤트화하면 오히려 복잡.

Part 10 — 학습 리소스

  • 책: Designing Event-Driven Systems (Ben Stopford, Confluent)
  • 책: Kafka: The Definitive Guide, 2nd Ed (Narkhede et al.)
  • 책: Flow Architectures (James Urquhart)
  • 블로그: Confluent Blog, Redpanda Blog, Uber Engineering.
  • 코스: Tim Berglund의 Kafka Fundamentals (Confluent Developer).
  • 실전: Kafka 소스에서 DistributedHerder.java, GroupCoordinator.scala 읽기.

마치며 — 흐름을 설계하는 엔지니어링

"상태(State)"를 다룬 글(DB 내부) 다음, "흐름(Flow)"을 다룬 이 글은 현대 백엔드 아키텍처의 두 축이다. 한쪽은 진실의 보관소, 다른 쪽은 진실의 전파.

2025년의 엔지니어에게 메시징은 단순한 '전송 기술'이 아니다. 시스템 설계의 언어다. 어떤 이벤트를 정의하고, 누가 발행하고, 누가 구독하며, 어떻게 진화시키는가 — 이 네 질문이 아키텍처의 절반을 결정한다.

좋은 이벤트 설계는 조직 구조를 반영한 모델이다. 나쁜 이벤트 설계는 조직을 가둔다. Conway의 법칙이 메시징에서 가장 잔인하게 드러난다.

다음 글 예고 — "현대 프론트엔드 상태 관리의 진화" — Redux, Zustand, Jotai, Signals, TanStack Query, Server State 완전 해부

우리는 지금까지 백엔드의 흐름을 다뤘다. 다음 글은 프론트엔드의 상태다.

  • 왜 Redux가 한 시대를 지배했는가 — 그리고 왜 이제 사람들이 떠나는가
  • Zustand, Jotai, Valtio의 미니멀리즘 — 한 줄로 상태를 정의한다
  • Signals의 부활 — SolidJS에서 Angular·Vue, 그리고 React까지
  • Server State vs Client State 분리 — TanStack Query(React Query) 혁명
  • Optimistic UI의 공식 — 네트워크가 느려도 빠르게 느껴지는 법
  • RSC(React Server Components)와 상태의 재정의 — 2024-2025년 큰 변화
  • Form 상태(React Hook Form, Tanstack Form, Formik) — 왜 따로 관리하는가
  • Undo/Redo, Time-travel — 상태 이력을 설계하는 법
  • 상태의 정규화(Normalization) — 중첩과 중복의 비용
  • 상태 머신(XState) — 복잡한 UI 로직을 수학으로 정리하기

프론트엔드의 "머리 아픈 것 90%"가 상태다. 다음 글에서 해부한다.

현재 단락 (1/249)

2010년대엔 "마이크로서비스"가 유행이었다면, 2020년대 중반은 **"이벤트 주도(Event-Driven)"**의 시대다. 이유:

작성 글자: 0원문 글자: 9,188작성 단락: 0/249