WebSocket · SSE — 실시간 통신
WebSocket · SSE — 실시간 통신
HTTP 는 본래 한 번 요청 → 한 번 응답의 모델입니다. 서버가 클라이언트에게 먼저 말 걸기 어렵습니다. 채팅·알림·라이브 업데이트 같은 자리에서는 서버가 능동적으로 데이터를 보낼 수 있어야 합니다.
1. 실시간 통신에 대한 이야기
| 사건 | 시기 |
|---|---|
| Comet (long polling 의 부모 개념) | 2006 |
| WebSocket 첫 초안 (HyBi) | 2009 |
| WebSocket RFC 6455 | 2011 |
| EventSource (SSE) HTML5 표준화 | 2011 ~ |
| HTTP/2 + Server Push (별도 모델) | 2015 (이후 사용 줄어듦) |
세 가지 후보의 큰 그림:
| 모델 | 방향 | 프로토콜 | 사용처 |
|---|---|---|---|
| Short Polling | 클라이언트 주도 | 일반 HTTP | 단순 · 비용 비효율 |
| Long Polling | 클라이언트 주도, 서버 대기 | 일반 HTTP | 폴백 자리 |
| SSE | 서버 → 클라이언트 단방향 | HTTP (Content-Type: text/event-stream) | 알림 · 라이브 피드 |
| WebSocket | 양방향 | ws:// · wss:// (HTTP Upgrade) | 채팅 · 게임 · 협업 |
2. Polling
Short Polling — 클라이언트가 일정 주기로 요청해 새 데이터를 묻습니다.
setInterval(() => fetch('/messages?after=' + lastId), 3000);
장점은 단순. 단점은 빈 응답이 많아 비용 비효율, 지연이 폴링 주기에 묶임.
Long Polling — 요청을 받은 서버가 새 데이터가 생길 때까지 응답을 보류합니다. 응답 후 클라이언트가 즉시 다음 요청.
Client → /messages?after=42 ─────────────┐
│ (서버가 새 메시지 올 때까지 대기)
Server ← 200 OK [{id:43,...}] ←──────────┘
Client → /messages?after=43 ─────────────┐ (즉시 다음 폴 시작)
WebSocket 도입 전 표준 흐름이었습니다. 지금도 폴백 자리 (옛 브라우저, 일부 프록시 환경) 로 남아 있습니다.
3. SSE
EventSource API 와 text/event-stream MIME 타입으로 표준화됐습니다. 서버가 한 번 열린 HTTP 연결로 데이터를 계속 흘려보냅니다.
GET /events
Accept: text/event-stream
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
data: {"id":1,"text":"hi"}
event: ping
data: {}
id: 12
data: {"type":"update"}
클라이언트는 브라우저 기본 API 로 열 수 있습니다.
const es = new EventSource('/events');
es.onmessage = (e) => console.log(JSON.parse(e.data));
es.addEventListener('ping', () => {});
연결이 끊기면 EventSource 가 자동 재연결합니다. 마지막 받은 id 가 Last-Event-ID 헤더로 다시 보내져 누락 복구를 돕습니다.
특징:
- 단방향 (서버 → 클라이언트).
- HTTP/1.1 · HTTP/2 모두 동작. HTTP/2 에서는 한 연결의 여러 SSE 가 가능.
- 텍스트 기반.
- 표준 HTTP 라 프록시·LB 와 잘 맞는 자리.
알림·뉴스 피드·LLM 토큰 스트리밍에 자주 쓰입니다.
4. WebSocket
HTTP Upgrade 헤더로 시작해 별도 프로토콜로 전환합니다.
GET /chat
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: <base64>
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: <derived>
이후 같은 TCP 연결 위에 프레임 기반의 양방향 통신. 텍스트·바이너리 모두 가능. ping/pong 프레임으로 keep-alive.
특징:
- 양방향, 저지연.
- 별도 프로토콜이라 일부 프록시·CDN 이 처리 못 할 수 있음 (대부분은 지원).
- 연결 상태 관리 부담은 사용자 (재연결·세션·인증).
채팅·게임·협업 편집 (Yjs · CRDT) · 실시간 시세에 자주 쓰입니다.
5. Node — ws · Socket.IO
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (sock) => {
sock.on('message', (m) => sock.send(`echo ${m}`));
});
Socket.IO 는 ws 위에 layer 를 얹어 자동 재연결·룸·이벤트 emit 을 표준화합니다. 프로토콜이 표준 WebSocket 과 살짝 달라 호환은 Socket.IO 클라이언트가 필요합니다.
6. FastAPI · Spring WebFlux
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket('/ws')
async def ws(s: WebSocket):
await s.accept()
while True:
msg = await s.receive_text()
await s.send_text(f"echo {msg}")
Spring WebFlux 는 Reactive Streams 위의 WebSocket. 백프레셔 자연스러움. Spring MVC 도 @MessageMapping (STOMP over WebSocket) 으로 같은 자리 가능.
대부분의 프레임워크가 응답을 chunked 로 흘려 SSE 를 만듭니다. FastAPI 는 StreamingResponse, Express 는 res.write 루프, Spring WebFlux 는 Flux<ServerSentEvent<...>>.
7. 매니지드 서비스
| 서비스 | 모델 | 메모 |
|---|---|---|
| Pusher (2010) | 매니지드 WebSocket | 채널·이벤트 모델. SDK 풍부. |
| Ably (2016) | 매니지드 메시징 | 글로벌 분산. 메시지 보장. |
| Supabase Realtime | Postgres 변경 → WebSocket | DB 변경 스트림. |
| Firebase Realtime DB · Firestore | 실시간 DB | DB 자체가 변경 스트림. |
| Pub/Sub (Google) · SNS · EventBridge | 이벤트 백본 | 클라이언트 직접보다는 백엔드 간. |
| Centrifugo | OSS WebSocket 서버 | 자체 호스팅 매니지드 대안. |
직접 운영의 부담 (연결 수 · 스케일링 · 재연결) 을 빼고 싶으면 매니지드 서비스. 이미 PostgreSQL 가 있고 변경 알림이면 Supabase Realtime 같은 자리가 자연스럽습니다.
8. 어떤 모델을 고를까
- 단방향, 비교적 저빈도, 알림·피드 — SSE. HTTP 인프라와 잘 맞고 복원도 자동.
- 양방향, 저지연, 채팅·게임·협업 — WebSocket.
- 레거시 프록시·옛 브라우저 호환 — Long Polling 폴백.
- DB 변경 알림 — Supabase Realtime · CDC + 백엔드 → SSE/WebSocket.
9. 인증과 스케일링
표준 EventSource API 는 헤더 추가를 못 합니다. 토큰 인증은 cookie 또는 query 로 전달합니다. WebSocket 도 비슷합니다 — 첫 핸드셰이크 시점에 인증.
토큰을 query 에 두면 access log 에 남을 수 있어 cookie 가 권장된다는 의견이 자주 보입니다.
연결 수가 많아지면 단일 인스턴스의 메모리·FD 한도에 닿습니다.
인스턴스를 여러 대로 분산
→ Sticky Session 또는 Redis Pub/Sub 으로 메시지 fan-out
ALB · NLB 가 WebSocket 지원
→ ALB idle timeout 기본 60 초라 ping 으로 유지
10. 자주 걸리는 자리
idle timeout — ALB · CloudFront · 일부 프록시가 60 초 ~ 몇 분에 끊습니다. 클라이언트·서버 양쪽에서 ping/pong 으로 유지합니다.
중복 연결 — 클라이언트가 재연결 로직을 잘못 짜면 두 연결을 동시에 들고 메시지가 두 번 도착합니다.
SSE 의 EventSource 헤더 한계 — 표준 EventSource API 는 헤더 추가 못 함. 토큰 인증은 cookie 또는 query.
메시지 순서·중복 — WebSocket 이 본질적으로 신뢰 채널이지만 재연결 후 누락 가능. 서버가 마지막 ID 기반 복원을 제공해야 합니다.
백프레셔 누락 — 서버가 빠르게 보내는데 클라이언트가 못 따라가면 메모리·연결 고갈. 토큰 단위 throttle.
CORS/Origin 검증 — WebSocket 은 same-origin 정책의 영향이 다릅니다. 핸드셰이크의 Origin 헤더 검증.
TLS 미적용 — ws:// 는 평문. 운영에서는 항상 wss://. 일부 환경 (공공 와이파이) 에서 평문 WebSocket 이 끊김 보고가 있습니다.
하고픈 말
실시간 통신은 WebSocket 이 정답이라는 인상이 있지만 SSE 가 더 나은 자리가 자주 있습니다. 단방향이라면 SSE 의 단순함이 운영을 줄여 줍니다. WebSocket 은 양방향이 진짜 필요한 자리에서만 들이는 편이 안전합니다.
Next
- typeorm-readonly
- spring-multi-module
RFC 6455 — WebSocket Protocol · HTML Living Standard — Server-Sent Events · WebSockets API (MDN) · ws · Socket.IO · FastAPI WebSocket · Spring WebFlux WebSocket · Pusher · Ably · Supabase Realtime 를 참고합니다.