Foundry
네트워크
중급
핵심

WebSocket 과 실시간 통신

HTTP 폴링 대신 연결을 유지해 양방향으로 주고받는 방식과, 연결을 오래 열어두는 데서 오는 운영 비용

WebSocket 과 실시간 통신

한 번 연결을 맺고 유지하면서 양쪽이 먼저 메시지를 보낼 수 있게 하는 프로토콜

왜 필요한가

HTTP 는 클라이언트가 물어야 서버가 답합니다. 서버가 먼저 알려줄 방법이 없습니다.

방식동작문제
폴링3초마다 "새 메시지 있어?" 요청없어도 요청한다. 최대 3초 지연
롱 폴링요청을 보내고 응답을 미뤄 둔다메시지마다 연결을 다시 맺는다
SSE서버가 한 방향으로 계속 보낸다클라이언트에서 서버로는 못 보낸다
WebSocket연결을 유지하고 양방향연결 유지 비용이 든다

폴링의 낭비를 숫자로 보면 이렇습니다. 사용자 1만 명이 3초마다 물으면 초당 3,300 요청이고, 그중 대부분은 "새 것 없음"입니다. 헤더와 연결 수립 비용이 실제 데이터보다 큽니다.

연결 수립

WebSocket 은 HTTP 로 시작해 프로토콜을 바꿉니다.

단계내용
1클라이언트가 HTTP 요청에 Upgrade 헤더를 담아 보낸다
2서버가 101 응답으로 승인한다
3같은 TCP 연결에서 이후 WebSocket 프레임을 주고받는다

HTTP 로 시작하는 이유는 기존 인프라(포트 80과 443, 프록시)를 그대로 쓰기 위해서입니다.

무엇을 고를지

요구선택
서버가 보내기만 하면 된다SSE. 구현이 단순하고 자동 재연결이 표준에 있다
양방향이 필요하다WebSocket
갱신이 드물다폴링으로 충분하다. 30초에 한 번이면 굳이 연결을 유지할 이유가 없다
지연이 100밀리초 이내여야 한다WebSocket

연결을 오래 열어두는 비용

항목내용
메모리연결마다 버퍼와 상태가 남는다
파일 디스크립터연결이 곧 열린 소켓이다. 상한에 걸린다
서버 재배포배포할 때 모든 연결이 끊긴다
로드밸런서연결이 한 서버에 고정되어 부하가 고르지 않다
중간 장비프록시나 NAT 가 유휴 연결을 끊는다

운영에서 챙길 것

항목내용
하트비트주기적으로 핑을 보내 중간 장비가 끊는 것을 막고 죽은 연결을 감지한다
재연결끊기면 간격을 늘려가며 다시 붙는다. 지터를 더해 몰림을 막는다
놓친 메시지끊긴 동안의 메시지를 어떻게 줄지 정한다. 마지막 수신 지점을 보내 이어받는다
서버 간 전달서버가 여러 대면 다른 서버에 붙은 사용자에게 어떻게 보낼지 정한다
상한서버당 연결 수에 상한을 두고 넘으면 거절한다

네 번째가 규모가 커질 때 반드시 만나는 문제입니다. A 서버에 붙은 사용자가 B 서버에 붙은 사용자에게 메시지를 보내려면, 메시지 브로커를 두고 서버들이 구독하는 구조가 필요합니다.

면접에서 이렇게 나옵니다

Q.WebSocket 이 폴링보다 나은 점과 그 대가는 무엇인가요?

항목폴링WebSocket
지연폴링 간격만큼즉시
빈 요청갱신이 없어도 계속 요청한다없다
헤더 비용요청마다 수백 바이트프레임 헤더 몇 바이트
서버가 먼저 보내기불가가능
연결 유지 비용없다연결마다 메모리와 소켓
배포 영향없다재배포 시 전부 끊긴다
구현 복잡도낮다재연결, 하트비트, 서버 간 전달

대가가 아래 세 줄입니다. 특히 마지막이 실무 비용의 대부분입니다. 폴링은 실패해도 다음 요청이 알아서 복구하지만, WebSocket 은 끊김을 감지하고 다시 붙고 놓친 메시지를 이어받는 것을 직접 만들어야 합니다.

흔한 실수: 실시간이 필요하다는 이유만으로 WebSocket 을 고르는 것. 갱신이 30초에 한 번이면 폴링이 더 단순하고 값쌉니다. 초 단위 이하의 지연이 실제로 필요한지가 기준입니다.

