트레디아 채팅 후기 [부제: socket.io Cluster Worker]
![트레디아 채팅 후기 [부제: socket.io Cluster Worker] - 1](https://attach.tradia.me/diablo2/files/attach/images/3124474/636/025/007/a744bd8c711c623280cc76a92428958e.jpg)
한개의 방에 1만 접속자의 채팅이 몰리니까 싱글스레드로 설계된 nodejs 앱이 버벅거립니다.
그 앱 혼자서 약 4~5만 소켓 커넥션을 담당 했습니다. (채팅 + 실시간 알림 기능)
nodejs 의 처리속도에 한계가 있으니까 병목현상으로 nignx 에서 시피유를 먹어댑니다.
응급으로 한 일은 일단 돈으로 해결했는데요, 채팅 서버 시피유의 성능을 꽤 업그레이드 시키고 nginx 가 버텨주었습니다.
하지만 한 개의 시피유 코어만 사용하는 nodejs 앱이 힘들어 했습니다.
이후 nodejs 앱의 cluster 와 worker 분산처리가 필요했습니다.
원리는 로드밸런서와 비슷한 것 같습니다.
(사진: 트레디아 채팅의 멀티스레드 설계)
위 시안은 트레디아 채팅 프로그램 설계를 매우 간단히 보여준 것인데요,
- 싱글 스레드: 채팅 프로그램이 기존에 1개의 코어만을 사용하기 때문에 시스템 자원의 일부만을 활용합니다.
알바생이 4명 있는데 그 중 1명만 주문받고 요리하고 나머지는 쉬는 셈이죠. - 멀티 스레드: 채팅 프로그램에서 분리 가능한 코드를 분리해서 각각의 시피유를 사용합니다.
시피유 코어 자원이 4개 라면 4개의 Worker 를 띄웁니다.
이렇게 해서 4명의 알바가 주문을 받고요, 1명의 요리사(Cluster)에게 주문을 전달합니다. - Cluster 와 Worker 역할
Clutser 는 중심부를 담당하는데요, 분리 불가능한 작업을 처리합니다.
Worker 는 분리 가능한 작업을 처리하므로 프로세스를 여럿 띄울 수 있습니다.- 대화 내용, 접속한 유저 목록은 모든 이용자에게 동일하게 뿌려줘야 하기 때문에 각각의 Worker 는 그 값을 가지고 있지 않고, Cluster 가 채팅 내용과 유저 목록을 가지고 있다가 유저가 Worker 에게 접속할 때 Cluster 가 Worker 에게 그 값을 전달합니다.
- 유저가 접속을 끊을 때마다 실시간으로 알려주면 서버와 유저 모두 고통스럽습니다.
접속을 끊으면 끊은 목록을 Cluster 가 일괄 가지고 있다가 일정 시간마다 모든 Worker 에게 뿌려주고 Worker 는 자신에게 접속한 유저들에게 접속 끊긴 유저를 알려줍니다.
작동 절차를 간단히 묘사하면 아래와 같습니다.
- CLIENT -> [접속] -> WORKER (CLUSTER 에게 비동기 전달) / 끝
CLUSTER -> 비동기 전달 [대화 내용, 유저 목록] -> WORKER -> CLIENT -
채팅도 간단합니다.
CLIENT -> [신규 접속], [대화 보냄] -> WORKER (CLUSTER 에게 비동기 전달) / 끝
CLUSTER -> [모든 워커에게 대화 전달] (비동기) -> ALL WORKERS -> 대화 전달 -> CLIENT
-
방의 멤버 목록과 인원수를 구한다고 가정해 보겠습니다.
어떤 유저는 그 채팅을 3개 띄우고, 또 어떤 유저는 그 채팅을 1개만 띄어놓습니다.
몇명입니까? 소켓은 4개, 워커는 n개, 하지만 클라이언트에게 보여줄 방의 멤버는 2명 입니다.
각각의 워커는 전체 접속자의 1/n 만 담당하기 때문에 접속 정보를 클러스터에게 전달합니다. 클러스터는 그 워커에게 접속한 멤버 목록과 대화 내용을 주고, 모든 워커에게 신규 접속을 알립니다.
접속자 목록과 접속자 수를 구하는게 어려웠습니다.
지난 10년간 밥먹고 개발만 했는데 이게 제일 어려웠던 것 같습니다.
php 나 sql 이 복잡하다고 생각해왔지만 js 에서 시피유와 메모리 분산처리를 설계하려니 재미있는 작업이기도 했네요.
아무튼 요약하자면: 알바 8명 고용했지만 1명이 대부분 담당하던 것을 이제는 자원을 분산해서 4명(+1)이 효율적으로 일하게 개선했습니다.