본문으로 바로가기
기술

WebSocket vs SSE vs Long Polling — 실시간 채널 선택 가이드

A
AlwaysCorp 기술팀· 프론트엔드 개발 콘텐츠 전문
||9분 읽기
#WebSocket#SSE#LongPolling#MQTT#실시간#HTTP#EventSource#Socket.IO#프로토콜

"실시간"의 의미부터 정리

"실시간"이라는 단어 하나에 너무 많은 게 들어 있습니다. 화상 통화의 60ms와 댓글 알림의 5초는 둘 다 실시간이지만 요구가 정반대입니다. 잘못 고르면 과도하게 복잡한 인프라를 떠안거나, 반대로 사용자가 새로고침하게 만듭니다. 처음 던질 질문은 단 두 개입니다.

  1. 양방향인가, 단방향(서버→클라)인가?
  2. 메시지를 잃어도 되는가, 절대 잃으면 안 되는가?

이 두 질문의 답이 프로토콜 선택을 거의 결정합니다.

한 표로 보는 비교

실시간 채널 비교 — WebSocket·SSE·Long Polling·MQTT 한눈에

WebSocket — 양방향이 필요할 때의 기본값

채팅·게임·협업 편집·라이브 입찰처럼 클라이언트가 실시간으로 보내고 받아야 하면 WebSocket입니다. 한 번 핸드셰이크가 끝나면 양방향 풀듀플렉스 채널이 열리고, 프레임 헤더가 6바이트로 작아 효율도 좋습니다.

다만 WebSocket은 HTTP 표준에서 한 발 비켜난 프로토콜이라 부수적인 일거리가 따라옵니다.

  • 자동 재연결 — 직접 짜거나 Socket.IO·socket-cluster 같은 라이브러리에 의존
  • 일부 기업 프록시·방화벽이 WS를 차단 — Socket.IO가 SSE/Polling 폴백을 자동으로 깔아주는 이유
  • Sticky session — 다중 인스턴스 환경이라면 같은 클라이언트가 같은 서버에 붙도록 LB 설정
const ws = new WebSocket("wss://api.example.com/chat");
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  // ...
};
ws.onclose = () => setTimeout(reconnect, 2000);  // exponential backoff 권장

Server-Sent Events — 가장 저평가된 도구

알림·진행률·라이브 카운터·뉴스 피드처럼 서버 → 클라이언트 단방향이면 SSE가 압도적으로 우수합니다. 그런데 의외로 잘 안 쓰입니다.

  • HTTP/HTTPS 그대로 — 모든 프록시·CDN·LB가 즉시 동작
  • 자동 재연결이 표준 스펙에 내장EventSource가 끊기면 알아서 재연결
  • Last-Event-ID 헤더로 끊긴 동안의 메시지 재전송 가능
const es = new EventSource("/api/notifications");
es.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  // ...
};

서버 쪽도 단순합니다.

// Next.js Route Handler 예시
export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      const enc = new TextEncoder();
      while (true) {
        const evt = await waitForEvent();
        controller.enqueue(enc.encode(`id: ${evt.id}\ndata: ${JSON.stringify(evt)}\n\n`));
      }
    },
  });
  return new Response(stream, { headers: { "Content-Type": "text/event-stream" } });
}

LLM 스트리밍 응답이 SSE를 다시 주류로 끌어올렸습니다. ChatGPT·Claude 응답이 한 글자씩 흘러나오는 것도 결국 SSE입니다.

Long Polling — 정말 환경이 험할 때

기업 내부망의 깐깐한 프록시·구형 클라이언트·방화벽 때문에 WS도 SSE도 못 쓸 때의 폴백입니다. 클라이언트가 요청을 보내면 서버가 메시지가 생길 때까지 응답을 보류하고, 응답을 받으면 클라이언트가 즉시 다음 요청을 보내는 구조입니다.

지연 시간이 다른 두 방식보다 약간 길고, 매 요청마다 헤더 오버헤드가 있어 효율은 떨어집니다. 신규로 선택할 일은 거의 없지만, 호환성 폴백으로는 여전히 가치가 있습니다.

MQTT — IoT라면 결국 여기로

WebSocket은 메시지 보장이 없습니다. SSE는 양방향이 안 됩니다. IoT 디바이스가 100대씩 붙고 정확한 메시지 도달이 필요하면 MQTT가 답입니다. 브로커 운영(Mosquitto·EMQX)이 추가 비용이긴 하지만, 디바이스 수가 늘어날수록 단위 비용은 떨어집니다. 토픽 기반 라우팅과 QoS 0/1/2의 보장 단계는 다른 프로토콜에선 직접 구현해야 하는 기능입니다.

선택 결정표

양방향이고, 메시지 손실 OK            → WebSocket
양방향이고, 손실 절대 안 됨           → MQTT (브로커) 또는 WS + 메시지 ID·재전송 직접 구현
단방향(서버→클라), 단순                → SSE
단방향, 손실 절대 안 됨                → SSE + Last-Event-ID
양방향, 환경이 험함                     → Long Polling 폴백
IoT 디바이스 100+                      → MQTT

다음 단계

실시간을 다루기 시작하면 자연스럽게 메시지 큐(Redis Pub/Sub·NATS)가 들어옵니다. 한 서버가 받은 이벤트를 클러스터의 다른 서버에서도 구독자에게 전달해야 하기 때문입니다. 그 다음은 이벤트 소싱CDC(Change Data Capture) 까지 시야가 넓어지는데, 거기까지 가면 실시간 시스템을 처음부터 다시 설계할 준비가 된 셈입니다.

A

AlwaysCorp 기술팀

프론트엔드 개발 콘텐츠 전문

얼웨이즈 블로그에서 유용한 정보와 인사이트를 공유합니다.