[Review] 시스템 디자인 스터디 5주차 후기
안녕하세요!
K-DEVCON (이하 “데브콘”) 그로스 매니저 박종훈입니다.🙋🏻♂️
이 글에서는 10/16 (목)에 진행된 스터디에서 나눈 이야기를 정리합니다.
이번 시간에는 “채팅 시스템 설계” 이야기를 나눴습니다.
[1] 채팅 시스템 설계
채팅 시스템은 우리가 매일 쓰는 시스템 중 하나입니다.
메시지를 보내고, 파일을 공유하는 이 모든 과정이 너무나 자연스럽고 당연하게 느껴집니다.
하지만 이 편리함 뒤에는 우리가 상상하기 어려운 거대한 기술적 복잡성이 숨어있습니다. 전 세계 수많은 사용자가 동시에 주고받는 수십억 개의 메시지를 안정적으로 처리하기 위해, 엔지니어들은 어떤 문제와 마주하고 또 어떻게 해결했을까요?
<1> 기본적인 시스템 설계
채팅 시스템의 기본적인 설계는 다음과 같습니다. 상태 서비스(stateful service)와 무상태 서비스(stateless service)로 나눠지게 됩니다. 상태 서비스는 채팅 서버에서 지속적으로 통신을 이어나가기 위해 사용됩니다.
어떤 채팅 서버와 통신하면 좋을지는 아래와 같이 최초 연결시 결정하게 됩니다.
그리고 챗 서버를 통해 발송한 채팅은 메시지 큐를 거쳐 상대방이 연결되어 있는 메시지 서버로 전송됩니다.
[2] Slack 사례
저는 이번 스터디에서 Slack의 사례를 조사해보고 스터디원 분들께 공유드리며 서로 이에 대해서 이야기를 나눠보았습니다.
서비스가 점진적으로 개선되어가는 과정을 엿볼 수 있어서 재밌는 이야기가 되었습니다.
<1> 데이터 송수신 개선
Slack은 재미있게도 처음부터 메시지 서비스를 만드는 회사는 아니였습니다. 처음에는 게임을 만드는 회사였고 그 과정에서 나온 커뮤니케이션 도구를 발전시켜 메시지 서비스를 만들게 되었습니다.
그래서 슬랙의 초기 디자인은 게임과 같은 디자인 설계를 가지고 있었습니다. 게임은 처음 시작할 때, 대부분의 데이터를 로드해둔 후 활용합니다. 이처럼 초기 슬랙에서도 처음 어플리케이션을 실행할 때, 필요한 데이터를 한번에 가져왔습니다.
그리고 그 이후에 필요할때마다, 처음에 가져온 데이터를 활용하였습니다. 실시간 데이터는 WebSocket을 통해서 받아왔습니다.
이 구조는 초기에는 적합하였습니다. 하지만 문제는 슬랙이 커지면서 발생되었습니다. 한 워크스페이스에 사용자가 200명이 넘어서게 되자, 처음 불러오는 데이터 양이 매우 커지게 되었습니다.
슬랙 팀은 모든 사용자가 모든 데이터를 초기부터 가지고 있을 필요는 없다는 것을 인지하게 됩니다. 사용자는 자신이 사용하는 채널과, 사람과, 그 대화내역에 대해서만 알고있으면 됩니다.
이러한 문제를 개선하고자 슬랙은 초기에 받는 데이터를 필수 데이터만 가져오도록 변경합니다.(필수 데이터에는 Websocket 과 통신할 수 있는 URL 을 포함합니다.) 그리고 그 이후에 API를 통해서 필요한 데이터를 받아오는 형태로 변경합니다.
웹 소켓 커넥션을 맺고 유지하는 것은 비용이 큰 작업입니다. 모바일에서는 특히나 그렇습니다. 이동에 따라 계속 네트워크가 변경되기 때문이죠. 이를 줄이기 위해 메시지 발송하는 작업을 HTTP API로 수행할 수 있도록 변경하였습니다. 또한 초기에는 broadcast 에 가까운 방식 이었다면, 이후에는 pub/sub 구조로 변경합니다. 마찬가지로 필요한 사람만 이벤트를 수신하면 되기 때문이었습니다.
데이터도 한 번에 다 가져오는 것이 아니라, 부분부분 가져와서 보여줄 수 있는 부분부터 렌더링 해나가는 것도 재밌는 부분이였습니다. 그래서 슬랙을 사용할 때, 가끔씩 일부분만 보이는 경우가 발생하는구나 알게 되었습니다.
개인적으로 사례들을 조사하면서 WebSocket은 유지/관리하는 비용이 크기 때문에 최소한의 실시간 이벤트를 수신하는데 사용되고, 실제 처리는 HTTP API로 분리하려고 하는 듯한 느낌을 받았습니다.
<2> Geo-distributed edge 서버 도입
또 한가지 슬랙 사례에서 재밌던 점은 Geo-distributed edge 서버를 사용하여 반응 속도를 개선하였다는 점입니다.
초기 Slack 에서는 중앙 서버를 사용하였지만, 점점 다양한 고객들을 다루게 되면서 Geo-distributed edge 서버를 도입하게 되었고, 이를 통해서 응답 속도 향상과, 트래픽 분산의 효과도 얻을 수 있었습니다.
특히 Slack의 경우에는 한 워크스페이스의 사용자들이 같은 지역에 있을 가능성이 높을 것 같아, 더 효과를 크게 느낄 수 있지 않았을까 생각되었습니다.
<3> 메시지큐 : Kafka 전환기
마지막으로 재미있던 부분은 Slack에서 Kafka 를 도입하게 된 이야기였습니다
초기 슬랙에서는 Redis를 메시지 큐로 활용하였습니다. Producer가 Produce하고 Worker가 Consume 하는 구조였습니다. 여기서 문제는 중간에 속도를 조절해줄 방법이 없다는 것이였습니다. 한 번은 너무 많은 메시지를 Produce하여 병목이 발생되었고, Redis의 메모리가 꽉 차게 되었습니다. 이는 쉽게 해결되지 않았다고 하는데, 데이터를 삭제하기 위해서도 여유공간이 필요하기 때문이었습니다.
이런 일을 한 번 겪게 되자 Slack 팀은 Kafka 도입을 검토하게 되었는데 Redis를 Kafka로 완전히 교체하는 대신 Redis 앞에 Kafka를 추가하기로 결정했습니다. 이렇게 해서 기존 애플리케이션의 대기열(enqueue) 및 대기열 제거(dequeue) 인터페이스는 그대로 유지하면서 시스템의 병목 현상을 완화할 수 있었습니다. 물론 Layer들이 추가되면서 시스템이 더 복잡해진 것과 같이 보이긴 하지만, 기존 시스템을 유지할 수 있다는 것이 큰 장점이였을 것입니다.
한 회원분이 관련하여 카카오 발표 내용을 공유해주셨었는데, 카카오에서는 중앙에서 풀 메시 방식으로 릴레이 서버를 구성하여 서버간 통신을 처리하고 있고 서버 한대당 약 50만 개의 세션을 관리하며, 최대 100K 정도의 트래픽을 처리해주고 있다고 해서, 이 부분도 회사마다의 철학 차이가 느껴지는 부분이라 재밌는 부분이였습니다.
[3] 데이터 장기 보관
채팅 서비스들은 어떤 데이터베이스를 사용할까요? 그리고 어떻게 데이터를 장기 보관할 수 있었을까요?
Facebook 에서 발표한 내용에 따르면 Facebook Messenger와 WhatsApp은 하루에 600억 개의 메시지를 처리한다고 합니다. 하루에 600억 개의 메시지를 처리해야한다면, 늘어나는 데이터양은 굉장히 부담이 되었을 것입니다.
이를 위해서 데이터를 종류/기간에 따라 분류하여 Cold Storage 를 잘 사용해야 할 것 같다는 이야기를 나눴습니다.
한 회원분은 메시지 서비스와 관련된 도메인에서 일하고 계신데
초기에는 PostgreSQL 로 처리를 하시다가 규모가 커지면서 DynamoDB로 이전을 하신 상태라고 하셨습니다.
지금 당장은 하나의 테이블에 쌓고 있는데, 문제 없이 동작하고 있다고 공유를 해주셨습니다.
옛날에는 데이터가 쌓여가는게 비용적인 측면에서 리스크 였지만, 요즘은 데이터가 중요한 시대이다보니 자산이 되었을지도 모르겠다는 재밌는 관점도 있었습니다.
[4] WebRTC
한 회원분 께서는 WebRTC로 프로젝트를 진행해보신 경험도 공유해주셨습니다.
WebRTC(Web Real-Time Communication) 는 Zoom, Google Meet, Slack Huddle 과 같은 영상 통화 서비스에 사용되는 웹 표준 기술입니다. 많은 사람들이 이 표준 기술만 있으면 누구나 쉽게 영상 통화 서비스를 만들 수 있다고 생각하지만, 현실은 전혀 달랐었다고 합니다. WebRTC 표준 스펙만으로는 줌과 같은 안정적인 상용 제품을 만드는 것이 불가능한 수준이였다고 합니다.
대부분의 성공적인 영상 통화 회사들은 공개된 WebRTC 기술을 그대로 사용하는 것이 아니라, 자신들만의 노하우로 내부를 대폭 수정하고 튜닝하여 사용하고 있으며, 사용자가 늘어남에 따라 생기는 문제들을 어떻게 해결할지 많은 고민이 필요하였다고 합니다.
쉽게 접하지는 못하는 분야라서 재밌게 이야기를 들을 수 있었습니다.
[참고] 추천 참고 자료
- 채팅 시스템 설계 : https://bytebytego.com/courses/system-design-interview/design-a-chat-system
- Slack 사례
- 2016 (초기 디자인): https://www.youtube.com/watch?v=WE9c9AZe-DY
- 2017 (초기 로딩 경량화 + Flannel(geo-distributed cache server) 관련 이야기): https://www.youtube.com/watch?v=x1Uz3rMlOBo
- 2018 (stateless 처리): https://www.youtube.com/watch?v=o4f5G9q_9O4
- JobQueue 개선 사례 (Redis → Kafka + Redis): https://slack.engineering/scaling-slacks-job-queue
오늘은 위와같은 이야기를 함께 나누었습니다.
만약 당신이 채팅 시스템을 설계한다면, 어떻게 설계하실것 같으신가요?
시스템 디자인을 하면서 배우는 것은 "절대적인 정답은 없다"는 것입니다.
서비스의 성장과 요구사항에 맞춰 유연하게 대응하는 것이 중요합니다.
우리 스터디에서는 이러한 문제들을 실제 사례에서 어떻게 해결해 나갔을까 많은 고민을 해보고 이야기를 나눠보고 있습니다. 이번주도 다들 열심히 참여해주셔서 더 풍성한 스터디가 될 수 있었습니다.
스터디에서 더 많은 이야기를 나눴지만, 오늘은 여기서 마쳐보겠습니다.
✅ 직접 스터디를 개설해보고 싶은 분이 계시다면, K-DEVCON에서 운영을 도와드리겠습니다. 데브콘의 '랩짱'에 도전하여 커뮤니티 성장을 함께 이끌어주세요! 슬랙을 통해 운영진에게 DM 부탁드리겠습니다😉