WebSocket 채팅으로 이해하는 Kafka Pub/Sub 오해
중년개발자
@loxo
약 10시간 전
WebSocket 채팅으로 이해하는 Pub/Sub과 Kafka
Kafka를 처음 이해할 때는 Pub/Sub(발행–구독) 구조로 접근하는 것이 가장 쉽습니다. 다만 Kafka의 진짜 강점은 Pub/Sub을 넘어, 메시지를 보관하고 다시 읽을 수 있는 분산 이벤트 로그라는 데 있습니다.
1. Pub/Sub의 핵심 개념
일반적인 프로그램은 보내는 쪽이 받는 쪽을 직접 지정합니다.
철수 → 영희에게 메시지 전송Pub/Sub에서는 메시지를 특정 사람에게 보내지 않고 **주제(Topic)**에 발행합니다.
철수 → "개발자 채팅방"에 메시지 발행
영희·민수·지수 → "개발자 채팅방"을 구독발행자는 구독자가 누구인지 알 필요가 없습니다. 구독자도 발행자와 직접 연결될 필요가 없습니다. 중간의 Kafka나 Redis가 메시지를 전달합니다.
이렇게 보내는 쪽과 받는 쪽을 분리하는 것을 **느슨한 결합(Loose Coupling)**이라고 합니다.
2. WebSocket과 Pub/Sub은 역할이 다르다
채팅 시스템에서 둘을 혼동하기 쉽지만 역할이 분명히 다릅니다.
| 기술 | 역할 |
|---|---|
| WebSocket | 브라우저와 채팅 서버를 실시간으로 연결 |
| Redis/Kafka | 여러 채팅 서버 사이에서 메시지를 전달 |
| 데이터베이스 | 채팅 내역을 영구 저장 |
WebSocket은 사용자와 서버 사이의 통신로이고, Pub/Sub은 서버와 서버 사이의 방송망이라고 생각하면 쉽습니다.
3. 서버가 한 대라면 Pub/Sub이 필요 없다
서버가 한 대뿐이라면 같은 서버에 연결된 사용자들에게 바로 전송하면 됩니다.
채팅 서버는 다음과 같은 WebSocket 연결 목록을 메모리에 보관합니다.
room:developer
├─ 철수 WebSocket
├─ 영희 WebSocket
└─ 민수 WebSocket철수가 메시지를 보내면 서버가 해당 방에 연결된 모든 WebSocket으로 메시지를 전달합니다.
문제는 사용자가 늘어나 서버를 여러 대로 확장할 때 발생합니다.
4. 서버가 여러 대면 Redis나 Kafka가 필요하다
사용자들이 서로 다른 서버에 접속했다고 가정해 보겠습니다.
철수가 서버 1에 메시지를 보냈지만 영희는 서버 2, 민수는 서버 3에 연결되어 있습니다. 서버 1은 다른 서버가 어떤 사용자를 관리하는지 모릅니다.
이때 Pub/Sub이 중간 방송국 역할을 합니다.
메시지 전달 순서
실제 흐름은 다음과 같습니다.
- 철수가 WebSocket으로 서버 1에 메시지를 보냅니다.
- 서버 1이 메시지를
room.developer토픽에 발행합니다. - Redis 또는 Kafka가 구독 중인 채팅 서버에 전달합니다.
- 각 서버는 자신에게 연결된 방 사용자에게 WebSocket으로 전송합니다.
- 필요하면 별도의 저장 서비스가 데이터베이스에 보관합니다.
5. 중요한 함정: Kafka Consumer Group
Kafka에서 같은 Consumer Group에 서버 3대를 넣으면 메시지가 세 서버 모두에 전달되는 것이 아니라, 일반적으로 그중 한 서버에만 분배됩니다.
이는 주문 처리처럼 하나의 이벤트를 한 번만 처리할 때 유용합니다. 하지만 채팅 메시지는 각 서버에 연결된 사용자 모두가 받아야 할 수 있습니다.
따라서 Kafka로 채팅 방송을 구현한다면 다음과 같은 설계가 필요합니다.
- 채팅 서버별 Consumer Group을 다르게 구성
- 방이나 사용자 기준으로 라우팅하는 별도 Gateway 구성
- Kafka는 저장·후속 처리를 담당하고, 실시간 방송은 Redis Pub/Sub으로 분리
단순 채팅에서는 마지막 방식이 실용적입니다.
6. Redis Pub/Sub과 Kafka의 차이
둘 다 발행과 구독이 가능하지만 성격이 전혀 다릅니다.
| 항목 | Redis Pub/Sub | Kafka |
|---|---|---|
| 핵심 성격 | 실시간 방송 | 분산 이벤트 로그 |
| 메시지 보관 | 보관하지 않음 | 디스크에 보관 |
| 구독자가 꺼져 있을 때 | 메시지 유실 | 나중에 다시 읽기 가능 |
| 과거 메시지 재처리 | 불가능 | 가능 |
| 처리 위치 관리 | 없음 | Offset으로 관리 |
| 구성 난이도 | 비교적 간단 | 상대적으로 복잡 |
| 적합한 용도 | 실시간 알림, 채팅 방송 | 주문, 결제, 로그, 이벤트 처리 |
| 전달 방식 | 구독자 모두에게 즉시 방송 | Consumer Group에 따라 방송 또는 분산 처리 |
Redis Pub/Sub
영희가 접속하지 않은 동안 메시지가 발행되면 영희는 그 메시지를 받을 수 없습니다.
메시지는 전파되지만 기록은 남지 않는다.Kafka
메시지가 토픽에 일정 기간 저장됩니다. 소비자는 자신이 어디까지 읽었는지를 Offset으로 관리합니다.
메시지가 전파되는 동시에 기록으로 남는다.Kafka는 단순한 확성기라기보다 녹음 기능이 있는 방송국에 가깝습니다.
참고로 Redis에도 메시지 저장과 재처리를 지원하는 Redis Streams가 있습니다. Kafka와 비교할 대상은 Redis Pub/Sub보다 Redis Streams가 더 가깝습니다.
7. 현실적인 채팅 시스템 구조
실제 서비스에서는 역할을 나누는 구성이 좋습니다.
각 기술의 역할은 다음과 같습니다.
- WebSocket: 접속 중인 사용자와 실시간 통신
- Redis Pub/Sub: 여러 WebSocket 서버에 빠르게 방송
- Kafka: 메시지 이벤트를 안전하게 보관하고 여러 업무로 전달
- DB: 사용자가 과거 대화를 조회할 수 있도록 영구 저장
- 푸시 서비스: 접속하지 않은 사용자에게 알림 전송
예를 들어 철수가 메시지 하나를 보내면 Kafka를 통해 서로 다른 서비스가 독립적으로 움직일 수 있습니다.
chat.message.created
├─ 메시지 DB 저장
├─ 상대방 푸시 알림
├─ 욕설·스팸 검사
├─ 읽지 않은 메시지 수 증가
└─ 서비스 통계 집계철수의 채팅 서버는 이 서비스들을 직접 호출할 필요가 없습니다. 이벤트만 Kafka에 발행하면 각 서비스가 필요한 이벤트를 구독해서 처리합니다. 이것이 Kafka가 대규모 시스템에서 강력한 이유입니다.
핵심 정리
WebSocket은 사용자와 서버를 연결하고, Redis는 현재 접속자에게 빠르게 방송하며, Kafka는 사건을 기록해서 여러 서비스가 각자의 속도로 처리하게 한다.
따라서 학습용 채팅 샘플이라면 다음 순서가 가장 이해하기 좋습니다.
- 단일 서버에서 WebSocket 채팅 구현
- 서버를 두 대로 늘리고 Redis Pub/Sub 추가
- Kafka를 추가해 메시지 저장·알림·분석을 이벤트 기반으로 분리
Kafka의 핵심을 Pub/Sub이라고 보는 것은 출발점으로 맞습니다. 그러나 Kafka를 특별하게 만드는 본질은 메시지를 전달하고 끝내는 것이 아니라, 이벤트를 순서대로 보관하여 다시 읽고 재처리할 수 있다는 것입니다.