NestJS WebSocket

Gateway는 연결, room, adapter 상태를 한 흐름으로 본다

실시간 기능의 핵심은 메시지 핸들러 하나가 아니라 client가 어떤 room에 속하고, adapter가 어느 노드로 broadcast하며, 끊김 뒤 어떤 상태로 복구되는지 추적하는 구조다.

연결 토폴로지

client → gateway → room → adapter
WebSocket client, gateway, room, adapter 관계 여러 클라이언트가 handshake guard를 거쳐 gateway에 연결되고 room registry와 Redis adapter를 통해 다른 서버 인스턴스로 broadcast된다. Clients browser A socket: s-104 mobile B socket: s-208 admin C socket: s-330 Handshake Guard token, namespace, origin Gateway @SubscribeMessage Room Registry room:user:socket Event Contract payload, ack, version Redis Adapter pub/sub fan-out Nest node B same namespace room broadcast acks/logs 장애는 연결 성공/room 권한/adapter 전파/backpressure 중 어디서 막혔는지 분리한다
clients browser A · mobile B · admin C socket ID가 user와 device를 가리킨다.
gateway Handshake Guard → Gateway handler token, namespace, origin을 검증하고 메시지 계약을 적용한다.
room Room Registry · Event Contract room:user:socket 관계와 payload/ack/version을 같이 기록한다.
adapter Redis Adapter · Nest node B 다중 인스턴스 broadcast와 pub/sub 지연을 별도 지표로 본다.

이벤트 생명주기

session trace
01

connect

token과 origin을 검증하고 user ID를 socket ID에 묶는다.

handleConnection
02

join room

권한을 확인한 뒤 room registry에 user/socket을 기록한다.

client.join()
03

broadcast

payload version과 ack 정책을 맞춘 뒤 adapter를 통해 전파한다.

server.to()
04

reconnect

이전 session을 정리하고 중복 구독·중복 수신을 제거한다.

disconnect

room membership 상태판

live state
room:order:42 주문 상세 구독
u17:s-104, u21:s-208 권한 확인 후 join
ack 120ms
room:admin 운영 알림
u01:s-330 role=admin만 허용
guard ok

adapter pressure

pub/sub
delivered queued dropped

dropped가 생기면 Gateway handler보다 adapter 지연, room fan-out, client ack timeout을 먼저 분리해 본다.

실시간 장애 분리

같은 끊김, 다른 원인
handshake reject 토큰 만료, origin 차단, namespace 불일치를 401/close reason으로 남긴다.
room join denied 연결은 성공했지만 room 권한이 없는 경우를 별도 error event로 보낸다.
adapter lag 다중 인스턴스 broadcast 지연은 Redis pub/sub latency와 함께 본다.
reconnect duplicate 재연결 후 예전 socket이 남아 있으면 중복 수신과 ghost member가 생긴다.