WebSocket 서버를 더 이상 직접 운영하지 않는 이유
· Hibira
직접 운영하는 실시간 서버는 메시지가 몰리면 막히고, PHP2Shell 같은 취약점 패치도 직접 쫓아야 합니다. 저희가 겪은 일, 직접 운영에 필요한 것, 호스팅 서비스가 대신 맡는 것.

Hibira를 만들기 전, 저희는 지난 프로젝트들에서 WebSocket 서버를 직접 운영했습니다. 당시에는 PHP로 만든 서버였고, 애플리케이션과 함께 모놀리스로 배포했습니다. 평소에는 문제가 없었지만, 메시지 양이 급증하면 뒤처졌습니다. 서버는 이미 가진 메시지를 처리하느라 바빴고, 새 요청은 그 뒤에 줄을 섰고, 지연은 계속 늘어났습니다. 메시지가 마침내 사용자에게 도착했을 때는 이미 지난 내용이었습니다. 가격은 이미 바뀌었고, 점수는 갱신됐고, "입력 중…" 표시는 상대가 메시지를 보낸 뒤에야 나타났습니다.
이 글은 그때의 경험을 정리한 것입니다. 실시간 서버가 피크 때 막히는 이유, 직접 운영한다면 무엇을 더해야 하는지, 그리고 언제 호스팅 서비스에 맡기는 편이 나은지를 다룹니다.
막힘은 이렇게 생긴다
실시간 서버가 하는 일은 단순해 보입니다. 이벤트를 받아 구독 중인 모든 연결에 보내는 것뿐입니다. 문제는 이 작업량이 몇 배로 불어난다는 것, 그리고 어느 한 구간이 느려지면 그 뒤가 모두 기다려야 한다는 것입니다.
처리보다 빨리 들어오면 큐는 길어지기만 한다
이것이 대기열의 기본 규칙입니다. 메시지가 들어오는 속도가 서버의 처리 속도보다 빠른 동안 큐는 계속 길어지고, 새 메시지는 앞의 모든 메시지가 처리될 때까지 기다립니다.
숫자로 계산해 봅시다. 서버가 초당 5,000건을 처리할 수 있고, 피크 때 초당 6,000건이 10초 동안 들어온다고 합시다. 이 10초 동안 처리하지 못한 메시지가 10,000건 쌓입니다. 피크가 끝나고 새 메시지가 완전히 멈춰도 서버가 이를 다 비우는 데 2초가 더 걸리고, 큐의 마지막 메시지는 2초 늦게 나갑니다. 피크 뒤에도 평소 트래픽이 계속 들어오면 더 천천히 비워집니다.
웹 페이지라면 2초 늦는 건 경험이 조금 나빠지는 정도입니다. 실시간 기능에서 2초 전의 가격, 위치, 게임 상태는 대개 그냥 틀린 데이터입니다.
이벤트 하나에 구독자 수를 곱한다
한 번 발행한다고 한 번만 보내는 게 아닙니다. 구독자가 2,000명인 채널에서는 이벤트를 하나 발행할 때마다 서버가 2,000번 씁니다. 실제 작업량은 "초당 발행 수 × 구독자 수"입니다. 라이브 채팅방, 경기 점수, 운영 대시보드 같은 일대다 상황일수록 사람이 늘어나는 순간 갑자기 한계를 넘기 쉽습니다.
느린 연결 하나가 모두를 붙잡는다
엘리베이터나 지하철 안의 휴대폰은 통신이 아주 느려지거나 잠시 데이터를 받지 못하기도 합니다. 서버가 연결마다 하나씩 차례로 쓰고, 쓰기가 끝나기를 기다린 뒤 다음으로 넘어간다면, 연결 하나가 막히는 것만으로 그 뒤의 모든 사용자가 늦어집니다. 이것이 저희가 겪은 "블로킹"이었습니다. 문제는 대부분의 사용자가 아니라, 몇몇 느린 연결이 브로드캐스트 전체를 늦춘 데 있었습니다.
기다림은 번진다
서버가 바빠지면 이벤트를 발행하는 HTTP 요청도 느려집니다. 백엔드는 그 응답을 기다리니 자신의 요청도 쌓입니다. 시간 초과된 요청을 자동으로 재시도하면, 이미 과부하인 서버에 일을 더 얹게 됩니다. 처음엔 실시간 서버만 느렸는데, 결국 시스템 전체가 느려집니다.
모놀리스가 문제를 키웠다
저희가 겪은 문제의 대부분은 모든 것을 하나의 모놀리스로 배포한 데서 왔습니다. WebSocket 서버가 애플리케이션의 나머지 부분과 함께 돌고, 함께 배포됐습니다. 실시간 트래픽이 몰리면 애플리케이션 전체의 자원을 써 버려서 일반 API 요청까지 줄을 섰고, 반대로 다른 기능이 느려지면 실시간 메시지도 함께 묶였습니다. 실시간 부분을 확장하려면 애플리케이션 전체에 서버를 늘리는 수밖에 없었습니다. 게다가 모놀리스에서는 앱을 배포할 때마다 보통 모든 WebSocket 연결도 끊겼다가 다시 연결됩니다.
또 하나의 이유: 실행 환경의 보안 업데이트
성능 말고도 직접 운영을 그만둔 또 하나의 이유는 보안입니다. 당시 저희는 PHP 보안 취약점도 겪었고, 패치를 직접 쫓아다닐 수밖에 없었습니다. 서버를 직접 운영하면 운영체제, 언어 런타임, 라이브러리의 취약점을 모두 직접 챙겨야 하고, 하나도 놓치면 안 됩니다.
최근 예가 PHP 프로젝트가 2026년 10월 21일에 수정한, "PHP2Shell"이라 불리는 두 가지 심각한 취약점입니다.
- CVE-2026-109618: 메모리 부족(OOM)을 처리하는 중에 생기는 메모리 손상.
- CVE-2026-109619: 요청 사이에 남은 상태로 인한 use-after-free.
둘 다 CVSS v4.0 점수 9.2로, 인증되지 않은 공격자가 원격으로 코드를 실행할 수 있습니다. 영향을 받는 것은 PHP-FPM, Apache mod_php, php-cgi처럼 웹 요청을 처리하는 PHP입니다. 수정 버전은 8.2.35, 8.3.36, 8.4.27, 8.5.12이며, 8.1 이하는 더 이상 공식 정기 업데이트를 받지 않습니다.
이 두 취약점은 웹 요청을 처리하는 PHP에 있으므로 상시 실행되는 WebSocket 프로세스를 반드시 직접 겨냥하지는 않습니다. 그래도 교훈은 같습니다. 취약점이 공개되는 순간, 어떤 서버가 영향을 받는지 확인하고, 업그레이드 일정을 잡고, 다시 배포해야 합니다. 그것도 최대한 빨리요.
직접 운영한다면 이것들을 더해야 한다
각 문제에는 해결책이 있지만, 모두 한 번 설정하면 끝나는 게 아니라 계속되는 일입니다.
- 애플리케이션과 분리해 배포. 실시간 서버를 모놀리스에서 떼어 내 따로 확장하고 따로 배포할 수 있게 하면, 피크가 다른 기능까지 무너뜨리지 않습니다.
- 연결마다 상한이 있는 큐. 쓰기끼리 서로 기다리면 안 됩니다. 연결마다 자기 큐를 두고, 큐가 가득 차면 그 클라이언트는 따라오지 못하는 것이므로 다른 사용자를 늦추게 두지 말고 연결을 끊습니다.
- 받기와 보내기를 분리. 발행 API는 모든 구독자에게 전달될 때까지 응답을 미루면 안 됩니다. 그러면 브로드캐스트가 느려질 때 백엔드도 같이 느려집니다.
- 수평 확장. 서버 한 대에는 언제나 한계가 있습니다. 서버를 늘리려면 모든 서버가 채널과 프레즌스를 공유하고, 앞에 로드 밸런서를 둬야 합니다.
- 피크를 기준으로 용량 테스트. 평균 트래픽으로 한 부하 테스트는 "문제없음"이라고만 말해 줍니다. 피크의 몇 배, 그리고 많은 클라이언트가 한꺼번에 재연결하는 상황을 테스트해야 합니다.
- CPU만이 아니라 큐와 지연을 모니터링. CPU에 여유가 있어 보여도 큐는 길어지고 있을 수 있습니다. 메시지가 얼마나 기다렸는지, 너무 느려서 끊긴 연결이 몇 개인지 보이게 하고, 나빠지면 알림을 받아야 합니다.
- 온콜 담당자. 피크는 대개 가장 중요한 순간입니다. 티켓 판매 오픈, 경기의 마지막 1분, 프로모션 시작. 서버에 문제가 생겼을 때 대응할 사람이 있어야 합니다.
- 보안 업데이트 따라가기. 운영체제, 언어 런타임, 라이브러리의 취약점이 공개되면 영향을 평가하고 업그레이드해 다시 배포합니다.
하나씩 차례로 쓰기
연결마다 각자의 큐
호스팅 서비스가 대신 맡는 것
호스팅 실시간 서비스를 쓰면 위의 일은 서비스 제공자가 맡습니다. Hibira를 예로 들면:
- 느린 클라이언트가 다른 사람을 늦추지 않습니다. 연결마다 상한이 있는 큐가 있고, 쓰기끼리 서로 기다리지 않습니다. 큐가 가득 차거나 쓰기가 시간 초과된 클라이언트는 Pusher 프로토콜의 4100 오류 코드로 연결이 끊기고, pusher-js는 이를 받으면 자동으로 다시 연결합니다.
- 무슨 일이 있었는지 보입니다. 이런 연결 끊김은 앱의 오류 카드에 "느린 클라이언트 연결 끊김"으로 표시되므로 서버 로그를 뒤질 필요가 없습니다.
- 용량은 저희가 계획합니다. 서버는 클러스터로 동작하며 채널과 프레즌스를 공유하고 수평으로 확장됩니다. 요금제의 한도는 명확한 하드 리밋이라 청구서에서 놀랄 일이 없습니다.
- Pusher 프로토콜을 씁니다. 지금 쓰는 pusher-js, Laravel Echo, 서버 라이브러리를 그대로 쓸 수 있고, 서비스를 바꾸는 것은 호스트와 키를 바꾸는 일뿐입니다.
- 패치는 저희가 맡습니다. WebSocket 서버와 그 아래 실행 환경의 보안 업데이트는 저희가 처리합니다. 앱 자체는 여전히 직접 업데이트해야 하지만, 지켜볼 것이 하나 줄어듭니다.
비용도 분명히 말씀드립니다. 무료 한도를 넘으면 월 요금을 내야 하고, 이벤트가 제3자를 거치며, 요금제의 한도를 받아들여야 합니다.
직접 운영하는 편이 여전히 맞을 때
호스팅 서비스만이 답은 아닙니다. 다음과 같은 경우에는 직접 운영하는 편이 더 맞을 수 있습니다.
- 트래픽이 적고 안정적이며 뚜렷한 피크가 없다.
- 이벤트 내용이 자사 인프라 밖으로 나가면 안 된다.
- 이미 운영 팀, 모니터링, 온콜 체계가 있어서 상시 서비스가 하나 늘어도 부담이 없다.
Laravel을 쓴다면 Reverb를 직접 운영할까, 호스팅 서비스를 쓸까에서 더 구체적으로 비교합니다. 둘 다 Pusher 프로토콜을 쓰므로 나중에 마음이 바뀌어도 설정만 바꾸면 됩니다.
저희의 결론
그래서 저희는 다시 만들기로 했습니다. 실시간 통신을 모놀리스에서 떼어 내 수평 확장이 가능한 독립 서비스로 만들고, 먼저 내부에서 쓴 다음 외부에도 열었습니다. 앞단에 로드 밸런서를 두고 부하에 따라 자동으로 수평 확장하기 때문에, 트래픽이 갑자기 몰려도 서버 한 대가 버티는 대신 서버를 늘려 나눠 받습니다. 그것이 Hibira입니다.
WebSocket 서버를 직접 운영할 때 어려운 것은 서버를 띄우는 일이 아니라, 가장 바쁜 몇 분 동안에도 제때 전달하는 일입니다. 그때가 바로 사용자가 가장 신경 쓰고, 문제가 가장 생기기 쉬운 순간입니다. 팀의 시간을 큐와 온콜이 아니라 제품에 쓰고 싶다면, 호스팅 서비스가 더 수월한 선택입니다.
Hibira의 무료 요금제는 신용카드가 필요 없고, 개발과 소규모 앱에 충분합니다. 이미 Pusher를 쓰고 있다면 Pusher Channels에서 마이그레이션의 단계대로 코드를 바꾸지 않고 옮겨 올 수 있습니다.