본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
https://fastcampus.info/4n8ztzq
안녕하세요 :)
오늘은 "50일의 기적 AI 환급반_ 대규모 채팅 플랫폼으로 한 번에 끝내는 실전 대용량 트래픽 커버 완전판 " 챌린지에 도전하는 서른 두번째 날입니다.
서른 한번째 날에는 왜 레디스 클러스터가 필요한가에 대해 배웠었는데요. 오늘은 과연 어떤 내용들을 배울 수 있을지 공부하고 포스팅 하도록 하겠습니다.
오늘 학습할 내용의 제목은,
Part 5. 모놀리틱 서비스 분해 > Ch 3. 책임 분리로 진행하는 모놀리틱 서비스 분해와 양방향 카프카 연동 >
01. 커넥션 서비스를 분리하는 이유
입니다.
⚡ 모놀리틱 분해: 커넥션 서비스 분리와 양방향 카프카 연동
- 이번 포스팅에서는 파트5의 새로운 챕터3를 시작하며, 주제는 바로 책임 분리에 따른 모놀리틱 서비스 분해와 양방향 카프카 연동입니다. 카프카는 본래 단방향 스트리밍 구조이지만, 이번 맥락에서는 커넥션과 메시지 서버 간의 양방향 흐름을 다루기 때문에 다소 생소하게 들릴 수 있습니다. 이번 글에서는 우선 왜 커넥션 서비스를 분리해야 하는가에 초점을 맞춰 보겠습니다.
📌 현재 구조의 한계
- 지금까지 채팅 프로젝트는 여러 차례 구조 개선을 거쳤습니다.
- 인증 서버 분리
- 푸시 서버 분리
- Redis 클러스터링
- DB 샤딩 및 리플리카 구성
- 이러한 개선으로 대부분의 컴포넌트는 수평 확장이 가능해졌습니다. 하지만 여전히 메시지 서버는 커넥션과 채팅 기능을 모두 가지고 있는 상태로 남아 있습니다.
- 메시지 서버는 웹소켓 커넥션을 직접 관리하는데, 이 과정에서 상태를 유지해야 하므로 스테이트풀(stateful) 한 구조가 됩니다. 인증 서버처럼 REST API 기반이라면 스테이트리스하게 확장할 수 있지만, 웹소켓은 연결 자체가 지속적이기 때문에 단순히 서버를 복제한다고 해서 문제가 해결되지 않습니다.
🔒 문제 상황: 확장이 불가능한 메시지 서버
- 사용자가 늘어나면서 웹소켓 연결 수도 증가합니다. 단순히 메시지를 보내지 않더라도 연결 자체가 서버 자원(메모리, 이벤트 리스닝 등)을 소비하기 때문에, 한 서버가 처리할 수 있는 동시 접속에는 한계가 존재합니다.
- 이를 해결하기 위해 서버를 여러 대 띄우더라도 새로운 문제가 발생합니다.
예를 들어,- A 유저는 메시지 서버 1번에 접속
- B 유저는 메시지 서버 2번에 접속
- 두 사용자가 같은 채널에 있다고 해도, 메시지를 교환할 수 없습니다. 각 서버는 자신의 세션만 관리하기 때문에 상대방이 실제로는 온라인 상태임에도 불구하고 푸시 서버를 통해 오프라인 메시지로 처리하는 상황이 벌어집니다. 즉, 현재 구조에서는 메시지 서버의 단순 스케일 아웃이 정상적인 채팅 기능을 보장하지 못합니다.
🛠 해결 방법: 커넥션 서버와 메시지 서버 분리
- 이를 해결하기 위한 첫 단계는 커넥션 서버를 별도로 분리하는 것입니다.
- 모든 클라이언트는 커넥션 서버와만 웹소켓을 맺습니다.
- 메시지 처리 로직은 메시지 서버가 전담합니다.
- 메시지 서버는 "B 유저가 어느 커넥션 서버에 연결되어 있는가"를 Redis 등에서 조회하여, 해당 서버로 메시지를 전달합니다.
- 커넥션 서버는 현재 연결된 세션을 찾아 사용자에게 메시지를 전달합니다.
- 이렇게 분리하면 메시지 서버는 더 이상 웹소켓 상태를 직접 관리하지 않고, 구조적으로 스테이트리스한 동작에 가까워집니다. 따라서 서버 대수를 늘려도 유저 수에 맞춰 확장할 수 있습니다. 예를 들어, 한 서버가 1만 명의 접속을 감당한다면, 두 대는 2만 명, 세 대는 3만 명을 처리할 수 있게 되는 것이죠.
🔗 의존성 제거: 카프카의 역할
- 그렇다면 커넥션 서버와 메시지 서버를 어떻게 연결할까요?
- 가능한 방법 중 하나는 서버 간 직접 연결 풀을 구성하는 것이지만, 이는 서버 수가 늘어날수록 풀메쉬 구조가 되어 관리가 복잡해지고 비효율적입니다.
- 따라서 우리는 중간 브로커로 카프카(Kafka)를 도입합니다.
- 커넥션 서버와 메시지 서버는 서로의 존재를 몰라도 됩니다.
- 각 서버는 카프카 토픽만 바라보며, 메시지를 발행하거나 소비합니다.
- 스케일 아웃 시에도 브로커만 유지되면 서버 간 의존성이 추가되지 않습니다.
- 이렇게 되면 시스템은 더 유연하고 확장성 있는 구조로 전환됩니다.
⚖️ 순서 보장의 과제
- 다만 카프카를 도입한다고 해서 모든 문제가 해결되는 것은 아닙니다. 채팅 메시지는 순서가 중요한 데이터입니다.
- 단일 파티션을 사용하면 FIFO 순서가 보장되지만 성능 확장이 어렵습니다.
- 멀티 파티션을 사용하면 성능은 확장되지만 메시지 순서가 뒤섞일 수 있습니다.
- 이를 해결하기 위해 이전에 구축한 메시지 시퀀스 아이디와 클라이언트 측 보정 로직이 중요한 역할을 하게 됩니다. 기본적으로 서버에서 순서를 최대한 지켜주되, 어쩔 수 없이 어긋난 경우 클라이언트가 보정하는 이중 안전망을 갖추는 것이 핵심입니다.
- 이번 과정을 정리하면서, 결국 핵심은 스테이트풀한 요소를 어떻게 스테이트리스하게 다룰 수 있는가라는 문제라고 느꼈습니다. 웹소켓 자체는 연결 상태를 유지해야 하므로 본질적으로 스테이트풀하지만, 이를 책임 분리를 통해 메시지 서버에서 분리하고, 카프카와 같은 브로커를 활용해 간접 의존성으로 바꾸면 사실상 확장 가능한 스테이트리스 구조처럼 다룰 수 있습니다.
- 즉, "상태는 반드시 존재하지만, 그 상태를 어떤 레이어에서 관리하느냐"가 아키텍처 확장의 관건입니다. 이 점은 다른 실시간 서비스(게임 서버, 실시간 알림, 스트리밍 등)에도 동일하게 적용될 수 있는 중요한 통찰이라는 생각이 들었습니다.