- Published on
2026년 실시간 웹 — WebSocket·SSE·WebTransport·WebRTC 심층 비교 (LLM 스트리밍이 SSE를 선택한 이유까지)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
프롤로그 — "실시간"이라는 단어의 함정
"실시간 기능을 넣어주세요"라는 요청을 받는다. 머리에 가장 먼저 떠오르는 단어는 WebSocket이다. 그래서 일단 WS 서버를 띄우고, 라우터 뒤에 sticky session을 붙이고, 토큰 인증을 얹는다. 6개월 뒤, 같은 회사의 다른 팀이 LLM 토큰을 스트리밍하는데 — 이상하게도 — SSE를 쓰고 있다. 한 명은 묻는다: "왜 WebSocket이 아니지?"
답은 단순하지 않다. "실시간"은 단일 문제가 아니라 스펙트럼이다.
- 채팅 한 줄을 전 세계 1만 명에게 50 ms 안에 — 양방향 저지연.
- LLM 토큰을 사용자 한 명에게 1분 동안 — 한 방향 스트림.
- 게임 좌표를 1초에 60번, 패킷이 하나 빠져도 다음 패킷에 따라잡힘 — 다대다 비신뢰 데이터그램.
- 화상 통화의 비디오/오디오 + 데이터 — P2P 미디어.
- 5분에 한 번 알림을 폰에 — 그건 푸시지 실시간이 아니다.
각 문제에 맞는 프로토콜은 다르다. 이 글은 2026년 시점의 다섯 가지 후보 — WebSocket, Server-Sent Events(SSE), WebTransport, WebRTC DataChannel, Long Polling — 를 비교한다. 보너스로 HTTP/2 Server Push가 왜 죽었는지, 그리고 LLM 스트리밍이 거의 모두 SSE로 수렴한 진짜 이유까지.
TL;DR. 한 방향 스트리밍이면 SSE. 양방향 저지연이면 WebSocket. 게임처럼 비신뢰·다중 스트림이면 WebTransport. P2P 또는 미디어면 WebRTC. 그리고 — 정직하게 — "그냥 polling"으로 충분한 경우가 많다.
1장 · 풍경 — 2026년 다섯 가지 프로토콜
브라우저 ◀──────── 서버 (또는 다른 브라우저)
─────────────────────────────────────────────
WebSocket 양방향, TCP, 프레임 기반
SSE (EventSource) 서버→클라 단방향, HTTP 위
WebTransport 양방향, QUIC/HTTP3, 다중화
WebRTC DataChannel P2P, UDP/SCTP, 비신뢰 가능
Long Polling HTTP 폴링, 응답 보류
각 프로토콜을 한 문장으로 요약하면:
- WebSocket — 단일 TCP 연결 위에 양방향 프레임. 2011년 표준화, 가장 보편적, 가장 잘 알려진 함정도 가장 많음.
- Server-Sent Events — HTTP 응답을 닫지 않고 텍스트 스트림을 흘려보냄.
text/event-streamMIME, 자동 재연결 내장. 단방향(서버→클라). - WebTransport — QUIC 위의 양방향. 신뢰 스트림 + 비신뢰 데이터그램을 한 연결로. 헤드 오브 라인 블로킹 없음. Chrome 안정판에서 사용 가능.
- WebRTC DataChannel — 두 피어 사이의 직접 연결. P2P, 옵션으로 비신뢰. 운영이 가장 어려움.
- Long Polling — 옛 친구. 응답을 보류하다 이벤트 발생 시 응답. 모든 환경에서 동작.
그리고 죽은 사람: HTTP/2 Server Push. Chrome이 2022년에 비활성화했고, 명세에서 제거가 진행됐다. 캐시 동작이 미묘하고 실제 성능 이득이 적었다.
2장 · WebSocket — 여전히 일꾼, 여전히 함정
WebSocket은 2011년 RFC 6455로 표준화됐다. 핵심은 단순하다: HTTP/1.1 Upgrade 헤더로 연결을 시작해, 그 후엔 TCP 위에 자체 프레임(텍스트/바이너리/Ping/Close)을 양방향으로 흘린다.
머릿속 모델
Client Server
│ GET /ws HTTP/1.1 │
│ Upgrade: websocket │
│ ──────────────────────────────▶ │
│ │
│ ◀────────────────────────────── │
│ HTTP/1.1 101 Switching │
│ │
│ ◀══════ frame ══════▶ │ (양방향)
│ ◀══════ frame ══════▶ │
│ │
│ Close 0x88 │
Upgrade 핸드셰이크가 끝나면 그 TCP 연결은 더 이상 HTTP가 아니다. 그래서 라우터·로드밸런서·프록시·CDN의 동작이 미묘해진다.
강점
- 진짜 양방향. 클라이언트가 언제든 보낼 수 있고, 서버도 언제든 보낼 수 있다.
- 저지연. 핸드셰이크 한 번, 이후 프레임 오버헤드는 2~14 바이트.
- 바이너리 안전. 게임·이진 프로토콜에 직접 쓸 수 있다.
- 거의 모든 브라우저·언어·SDK 지원.
함정
- HTTP/2가 아니다. WS 자체는 HTTP/1.1 업그레이드다. RFC 8441로 HTTP/2 위 WebSocket이 가능하지만 서버 구현이 들쭉날쭉.
- 로드밸런서 sticky session. WS 연결은 같은 백엔드 인스턴스에 고정되어야 한다 — Pub/Sub 같은 fan-out 계층이 필요.
- 헤드 오브 라인 블로킹. 단일 TCP라, 한 메시지가 큰 페이로드면 그 뒤가 막힌다.
- 자동 재연결 없음. 직접 짜야 함 — exponential backoff, 메시지 재전송 큐, sequence number.
- 프록시 비호환. 일부 기업 프록시는 WS 핸드셰이크를 끊는다.
- HTTP 캐시 우회. GET이 아니므로 CDN 캐시 혜택 없음.
작은 채팅 서버 (Bun + ws 스타일)
// server.ts — 미니멀 WS 에코·브로드캐스트
import { WebSocketServer } from 'ws'
const wss = new WebSocketServer({ port: 8080 })
const clients = new Set<WebSocket>()
wss.on('connection', (ws) => {
clients.add(ws)
ws.send(JSON.stringify({ type: 'welcome', count: clients.size }))
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString())
// 모든 다른 클라이언트에게 브로드캐스트
for (const c of clients) {
if (c !== ws && c.readyState === 1) {
c.send(JSON.stringify({ ...msg, ts: Date.now() }))
}
}
})
ws.on('close', () => clients.delete(ws))
})
클라이언트 쪽:
const ws = new WebSocket('wss://example.com/ws')
ws.onopen = () => ws.send(JSON.stringify({ type: 'hello', name: '준' }))
ws.onmessage = (e) => console.log('recv:', JSON.parse(e.data))
ws.onclose = () => {
// 직접 재연결 로직을 작성해야 한다 — 백오프, jitter, 인증 토큰 갱신…
}
이게 끝이 아니다. 프로덕션은 — 인증·하트비트·재연결·메시지 큐·롤아웃 시 graceful drain — 까지 합쳐 수백 줄로 자란다.
3장 · Server-Sent Events — LLM 스트리밍이 고른 우승자
SSE는 2009년에 HTML5 명세로 들어왔다. 너무 단순해서 처음엔 우습게 봤지만, 2024~2026년의 LLM 스트리밍 붐과 함께 사실상 표준이 됐다.
머릿속 모델
Client Server
│ GET /stream HTTP/1.1 │
│ Accept: text/event-stream │
│ ──────────────────────────────────▶ │
│ │
│ ◀────────────────────────────────── │
│ HTTP/1.1 200 OK │
│ Content-Type: text/event-stream │
│ │
│ ◀── data: {"token":"안녕"}\n\n │
│ ◀── data: {"token":"하세요"}\n\n │
│ ◀── data: [DONE]\n\n │
│ │
│ (응답이 영원히 안 닫힘) │
연결은 그냥 HTTP 응답이다. 서버는 응답을 닫지 않고 data: ...\n\n 형식으로 한 줄씩 흘려보낸다. 클라이언트는 EventSource 객체로 자동 파싱·재연결한다.
강점
- 단순하다. 그냥 HTTP다. 헤더, 메서드, 캐시 정책 그대로.
- 자동 재연결.
EventSource는 끊기면 자동으로 다시 연결,Last-Event-ID헤더로 재개. - 방화벽 친화적. 일반 HTTP니까 어떤 프록시도 통과.
- HTTP/2·HTTP/3 자동 사용. 다중화도 무료로.
- 단방향 = 서버 로직이 단순. Pub/Sub fan-out에 자연스럽게 맞음.
- CDN·gzip 호환. 응답이지 업그레이드가 아니라서.
약점
- 단방향뿐. 클라이언트가 서버에 보내려면 별도 POST. (대부분의 LLM API가 정확히 이 패턴.)
- 텍스트 전용. UTF-8 텍스트. 바이너리 보내려면 base64 — 비효율.
- 연결 수 제한. HTTP/1.1에선 도메인당 6 — HTTP/2에선 거의 무한.
- 타임아웃. 일부 프록시(특히 nginx 기본값)는 idle 응답을 끊는다 — 주기적 ping이 필요.
왜 LLM 스트리밍이 SSE를 골랐는가
OpenAI, Anthropic, Google Gemini, Cohere — 거의 모든 LLM 토큰 스트리밍 API는 SSE다. 자료: OpenAI Streaming docs https://platform.openai.com/docs/api-reference/streaming, Anthropic Messages Streaming https://docs.anthropic.com/en/api/messages-streaming. 이유는 일곱 가지:
- 본질적으로 단방향. 프롬프트는 한 번에 POST, 답은 토큰별로 흐른다. WS의 양방향이 필요 없다.
- 재시도가 자연스럽다. 보통의 HTTP 재시도 정책(429, 500, 5xx)이 그대로 작동.
- 인증·rate limit·과금이 표준 HTTP 미들웨어로 들어간다. WS는 매번 직접 짜야 함.
- 방화벽·프록시 통과. 기업망에서 WS는 막힐 수 있음.
- CDN과 호환. Cloudflare·Vercel Edge Functions가 SSE를 일급으로 지원.
- 테스트가 쉽다.
curl -N이면 끝. - HTTP/2·HTTP/3에서 멀티플렉싱이 공짜. 동시 스트림이 한 연결을 공유.
WebSocket을 쓰면 이 일곱 가지가 전부 직접 작성 항목이 된다.
30줄 토큰 스트리머 (Bun/Node)
// app/api/chat/route.ts — 30줄 SSE 토큰 스트리머
export const runtime = 'edge'
export async function POST(req: Request) {
const { prompt } = await req.json()
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder()
const send = (data: unknown) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
}
// 가상 LLM: 토큰을 50 ms마다 보냄
const tokens = ['안녕', '하세요', ',', ' SSE', ' 데모', '입니다', '.']
for (const t of tokens) {
send({ token: t })
await new Promise((r) => setTimeout(r, 50))
}
send('[DONE]')
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
},
})
}
클라이언트 — EventSource는 POST를 못 받으므로 fetch 스트리밍 또는 SSE 라이브러리(예: eventsource-parser)를 쓴다.
const res = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt: '안녕' }),
headers: { 'Content-Type': 'application/json' },
})
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buf = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buf += decoder.decode(value, { stream: true })
for (const line of buf.split('\n\n')) {
if (line.startsWith('data: ')) console.log(line.slice(6))
}
buf = ''
}
이게 ChatGPT·Claude UI 토큰 스트리밍의 모습이다. 40줄로 끝난다.
4장 · WebTransport — HTTP/3 시대의 새 일꾼
WebTransport는 IETF가 2024년 RFC 9484로 표준화한 새 프로토콜이다. QUIC 위의 양방향 + 데이터그램을 한 연결로 제공한다. 자료: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API.
머릿속 모델
브라우저 ──────── QUIC (UDP 위) ──────── 서버
│
├── 양방향 신뢰 스트림 #1
├── 양방향 신뢰 스트림 #2
├── 단방향 신뢰 스트림 #3
└── 비신뢰 데이터그램 (UDP-스럽게)
한 QUIC 연결 위에서 여러 스트림을 동시에. 한 스트림의 패킷 로스가 다른 스트림을 막지 않는다 — 헤드 오브 라인 블로킹이 없다. WebSocket의 가장 큰 약점이 사라진 셈.
2026년 브라우저 지원 현황
- Chrome/Edge — 안정판 지원 (Chrome 97+, 2022년부터). https://chromestatus.com/feature/4854541929873408
- Firefox — 안정판 지원 (Firefox 114+, 2023년부터). 기본 활성화는 작업 중.
- Safari — 2026년 5월 현재 부분 지원(Safari Tech Preview에서 실험적). 안정판 전면 활성화는 아직.
서버 구현: aioquic(Python), quinn(Rust), msquic(MS), Cloudflare Workers WebTransport API. 자료: https://github.com/aiortc/aioquic.
강점
- 헤드 오브 라인 블로킹 없음. 게임·다중 콘텐츠 스트림에 결정적.
- 신뢰·비신뢰 둘 다. 비신뢰 데이터그램은 게임 좌표·VOIP 보조 트랙에 이상적.
- 연결 마이그레이션. QUIC 특성 — 와이파이→셀룰러로 IP가 바뀌어도 연결 유지.
- TLS 1.3 필수. 보안 기본.
약점
- Safari 미완. 모든 사용자 대상이면 폴백 필요.
- 운영 도구 미성숙. 디버깅 도구·로깅·메트릭 생태계가 WS만큼 풍부하지 않음.
- UDP 차단. 기업망·통신사망 일부에서 UDP가 막혀 폴백이 필요.
- 인증서 요구사항. 도메인 인증서가 필요(자체서명 도메인이면
serverCertificateHashes옵션으로 우회 가능).
데이터그램 데모
// 게임 좌표를 30 fps로 비신뢰 데이터그램으로 보냄
const transport = new WebTransport('https://game.example.com/wt')
await transport.ready
const writer = transport.datagrams.writable.getWriter()
const reader = transport.datagrams.readable.getReader()
// 송신: 좌표 패킷
setInterval(async () => {
const buf = new ArrayBuffer(8)
const view = new DataView(buf)
view.setFloat32(0, player.x)
view.setFloat32(4, player.y)
await writer.write(new Uint8Array(buf))
}, 33)
// 수신: 상대 좌표
;(async () => {
while (true) {
const { value, done } = await reader.read()
if (done) break
const view = new DataView(value.buffer)
other.x = view.getFloat32(0)
other.y = view.getFloat32(4)
}
})()
이걸 WebSocket으로 똑같이 하면 — 좌표 한 패킷이 늦으면 그 뒤가 막힌다. WebTransport는 그냥 새 데이터그램이다. 잃어버리면 잃어버린 채로.
5장 · WebRTC DataChannel — P2P의 왕, 운영의 지옥
WebRTC는 본래 화상 통화용으로 만들어졌다. RTCDataChannel은 비디오/오디오 트랙과 같은 P2P 연결 위에 임의 데이터를 흘릴 수 있게 해주는 보조 API다.
머릿속 모델
Peer A Peer B
│ (시그널링 서버) │
│ ───── SDP offer ────────▶ │
│ ◀──── SDP answer ───────── │
│ ───── ICE candidates ───▶ │
│ ◀──── ICE candidates ───── │
│ │
│ ╔════ P2P SCTP/UDP ════╗ │
│ ║ DataChannel ║ │
│ ║ Video/Audio ║ │
│ ╚══════════════════════╝ │
핵심은 시그널링은 별도라는 점이다. WebRTC는 두 피어의 IP·포트·미디어 능력을 협상하는 동안 메시지를 전달할 채널이 필요하다 — 그게 시그널링 서버다(보통 WebSocket이나 SSE로 구현).
강점
- 진짜 P2P. 서버를 경유하지 않으니 지연이 가장 낮고, 서버 대역폭이 들지 않음.
- 비신뢰 모드.
ordered: false, maxRetransmits: 0으로 UDP스럽게. - NAT 통과. STUN·TURN 인프라가 NAT 뒤의 사용자도 연결.
- 미디어와 일체. 화상통화에 데이터 채널을 한 연결에 얹을 수 있음.
약점
- 운영이 가장 어렵다. 시그널링 서버 + STUN 서버 + TURN 서버(NAT 통과 실패 시 폴백) — 세 가지 인프라.
- TURN은 P2P가 아니다. TURN으로 폴백하면 사실상 서버 릴레이고, 트래픽·과금이 폭발.
- 연결 시간이 길다. 수백 ms~수 초의 ICE 협상.
- 브라우저 호환성의 미세 차이. Chrome/Firefox/Safari가 모서리에서 다르게 동작.
언제 쓰나
- 화상/음성 통화에 부가 데이터(채팅·파일 전송·게임 입력).
- 진짜 P2P 게임(서버 비용 절감, 지연 최소화).
- 파일 공유(WebTorrent류).
- 메타버스·VR — 위치·표정 등 고빈도 데이터.
LLM 토큰 스트리밍에 WebRTC를 쓰는 사람은 없다. 그 칼은 사용처가 좁고 운영 부담이 크다.
6장 · Long Polling — 옛 친구, 죽지 않았다
브라우저는 HTTP/1.1 시절부터 polling을 했고, 그게 진화한 게 Long Polling이다. 클라가 GET을 보내면 서버는 응답을 즉시 닫지 않고 이벤트가 생길 때까지 보류한다. 이벤트가 생기면 응답하고, 클라는 즉시 다시 GET.
머릿속 모델
Client Server
│ GET /events │
│ ──────────────────────────▶ │
│ │ (이벤트 대기, 보류)
│ │
│ │ 이벤트 발생!
│ ◀────── 200 OK [event] ───── │
│ GET /events?since=... │
│ ──────────────────────────▶ │
│ │ (또 대기)
지금 봐도 단순하고, 동작 안 하는 환경이 없다.
강점
- 모든 환경에서 동작. HTTP/1.0이라도. 어떤 프록시도 통과.
- 인증·CORS·캐시·CDN 모두 표준 HTTP.
- 상태가 적다. 한 요청-한 응답이라 서버가 stateless로 갈 수 있음.
- fallback으로 최고. WS·SSE·WT 모두 실패해도 polling은 동작.
약점
- 요청 오버헤드. 매 이벤트마다 HTTP 헤더 비용.
- 지연이 변동적. 응답 후 다음 GET 사이에 갭이 있음.
- 타임아웃 균형. 너무 길면 프록시가 끊고, 너무 짧으면 polling이 됨.
2026년 현실
여전히 살아 있다. 특히 — 모바일 SDK 폴백, 사내 LAN 채팅, 알림 시스템 — 에서. 그리고 SSE를 못 쓰는 환경(IE는 죽었지만 일부 임베디드 브라우저)에서. "쿨"하지 않다고 무시하면 안 된다.
7장 · 죽은 자 — HTTP/2 Server Push의 짧은 생애
HTTP/2 Server Push는 2015년에 등장했다. 아이디어는 깔끔했다 — 클라가 요청하기 전에 서버가 미리 리소스를 푸시. 페이지가 빨라지리라.
현실은 실망스러웠다. Chrome 텔레메트리(2020~2021)에서 대다수 푸시가 — 이미 캐시에 있어서 — 낭비됐다. 캐시 동작이 미묘했고, 구현이 일관되지 않았다. Chrome은 2022년에 비활성화했다. 자료: https://developer.chrome.com/blog/removing-push. 명세에서도 제거됐다.
대안. 진짜 리소스 푸시가 필요하면 103 Early Hints + <link rel=preload>. 메시지 푸시면 SSE.
8장 · 의사결정 매트릭스
| 유스 케이스 | 1순위 | 대안 | 비고 |
|---|---|---|---|
| LLM 토큰 스트리밍 | SSE | WS | 서버→클라 단방향, 재시도·CDN 친화 |
| 채팅 (1:1, 1:다) | WebSocket | SSE+POST | 양방향 저지연 |
| 멀티플레이어 게임 (실시간) | WebTransport | WebRTC | 비신뢰 데이터그램, no HoL blocking |
| 대시보드·실시간 메트릭 | SSE | WS | 단방향, 자동 재연결 |
| 협업 편집기 (CRDT) | WebSocket | WT | 양방향 + 메시지 순서 |
| 화상 통화 | WebRTC | — | 미디어 + 데이터 한 채널 |
| 파일 전송 P2P | WebRTC | WS 릴레이 | 서버 대역폭 절감 |
| 알림 (저빈도) | Long Polling | SSE | 서버 자원·복잡도 최소 |
| 주식·코인 시세 (브로드캐스트) | SSE | WS | 단방향, fan-out 단순 |
| IoT 명령 (양방향) | WebSocket | WT | 작은 페이로드, 양방향 |
| 폴백 마지막 한 줄 | Long Polling | — | 어떤 환경도 통과 |
이 표는 정답이 아니라 시작점이다. 모든 결정에는 트레이드오프가 있고, 팀의 운영 능력·기존 인프라·사용자 환경이 매트릭스를 흔든다.
9장 · 정직한 결정 프레임워크
다섯 가지 질문을 순서대로 던지자.
Q1. 진짜로 양방향인가, 아니면 단방향인가?
LLM 답변은 단방향이다. 프롬프트는 POST 한 번이고, 답은 토큰 스트림. 채팅 메시지도 사실 — 송신은 POST로 끝나고, 수신만 stream — 으로 보면 단방향이다.
진짜 양방향은 두 방향에서 동시에 메시지가 흐를 때다 — 협업 편집의 CRDT 업데이트, 멀티플레이어 게임의 입력/상태, IoT 양방향 명령.
단방향이면 SSE를 먼저 검토. 비용·운영·복잡도가 압도적으로 낮다.
Q2. 신뢰가 필요한가, 비신뢰면 되는가?
채팅 메시지·결제 이벤트·상태 업데이트 — 신뢰가 필수다. TCP·QUIC 스트림.
게임 좌표·VOIP·실시간 센서 — 비신뢰 OK. 한 패킷 빠져도 다음 게 따라잡으면 끝. UDP·QUIC datagram.
비신뢰 필요하면 WebTransport(또는 WebRTC). WS는 비신뢰 모드가 없다.
Q3. P2P가 정말 필요한가?
P2P는 — 정말 — 서버를 거치지 않는 경우만이다. 화상통화, 파일 전송, 진짜 P2P 게임. 그 외에는 클라이언트-서버가 압도적으로 간단하다.
P2P 한다고 결정했으면 STUN + TURN 인프라 비용·운영을 받아들이는 거다. TURN으로 폴백되면 그건 사실상 릴레이고, P2P 이득이 사라진다.
Q4. 기존 인프라는?
회사가 이미 Kafka + Pub/Sub + nginx로 SSE를 잘 돌리고 있다면 — 새 기능에 WS를 넣지 마라. 운영팀이 둘 다 알아야 한다.
WS를 잘 운영 중이라면 — SSE를 새로 도입하는 게 합리적일 수도 있다. 두 프로토콜이 다른 문제를 푼다면.
Q5. 사용자의 네트워크 환경은?
기업망·통신사망에는 — UDP 차단, WS 차단, idle 응답 차단 — 별 게 다 있다. 사용자가 다양한 환경이면 폴백이 필수다.
WT 시도 ──실패──▶ WS 시도 ──실패──▶ SSE 시도 ──실패──▶ Long Polling
대부분의 프로덕션 실시간 스택은 이 폴백 체인을 어느 정도 갖는다.
10장 · 실전 사례 — 누가 무엇을 쓰는가
- ChatGPT, Claude, Gemini API의 토큰 스트리밍 — SSE. 자료: OpenAI https://platform.openai.com/docs/api-reference/streaming, Anthropic https://docs.anthropic.com/en/api/messages-streaming.
- Slack 메시지 — 한때 WebSocket. 2025년경 모바일은 HTTP 폴링 + 푸시 노티 혼합으로 이동.
- Discord — WebSocket(메시지) + WebRTC(음성/비디오). 자료: https://discord.com/developers/docs/topics/gateway.
- Figma 동시 편집 — WebSocket + 자체 CRDT.
- Google Docs — Long Polling에서 출발, WebSocket으로 이행.
- Cloudflare Stream/Workers — WebTransport 지원, 게임·라이브 비디오 워크로드를 받음.
- Zoom — WebRTC(미디어) + 자체 시그널링.
- GitHub Live Updates — SSE.
- Twitch IRC (채팅) — TCP + 자체 프로토콜, 웹 클라이언트는 WebSocket 위에서 IRC를 흘림.
- 주식 거래 플랫폼 — 거의 WebSocket(브로드캐스트 + 주문) 또는 자체 바이너리 TCP(데스크탑).
패턴이 보인다 — 단방향 스트리밍은 SSE, 양방향 채팅은 WS, 미디어는 WebRTC, 게임은 점진적으로 WT.
11장 · 운영 노트 — 진짜로 아픈 부분들
sticky session
WS·WT는 클라이언트가 같은 백엔드 인스턴스에 묶여야 한다. ALB·Envoy·HAProxy의 sticky session 설정 필요. SSE는 — 응답이 끝까지 한 인스턴스에서 흐르긴 하지만 — 재연결마다 다른 인스턴스로 가도 OK(서버가 stateless하다면).
graceful shutdown
배포할 때 WS 연결을 끊으면 사용자 1만 명이 동시에 재연결을 시도해 thundering herd. 정해진 패턴:
- 새 연결을 거부(헬스체크 fail).
- 기존 연결에 "곧 끊겠다" 메시지 전송.
- 클라가 jitter 두고 자발적 재연결.
- 30~60초 후 강제 종료.
SSE도 같은 문제. Long Polling은 — 한 요청이 짧으니 — 운이 좋다.
인증
WS의 Upgrade 핸드셰이크는 HTTP니까 쿠키·헤더·토큰 다 보낼 수 있다. 그러나 — 일부 라이브러리는 핸드셰이크 후 인증을 확인 — 이 패턴은 위험. 핸드셰이크 단계에서 인증하라.
WT는 인증서 기반 + 헤더. SSE는 — 그냥 HTTP니까 — 가장 단순.
WebRTC는 시그널링 서버 단계에서 인증. P2P 연결 자체는 DTLS-SRTP로 암호화.
비용
- WS·SSE — 서버 컴퓨트는 연결 수에 비례. 대역폭은 메시지 수에 비례.
- WT — 비슷하지만 QUIC CPU 오버헤드가 약간 더 크다(2026년 시점).
- WebRTC — 서버는 시그널링·STUN·TURN. TURN 사용량이 비용 폭탄.
- Long Polling — 연결당 CPU·메모리 + 매 요청 HTTP 헤더 오버헤드.
대규모(연결 10만 이상)에서는 — 한 연결당 메모리 — 가 결정적. Node WS는 약 3050 KB/연결, Go·Rust는 515 KB. 언어 선택이 운영비를 좌우.
모니터링
- WS — 연결 수, 메시지 처리량, 평균 연결 수명, 재연결 빈도.
- SSE — 활성 응답 수, 응답 수명, 데이터 인코딩 에러.
- WT — 활성 세션, 스트림당 처리량, 데이터그램 손실률.
- WebRTC — ICE 성공률, TURN fallback 비율, 미디어 RTT.
- Long Polling — RPS, 보류 시간 분포.
OpenTelemetry는 — WS 일부, SSE 완전, WT는 2026년 시점 진행 중 — 지원이 다르다.
12장 · 직접 만들어보는 WT 데이터그램 서버 (Node + aiortc 없이)
브라우저 데모는 위에 있고, 서버는 Rust wtransport나 Python aioquic가 가장 흔하다. Node는 2026년 5월 현재 @fails-components/webtransport 같은 패키지가 가장 성숙.
// server.ts — 최소 WebTransport 데이터그램 에코
import { Http3Server } from '@fails-components/webtransport'
import { readFileSync } from 'node:fs'
const server = new Http3Server({
port: 4433,
host: '0.0.0.0',
secret: 'demo',
cert: readFileSync('./cert.pem'),
privKey: readFileSync('./key.pem'),
})
server.startServer()
const stream = await server.sessionStream('/wt')
const reader = stream.getReader()
while (true) {
const { value: session, done } = await reader.read()
if (done) break
await session.ready
const dgReader = session.datagrams.readable.getReader()
const dgWriter = session.datagrams.writable.getWriter()
;(async () => {
while (true) {
const { value, done } = await dgReader.read()
if (done) break
// 에코백
await dgWriter.write(value)
}
})()
}
데모용 인증서가 필요하고(브라우저가 자체서명을 받으려면 serverCertificateHashes 옵션 필요), Chrome 플래그를 켜서 테스트.
13장 · 안티패턴 10가지
- 모든 실시간 요구사항에 WebSocket 반사적으로 선택. 단방향이면 SSE가 더 단순·저렴.
- 인증을 핸드셰이크 이후에. WS upgrade 단계에서 인증하지 않으면 미인증 연결을 잠시 받게 됨.
- 자동 재연결을 직접 짜지 않음. 모바일 네트워크에서 끊김은 일상.
- 하트비트 없음. idle 프록시가 30초 후 끊을 때 모름.
- 메시지 sequence ID 없음. 재연결 후 중복·결손 메시지를 못 잡음.
- graceful shutdown 없이 재배포. thundering herd 자가 DDoS.
- 거대한 단일 메시지. WS의 head-of-line blocking을 자기 손으로 트리거.
- WebRTC를 P2P 아닌 곳에. 클라이언트-서버면 WS·WT가 더 간단.
- HTTP/2 Server Push를 부활시키려고 시도. 죽은 자다.
- 폴백 체인 없음. 사용자의 30%가 회사 프록시 뒤에 있을 수 있음.
14장 · 2026년 예측과 미래
- WebTransport — Safari가 안정판에서 켜는 순간(2026~2027 예상) 게임·미디어 워크로드의 기본 후보가 됨.
- HTTP/3 + SSE — 멀티플렉싱과 0-RTT 재접속이 결합돼 SSE가 더욱 강해짐.
- MoQ (Media over QUIC) — IETF MoQ 워킹그룹이 라이브 미디어 전송 표준을 만들고 있음. 자료: https://datatracker.ietf.org/wg/moq/about/.
- Edge runtime의 SSE 일급 지원 — Vercel, Cloudflare, Deno Deploy가 SSE를 우선시.
- WebSocket over HTTP/3 — RFC 9220, 실제 채택은 아직 더디지만 진행 중.
- AI 에이전트 워크플로 — 토큰 스트리밍은 SSE, 도구 호출 결과는 SSE 이벤트 타입으로 분리하는 패턴이 확산.
LLM 시대 이후의 실시간 웹은 — 한 프로토콜이 모든 걸 푸는 게 아니라 — 유스 케이스별로 최적 도구를 고르는 시대다. WS가 모든 걸 풀던 2010년대는 끝났다.
에필로그 — 프로토콜은 도구다
이 글의 한 문장 요약: "실시간"이라는 단어를 보면 먼저 양방향인지 단방향인지를 묻고, 그다음 신뢰·비신뢰를 묻고, 그다음 P2P 여부를 묻는다. 세 질문이 4~5 줄이면 답이 나온다.
LLM 토큰 스트리밍이 SSE를 고른 건 SSE가 "최고"여서가 아니다. 그 문제에 SSE가 가장 단순한 답이기 때문이다. 같은 논리로, 협업 편집은 WebSocket이 가장 단순하고, 게임 좌표는 WebTransport가 가장 자연스럽다.
도구를 사랑하지 말고, 문제를 사랑하자.
14개 항목 체크리스트
- 양방향이 진짜 필요한가, 사실은 단방향인가?
- 신뢰가 필수인가, 비신뢰면 되는가?
- P2P가 정말 필요한가?
- 사용자 네트워크 환경(기업망·모바일·해외)을 가정했는가?
- 폴백 체인이 있는가?
- 인증을 핸드셰이크 단계에서 하는가?
- 자동 재연결 + 백오프 + jitter가 있는가?
- 하트비트·idle ping이 있는가?
- 메시지 sequence ID와 재개 메커니즘이 있는가?
- graceful shutdown 절차가 있는가?
- sticky session·세션 친화도 설정이 라우터에 있는가?
- 연결당 메모리 비용을 측정했는가?
- 모니터링·SLO가 정의돼 있는가?
- (LLM이면) SSE를 먼저 검토했는가?
안티패턴 10가지 (요약)
- 단방향에 WebSocket.
- 인증 늦음.
- 재연결 없음.
- 하트비트 없음.
- sequence ID 없음.
- graceful shutdown 없음.
- 거대 단일 메시지.
- WebRTC를 P2P 아닌 곳에.
- HTTP/2 Server Push 부활 시도.
- 폴백 체인 없음.
다음 글 예고
다음 글 후보: 에지 런타임 위 SSE — Vercel/Cloudflare/Deno에서 LLM 토큰 스트리밍 패턴 비교, MoQ(Media over QUIC) 입문 — 차세대 라이브 미디어, CRDT + WebSocket — 협업 편집의 운영 노트.
"실시간 웹의 다음 10년은 한 프로토콜이 아니라 — 문제에 맞는 도구를 고르는 — 시대다. 단방향이면 SSE, 양방향이면 WebSocket, 비신뢰 다중 스트림이면 WebTransport, P2P면 WebRTC. 그리고 그래도 모자라면 Long Polling은 죽지 않았다."
— 2026 실시간 웹 심층, 끝.
참고 / References
- WebSocket: RFC 6455 — https://datatracker.ietf.org/doc/html/rfc6455
- WebSocket over HTTP/2: RFC 8441 — https://datatracker.ietf.org/doc/html/rfc8441
- WebSocket over HTTP/3: RFC 9220 — https://datatracker.ietf.org/doc/html/rfc9220
- SSE — HTML Living Standard: https://html.spec.whatwg.org/multipage/server-sent-events.html
- EventSource MDN: https://developer.mozilla.org/en-US/docs/Web/API/EventSource
- WebTransport: RFC 9484 — https://datatracker.ietf.org/doc/html/rfc9484
- WebTransport MDN: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API
- WebTransport Chrome status: https://chromestatus.com/feature/4854541929873408
- WebRTC MDN: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API
- HTTP/2 Push removal: https://developer.chrome.com/blog/removing-push
- OpenAI Streaming API: https://platform.openai.com/docs/api-reference/streaming
- Anthropic Messages Streaming: https://docs.anthropic.com/en/api/messages-streaming
- Discord Gateway: https://discord.com/developers/docs/topics/gateway
- aioquic Python QUIC/HTTP3: https://github.com/aiortc/aioquic
- Cloudflare WebTransport: https://blog.cloudflare.com/webtransport-now-supported-in-workers/
- IETF MoQ Working Group: https://datatracker.ietf.org/wg/moq/about/