Q.WebSocket 연결을 오래 유지하면 운영에서 어떤 문제가 생기나요?

문제내용
소켓 상한연결이 곧 파일 디스크립터다. 기본 상한에 걸려 새 연결이 거부된다
메모리 누적연결마다 버퍼와 상태가 남아 연결 수에 비례해 늘어난다
배포 시 전체 끊김서버를 내리면 붙어 있던 모든 연결이 끊긴다
부하 불균형로드밸런서가 연결 시점에만 분배하므로 오래된 서버에 연결이 쌓인다
유휴 연결 절단프록시나 NAT 가 일정 시간 조용한 연결을 끊는다
좀비 연결클라이언트가 사라졌는데 서버는 모르고 자원을 쥐고 있다

세 번째와 네 번째가 함께 나쁘게 작용합니다. 배포로 서버를 하나씩 교체하면, 남은 서버에 연결이 몰리고 새 서버는 비어 있습니다.

대응내용
소켓 상한 상향운영체제 설정을 올린다
하트비트죽은 연결을 감지해 정리하고 중간 장비의 절단을 막는다
점진 종료배포 시 재연결 안내를 보내고 시간을 두어 흩어지게 한다
최대 연결 수명일정 시간이 지나면 스스로 끊어 재분배를 유도한다

흔한 실수: 하트비트를 클라이언트만 보내게 하는 것. 서버도 응답이 없는 연결을 정리해야 좀비 연결이 쌓이지 않습니다.

Q.서버가 여러 대일 때 WebSocket 메시지를 어떻게 전달하나요?

연결은 특정 서버에 붙어 있으므로, 서버끼리 메시지를 나눠 갖는 통로가 필요합니다.

방법내용
발행 구독모든 서버가 브로커를 구독한다. 누가 보내도 전체가 받는다
라우팅 표어느 사용자가 어느 서버에 있는지 저장해 그 서버로만 보낸다
전용 게이트웨이연결만 담당하는 계층을 두고 뒤에서 붙인다

첫 번째가 가장 흔합니다. Redis 의 발행 구독이나 메시지 브로커를 두고, 서버는 받은 메시지 중 자기에게 붙은 사용자 몫만 내보냅니다. 구현이 단순한 대신 모든 서버가 모든 메시지를 받습니다.

두 번째는 규모가 커질 때 씁니다. 서버가 50대면 첫 번째 방식에서 각 서버가 전체 메시지를 다 받게 되어 낭비가 큽니다.

함께 정할 것내용
접속 위치 저장사용자와 서버의 매핑. 연결이 끊기면 지운다
정리서버가 죽으면 그 서버의 매핑이 남는다. 만료를 둔다
순서같은 대화의 메시지가 순서대로 가게 한다

흔한 실수: 로드밸런서의 고정 세션으로 해결하려는 것. 같은 사용자가 같은 서버로 가는 것은 되지만, 다른 사용자에게 보내는 문제는 그대로 남습니다.

Q.끊긴 동안 놓친 메시지를 어떻게 이어받나요?

클라이언트가 어디까지 받았는지를 알려주고 서버가 그 뒤부터 보냅니다.

방법내용
마지막 수신 식별자재연결 시 마지막 메시지 id 를 보내고 그 이후를 받는다
시각 기준마지막 수신 시각 이후를 받는다. 시계 차이에 주의
전체 재조회재연결 후 목록을 다시 불러온다. 단순하지만 무겁다
보관 기간서버가 최근 메시지를 얼마간 들고 있어야 한다

첫 번째가 정확합니다. 순번이나 단조 증가하는 식별자를 쓰면 중복도 누락도 없습니다. SSE 는 이것이 표준에 있어서 마지막 수신 id 를 자동으로 다시 보냅니다.

네 번째를 함께 정해야 합니다. 서버가 최근 메시지를 보관하지 않으면 이어받을 대상이 없습니다. 보관 기간을 넘겨 끊겼던 클라이언트는 전체 재조회로 넘겨야 합니다.

주의내용
중복 수신경계에서 겹칠 수 있어 클라이언트가 id 로 걸러낸다
대량 전송오래 끊겼다가 붙으면 수천 건이 몰린다. 개수에 상한을 둔다
순서이어받은 것과 실시간으로 오는 것의 순서를 맞춘다

흔한 실수: 재연결만 구현하고 이어받기를 빼는 것. 연결은 복구되지만 그동안의 메시지가 사라져 사용자는 대화가 비어 있는 것을 봅니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

네트워크 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.