10만 연결에서 배운 교훈: SSE가 WebSocket보다 메모리를 40% 덜 쓴다는 것

|Platform Decision|7분 읽기

모든 게 당연해 보였던 선택

실시간 대시보드를 만들 때 WebSocket을 선택하는 건 마치 공식처럼 느껴집니다. 실시간 가격, 재고량, 알림 등을 표시해야 한다면 당연히 WebSocket이죠. 튜토리얼도, 포럼의 시니어 개발자들도 모두 같은 이야기를 하니까요.

저희 팀도 처음엔 그런 '당연함'에 의문을 품지 않았습니다. 1만 연결까지는 문제없었고, 3만 연결에서도 마찬가지였거든요. 하지만 10만 연결 부하 테스트에서 메모리 사용량 그래프가 급격히 치솟으며 시스템이 멈춰버렸습니다.

그 이후 4개월간의 삽질과 약 3,200달러의 불필요한 컴퓨팅 비용을 지불하고 나서야 깨달았습니다. 처음부터 다른 선택지를 고려해봤어야 했다는 걸요.

WebSocket vs SSE: 구조적 차이가 만드는 비용

둘 사이의 가장 큰 차이는 HTTP 사용 여부입니다. 이 차이 하나가 생각보다 큰 비용 차이를 만들어냅니다.

WebSocket (전이중)
클라이언트 <──────────────> 서버
         송수신
         [ 상태 유지, 사용자 지정 프로토콜 ]

SSE (반이중)
클라이언트 <────────────── 서버
         수신 전용
         [ 일반 HTTP, 브라우저 자동 재연결 ]

WebSocket은 HTTP 업그레이드 핸드셰이크 후 자체 프로토콜로 동작합니다. 서버가 모든 소켓 상태를 관리하고, 핑/퐁 하트비트로 연결 상태를 확인해야 하죠.

SSE는 그냥 HTTP 응답을 열어두고 텍스트 이벤트를 스트리밍하는 방식입니다. 사용자 지정 프레임도, 복잡한 협상도 없어요. 연결이 끊어지면 브라우저가 알아서 다시 연결해줍니다.

숫자로 보는 현실

동일한 환경에서 10만 연결 테스트를 진행한 결과입니다. EC2 c5.2xlarge 인스턴스에서 클라이언트당 초당 1회 업데이트를 보내는 조건이었어요.

메트릭 WebSocket SSE 차이
연결당 메모리 ~3.5KB ~1.2KB -65%
10만 연결 시 총 RAM ~342MB ~205MB -40%
CPU 유휴 부하 18% 11% -39%
재연결 로직 수동 구현 브라우저 내장 개발 편의성 ↑

메모리 40% 절약은 단순한 최적화가 아니라 구조적 차이에서 나온 결과입니다. WebSocket은 연결별로 버퍼, 상태 머신, 프로토콜 메타데이터를 유지해야 하지만, SSE는 서버의 기존 HTTP 스택을 그대로 활용하거든요.

가장 충격적이었던 건 WebSocket 10만 연결 상태에서 트래픽이 급증하면 시스템이 완전히 멈춰버린다는 점이었습니다. 성능 저하가 아니라 아예 다운되는 거예요. 예상 복구 시간은 22분. 실시간 데이터가 핵심인 서비스에서는 치명적이죠.

실제 코드로 보는 차이

실제 운영에서 사용한 코드를 비교해보면 복잡성 차이가 명확합니다.

SSE 서버 (더 간단함):

const http = require('http');

http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({price: getPrice()})}\n\n`);
  }, 1000);
  
  req.on('close', () => clearInterval(interval));
}).listen(8080);

SSE 클라이언트 (재연결 자동):

const src = new EventSource('/events');
src.onmessage = (e) => {
  const { price } = JSON.parse(e.data);
  updateUI(price);
};

외부 라이브러리도 필요 없고, 재연결 로직을 따로 구현할 필요도 없습니다. 브라우저 스펙에서 자동으로 처리해주거든요. 전환하면서 재시도 처리 코드 200줄 정도를 통째로 삭제했는데, 팀 누구도 아쉬워하지 않았어요.

선택 기준: 하나의 간단한 질문

경험을 통해 얻은 선택 기준은 명확합니다.

클라이언트가 데이터만 받고 서버로 보낼 게 없다면 SSE가 정답입니다. 실시간 피드, 대시보드, 알림, 배포 진행률, 로그 스트리밍 등이 여기에 해당하죠.

클라이언트가 서버로 데이터를 보내야 한다면 WebSocket이 맞습니다. 채팅, 공동 편집, 멀티플레이어 게임처럼 양방향 상호작용이 필요한 경우요.

저희의 실수는 WebSocket을 선택한 게 아니라, 애초에 양방향 통신이 정말 필요한지 묻지 않은 것이었습니다.

놓치기 쉬운 세부사항

SSE 관련해서 자주 언급되는 "HTTP/1.1에서 도메인당 6개 연결 제한" 이슈가 있는데요. HTTP/2 환경에서는 이 제한이 사라집니다. 전송 계층에서 연결이 다중화되기 때문이에요.

2026년 현재 대부분의 프로덕션 환경이 HTTP/2를 사용하고 있으니, 이 제한은 실제로는 큰 문제가 되지 않습니다. 서버 설정만 확인해보면 되죠.

마무리

4개월의 삽질과 3,200달러의 비용을 치르고 얻은 교훈이지만, 돌이켜보면 꽤 값진 경험이었다고 생각합니다. 기술 선택에서 '당연함'을 의심하는 습관을 갖게 되었거든요.

#WebSocket#SSE#실시간통신#성능최적화#백엔드