확장가능한 웹소켓 서비스 구축하기

Key Kim
Neuralworks
Published in
5 min readJul 28, 2021

실시간 알림 서비스를 구현하면서 확장 가능한 웹소켓 서비스를 구현한 내용에 대해서 공유하려합니다.

사용된 기술스택은 socket.io 서비스로 Nest.js(Node.js), Mongodb, Redis을 사용했습니다.

OverView

구현에는 총 4개의 컴포넌트가 필요합니다.

0. L7 Loadbalancer

웹소켓 서비스접속을 위한 첫번째 관문입니다. AWS ELB (ALB)를 사용하며, 쿠키를 통해 스티키세션을 유지합니다.

스티키세션은 3시간으로 지정해주었습니다.

1. socket.io backend

사용자의 웹소켓 연결 요청을 처리합니다. N대의 socket.io 서비스로 구성됩니다.

Watcher로 부터 Publish된 Event를 Subscribe하며, 이 때 호출되는 콜백을 등록하여 Client로 Emit합니다.

2. Watcher

몽고디비에서 특정 컬렉션의 onChangedEvent 이벤트를 모니터링하는 서비스, 몽고디비와 지속적인 커넥션 유지가 필요합니다.

3. Redis

Watcher에서 발생한 이벤트를 socket.io backend에서 구독하기 위해 Redis PUB/SUB을 사용합니다. Watcher 서비스는 PUB, socket.io backend 는 SUB을 담당합니다.

Redis Pub/Sub의 동작 방식

Redis Pubsub은 LinkedList 형태로 Client를 관리하여, 등록된 Client List를 순회하며 Publish하는 방식입니다.

따라서 Publish Unsubscribe operation에 O(N)이 소요됩니다. Redis에서 O(N)은 꽤 큰 코스트의 명령이므로, 위 명령어를 빈번하게 사용하지 않도록 주의해야합니다. 아래는 실제 Redis Pub/Sub의 구현체 입니다.

https://github.com/redis/redis/blob/unstable/src/pubsub.c
https://github.com/redis/redis/blob/unstable/src/pubsub.c

Design 1 : 브로드캐스팅 방식

최초로 설계했던 방식입니다. 당시 Redis Pub/Sub 에 대한 이해가 부족해서, 각각의 Pub/Sub이 많은 리소스를 소비한다고 생각하여, 브로드캐스팅 방식으로 구현하였습니다.

어떤 서버에 소켓 연결이 되어있던간에 브로드캐스팅으로 모든 socket.io backend 서버에 PUB하고, 각 서버 메모리에 저장된 클라이언트 연결 정보를 조회하여, 발생한 이벤트의 userId에 해당하는 연결이 존재한다면 해당 연결에 Emit 을 실행하는 방법입니다. (N:1 연결)

socket.io backend 입장에서 O(1)로 요청을 처리할 수 있지만, Watcher 입장에선 O(N) 이 소요됩니다. (Publish할 서버가 매우 많거나, 발생하는 이벤트가 매우 많은 경우 항상 모든 서버에 콜백이 호출되므로, 비효율적일 수 있음.)

Design 2 : 유니캐스팅 방식

각 socket.io backend 에서 userId별로 SUB을 수행하고, Watcher에선 userId별로 PUB을 진행합니다. (1:1 연결)

결과적으로 Design 2 의 방식을 채택하여 서비스 운영을 준비하고 있습니다.

감사합니다.

Reference

--

--