
SSE vs WebSocket vs WebTransport: 2026년 실시간 웹 프로토콜 선택 가이드
요약
SSE, WebSocket, WebTransport의 차이점을 비교하고 상황별 최적의 실시간 웹 프로토콜 선택 가이드를 제공합니다. 특히 LLM 토큰 스트리밍과 같은 단방향 데이터 푸시에는 SSE가 효율적임을 강조합니다.
핵심 포인트
- 서버 단방향 푸시(LLM 스트리밍, 알림)에는 SSE가 가장 단순하고 적합함
- 양방향 통신(채팅, 협업 도구)이 필요한 경우 WebSocket을 사용
- 저지연 데이터그램이나 불안정한 네트워크 환경에는 WebTransport 권장
- 데이터의 방향성을 먼저 정의함으로써 과도한 설계를 방지할 수 있음
SSE, WebSocket, 또는 WebTransport? Server-Sent Events (SSE)를 통해 LLM 토큰을 스트리밍하는 Next.js 예시와 함께 살펴보는 2026년 실시간 웹 프로토콜 선택을 위한 실무 가이드.
몇 주 전, 저는 어떤 공개 URL이든 입력받아 AI 요약을 토큰 단위로 스트리밍해 주는 도구인 Pagewise를 출시했습니다. 이 서비스에서 스트리밍은 경험의 핵심입니다. 요약 내용이 스스로 타이핑되는 것을 지켜보는 것은, 스피너(spinner)가 돌아간 뒤 텍스트 뭉치가 한꺼번에 나타나는 것과는 비교할 수 없을 정도로 생동감이 느껴집니다.
이를 구축하기 위해 저는 단순해 보이지만 실제로는 그렇지 않은 질문에 답해야 했습니다. 어떻게 하면 서버가 브라우저로 데이터를 실시간으로 푸시(push)하게 만들 수 있을까?
2026년 현재, 세 가지 실질적인 해답이 있습니다: Server-Sent Events (SSE), WebSocket, 그리고 WebTransport입니다. 이들은 서로 대체 가능한 것이 아니며, 잘못된 것을 선택하면 인프라와 싸우거나 필요하지 않은 복잡한 장치를 구축하게 됩니다. 여기 각 프로토콜의 용도와 적합한 위치, 그리고 제가 왜 SSE를 선택했는지에 대한 이유가 있습니다.
(이 주제에 대한 저의 첫 번째 포스트에서 저는 WebTransport를 미래에 대한 "행운을 빌어보는" 도박이라고 불렀습니다. 그 생각은 3월에 바뀌었습니다. 자세한 내용은 아래에서 다룹니다.)
짧은 답변: AI 응답 스트리밍, 라이브 대시보드, 알림과 같이 서버만 데이터를 푸시해야 하는 경우에는 SSE를 사용하세요. 채팅, 멀티플레이어, 협업 편집과 같이 클라이언트와 서버가 모두 지속적으로 메시지를 보내야 하는 경우에는 WebSocket을 사용하세요. 2026년 3월부터 모든 주요 브라우저에서 지원되는 WebTransport는 신뢰할 수 없는 저지연 데이터그램(unreliable low-latency datagrams)이나 불안정한 네트워크에서의 QUIC의 탄력성이 필요할 때 사용하며, 항상 WebSocket 폴백(fallback)과 함께 사용하세요.
한 가지 질문으로 시작하세요: 누가 말을 하고 있는가?
대부분의 실시간 결정은 방향성에 달려 있습니다.
- 서버만 푸시하고 클라이언트는 듣기만 한다면, 스트리밍을 위한 가장 단순한 것을 원할 것입니다. 그것이 바로 SSE입니다.
- 양측 모두 지속적으로 메시지를 보낸다면, 양방향 채널을 원할 것입니다. 그것이 바로 WebSocket입니다.
- 양방향이면서 동시에 신뢰할 수 없는 저지연 데이터가 필요하거나(또는 불안정한 모바일 네트워크 환경에 있다면), 바로 그 지점에서 WebTransport가 등장합니다.
솔직하게 말하자면, 우리가 생각하는 것보다 실제로 양방향 통신 (bidirectional communication)이 꼭 필요한 기능의 비중은 훨씬 적습니다. "실시간 (Real-time)"이라는 용어가 "WebSocket"의 동의어처럼 사용되곤 하지만, 많은 실시간 문제들(알림, 대시보드, 진행률 표시줄, 그리고 맞습니다, LLM 출력 등)은 단방향으로만 흐릅니다. 데이터의 방향을 먼저 정의하는 것만으로도 과도한 설계를 방지할 수 있습니다.
SSE (Server-Sent Events)란 무엇인가?
Server-Sent Events (SSE)는 서버가 단일하고 오래 유지되는 HTTP 연결을 통해 브라우저로 텍스트 업데이트 스트림을 푸시할 수 있게 해주는 단방향 채널입니다. 이는 일반적인 HTTP 위에서 동작하는, "좋은 의미에서 지루한" 옵션입니다. 클라이언트가 일반적인 GET 요청을 열면, 서버는 Content-Type: text/event-stream으로 응답하며 연결을 유지하고, 데이터가 준비되는 대로 청크 (chunks)를 푸시합니다. 프로토콜 업그레이드도, 특별한 포트도 필요 없습니다. 이미 HTTP를 이해하고 있는 모든 프록시 (proxy), 로드 밸런서 (load balancer), 또는 CDN은 SSE를 이해합니다.
브라우저 측의 클라이언트 로직 전체는 EventSource API를 사용합니다:
const events = new EventSource("/api/summarize");
events.addEventListener("token", (e) => {
...
전송 형식 (wire format)은 단순한 UTF-8 텍스트입니다. 각 메시지는 빈 줄로 끝나는 작은 라인 블록입니다:
event: token
data: {"text": "Hello"}
id: 42
...
네 가지 필드가 핵심 역할을 합니다: data (페이로드 (payload)), event (이벤트 이름), id (마지막으로 확인된 이벤트 ID), 그리고 retry (밀리초 단위의 재연결 지연 시간)입니다.
제가 이 방식에서 좋아하는 점:
- 재연결 (Reconnection)이 기본으로 제공됩니다. 연결이 끊어지면 브라우저가 스스로 (기본적으로 약 3초 후에) 재연결을 시도하며, 마지막으로 확인한 ID를
Last-Event-ID헤더에 담아 보냅니다. 덕분에 서버는 중단된 지점부터 정확히 다시 시작할 수 있습니다. 순수 WebSocket을 사용할 때는 이 모든 것을 직접 구현해야 합니다. - 실시간 통신이 가질 수 있는 가장 단순한 형태입니다. 핸드셰이크 (handshake), 자체 프로토콜, 스티키 세션 (sticky sessions) 등이 필요 없습니다.
- 이미 보유하고 있는 평범한 인프라와 잘 작동합니다.
주의해야 할 점:
- 텍스트 전용입니다. SSE는 UTF-8을 전달합니다. Base64 인코딩을 통해 바이너리 (binary) 데이터를 보낼 수는 있지만, 이는 페이로드 (payload)를 약 33% 증가시키므로 바이너리 처리량 (throughput)을 위한 도구로는 적합하지 않습니다.
EventSource는 GET 방식만 지원하며 커스텀 헤더 (custom headers)를 설정할 수 없으므로,Authorization: Bearer를 사용할 수 없습니다. 이를 해결하기 위해 쿠키 (cookies)를 사용하거나,EventSource를 건너뛰고fetch()와ReadableStream리더 (reader)를 사용하여 직접 스트림 (stream)을 읽어야 합니다 (대부분의 AI SDK가 헤더를 보낼 수 있는 능력을 얻기 위해 자동 재연결 기능을 포기하고 이 방식을 사용합니다).- HTTP/1.1에서는 브라우저가 도메인당 연결을 6개로 제한하며, 열려 있는 모든 스트림이 슬롯 하나를 차지하므로 여러 탭을 사용할 때 상황이 어려워질 수 있습니다. HTTP/2는 하나의 연결을 통해 모든 것을 멀티플렉싱 (multiplexing)하므로 이 제한이 사라집니다. 현대적인 호스트를 사용한다면 거의 확실히 문제가 없을 것입니다.
- 전형적인 배포 버그: 프록시 (proxy)가 스트림을 버퍼링 (buffering)하여 토큰이 하나씩 도착하는 대신 한꺼번에 쏟아져 들어오는 현상입니다. 이런 현상이 발생한다면 버퍼링을 비활성화하십시오 (Nginx의 경우
X-Accel-Buffering: no헤더 사용).
WebSocket이란 무엇이며, SSE와 어떻게 다른가요?
WebSocket은 단일 TCP 연결을 전이중 (full-duplex) 채널로 열어두는 프로토콜로, 클라이언트와 서버가 언제든 메시지를 보낼 수 있습니다. 클라이언트 또한 지속적으로 데이터를 푸시 (push)해야 하는 경우 이것이 정답입니다. HTTP 요청과 Upgrade 헤더로 시작하여, 서버가 101 Switching Protocols로 응답하면 그 시점부터 연결은 전이중 방식이 됩니다. 즉, 요청과 응답의 쌍을 맞출 필요 없이 양측이 원할 때마다 메시지를 보낼 수 있습니다. ws:// 및 wss:// (TLS 버전이며, 반드시 이것을 사용해야 함) 스키마를 사용하며, Base64 비용 없이 바이너리 (binary) 데이터를 네이티브하게 전달합니다.
이것은 SSE가 무료로 제공하는 모든 기능에 대한 비용입니다. WebSocket은 내장된 재연결 (reconnection) 기능과 재개 (resume) 기능이 없습니다. 재연결 (보통 지수 백오프 (exponential backoff) 방식)을 직접 구현해야 하며, 놓친 메시지를 다시 재생 (replay)해야 하는 경우 시퀀스 번호 (sequence numbers)를 추적해야 하고, 끊긴 연결을 감지하기 위해 자체적인 하트비트 (heartbeats, ping 및 pong 프레임)를 실행해야 합니다. 이 모든 것이 가능은 하지만, 단지 당신이 직접 감수해야 할 작업일 뿐입니다.
상호작용이 양방향 (two-way)이고 빈도가 높은 경우, 즉 채팅, 멀티플레이어 게임, 협업 편집 (공유 문서의 실시간 커서와 같은 기능), 트레이딩 터미널의 경우에는 WebSocket을 선택하세요. 만약 WebSocket을 도입했는데 서버에서만 데이터를 보내고 있다면, 그것은 SSE가 더 적합했다는 신호입니다.
WebTransport란 무엇이며, 2026년에 사용할 준비가 되었는가?
WebTransport는 HTTP/3 및 QUIC 위에서 저지연 (low-latency) 양방향 통신을 지원하는 브라우저 API로, 신뢰할 수 있는 스트림 (reliable streams)과 신뢰할 수 없는 데이터그램 (unreliable datagrams)을 모두 지원합니다. 이 세 가지 중 가장 최신이며, 상태가 막 변화한 기술입니다. QUIC는 UDP를 기반으로 하기 때문에, TCP 기반 프로토콜이 제공할 수 없는 것들을 제공합니다:
- 신뢰할 수 있고 순서가 보장되는 스트림 (WebSocket과 유사)과 신뢰할 수 없는 데이터그램 (UDP와 유사)을 모두 지원하므로, 메시지별로 선택할 수 있습니다. 도착했을 때 이미 오래된 정보가 된 플레이어 위치 업데이트는 재전송되는 대신 폐기되어야 합니다.
- 헤드 오브 라인 블로킹 (head-of-line blocking)이 없습니다. TCP에서는 패킷 하나를 분실하면 그 뒤에 대기 중인 모든 데이터가 중단됩니다. QUIC는 독립적인 스트림들을 멀티플렉싱 (multiplexing)하므로, 하나의 스트림에서 손실이 발생해도 다른 스트림들을 멈추게 하지 않습니다.
- 연결 마이그레이션 (Connection migration). 세션 중간에 Wi-Fi에서 셀룰러로 전환되어도 연결이 유지됩니다. 이는 QUIC가 WebSocket이 의존하는 IP 및 포트 쌍이 아닌 연결 ID (connection ID)를 통해 연결을 식별하기 때문입니다.
2026년 업데이트: WebTransport는 2026년 3월 24일 Safari 26.4에 탑재되면서 브라우저 기본 지원(Baseline) 단계에 도달했습니다. Chrome, Edge, Firefox, Opera는 이미 한동안 지원해 왔으나, Safari가 마지막까지 지원하지 않아 교차 브라우저(cross-browser) 프로덕션 적용이 어려웠던 문제가 이제 해결되었습니다. Apple은 이를 멀티플레이어 및 실시간 협업과 같은 저지연(low-latency) 사용 사례를 위한 WebSocket의 현대적인 대안으로 명확히 정의했습니다.
하지만 아직 공짜 점심은 아닙니다:
- W3C와 IETF 모두에서 사양(specs)이 여전히 초안(drafts) 단계이므로, 파괴적 변경(breaking changes)이 발생할 수 있으며 서버 라이브러리가 브라우저를 따라가지 못하는 경우가 있습니다.
- 443 포트의 UDP를 사용하는데, 이는 많은 기업 및 공공 네트워크에서 차단됩니다. 만약 여기서 핸드셰이크(handshake)가 실패한다면 WebSocket 폴백(fallback)이 필요합니다. 실제로 WebTransport를 배포할 때는 단독이 아닌 안전망(safety net)과 함께 배포해야 합니다.
- 서버 툴링(tooling)이 WebSocket 생태계보다 아직 초기 단계입니다.
이를 추적하고, 데이터그램(datagrams)이나 연결 마이그레이션(connection migration)이 필요한 경우 프로토타입을 만들어 보되, 당분간은 신뢰할 수 있는 기본값으로 WebSocket을 유지하세요.
SSE vs WebSocket vs WebTransport: 비교표
| SSE | WebSocket | WebTransport | |
|---|---|---|---|
| 방향 | 서버에서 클라이언트로 | 양방향 | 양방향 |
| ... |
제가 사용하는 경험 법칙(Rules of thumb)은 다음과 같습니다:
- 서버만 푸시하고 클라이언트는 듣기만 하는 경우: SSE.
- 양쪽 모두에서 빈번하게 푸시하는 경우: WebSocket.
- 신뢰할 수 없는 데이터그램(unreliable datagrams), 헤드 오브 라인 블로킹(head-of-line blocking) 없는 스트림 멀티플렉싱(stream multiplexing), 또는 네트워크 변경 시에도 연결을 유지해야 하는 경우: WebSocket 폴백을 포함한 WebTransport.
Pagewise를 위해 SSE를 선택한 이유 (LLM 토큰 스트리밍)
Pagewise를 이 필터에 통과시켜 보면 답은 즉각적입니다. 서버는 요약본을 스트리밍하고, 클라이언트는 이를 렌더링하기만 합니다. 데이터는 텍스트(마크다운 토큰)입니다. 연결이 불안정하더라도 위치를 잃지 않고 버텨야 합니다. 그리고 서버리스(serverless) 환경에서 실행됩니다.
이 모든 조건은 위에서 아래까지 SSE 열에 해당합니다.
그것을 결정지은 두 번째 이유는 다음과 같습니다: LLM(Large Language Model) 생태계 전체가 이미 이런 방식으로 스트리밍을 수행하고 있다는 점입니다. OpenAI와 Anthropic 모두 토큰을 text/event-stream 방식으로 전달합니다 (OpenAI는 data: [DONE] 마커로 끝나는 data: 라인을 보내고, Anthropic은 content_block_delta와 같은 이름이 지정된 이벤트를 보냅니다). Vercel AI SDK는 버전 5부터 스트리밍 프로토콜로 SSE를 표준으로 채택했습니다. SSE를 선택한다는 것은 흐름을 거스르는 대신 흐름을 타고 가는 것을 의미했습니다.
Next.js route handler에서 SSE를 구현하는 방법
다음은 Claude의 출력을 타이핑되는 이벤트로 스트리밍하는 Next.js route handler인 Pagewise에서의 구현 형태입니다 (여기서는 스트리밍의 핵심 요소만 간략하게 작성되었습니다):
// app/api/summarize/route.ts
export async function POST(req: Request) {
const encoder = new TextEncoder();
...
여기에는 "스트리밍된다"와 "마지막에 모든 것을 한꺼번에 쏟아낸다"의 차이를 만드는 두 가지 요소가 있습니다:
- 이것은 Server Action이 아니라 route handler입니다. Server Action은 데이터 변경(mutation)을 위해 구축되었으며 HTTP 응답을 점진적으로 스트리밍할 수 없으므로, 스트리밍은 반드시 route 내에서 이루어져야 합니다.
- 생성 루프는
start()내부에서 백그라운드로 실행됩니다. 만약Response를 반환하기 전에 전체 생성을await해버리면, 프레임워크가 이를 버퍼링(buffering)하게 되어 사용자는 피하려고 했던 '스피너가 돌다가 갑자기 텍스트 폭탄이 쏟아지는' 경험을 하게 됩니다.
타입이 지정된 이벤트(metadata, token, done, error)는 클라이언트의 각 핸들러로 깔끔하게 매핑됩니다. 그리고 이것은 서버리스(serverless) 환경이기 때문에, 절대 무시할 수 없는 한 가지 숫자는 함수의 최대 실행 시간(maximum duration)입니다. 긴 생성 과정은 이 시간을 초과할 수 있으므로, maxDuration을 신중하게 설정해야 합니다. 만약 응답이 매우 오래 걸릴 수 있다면, 함수가 계속 살아있기를 기대하기보다는 재개 가능한 스트림(resumable streams)을 고려하십시오.
자주 묻는 질문 (FAQ)
SSE가 WebSocket보다 나은가요?
추상적인 관점에서 어느 하나가 더 낫다고 할 수는 없습니다. 두 기술은 서로 다른 문제를 해결합니다. 알림, 대시보드, LLM 토큰 스트리밍 (token streaming)과 같이 서버가 데이터를 밀어주기만 하면 되는 경우에는 SSE가 더 단순한 선택입니다. 채팅이나 멀티플레이어 게임처럼 클라이언트와 서버가 모두 지속적으로 메시지를 주고받아야 하는 경우에는 WebSocket이 적합한 도구입니다.
SSE가 HTTP/2에서 작동하나요?
네, 작동하며 HTTP/2 환경에서 더 효율적입니다. HTTP/1.1에서는 브라우저가 도메인당 단 6개의 연결만 허용하며, 열려 있는 각 SSE 스트림이 하나의 연결을 사용합니다. HTTP/2는 단일 연결을 통해 여러 스트림을 멀티플렉싱 (multiplexing)하므로, 이러한 제한이 사실상 사라집니다. 대부분의 현대적인 호스트는 기본적으로 HTTP/2를 제공합니다.
SSE로 이진 데이터 (binary data)를 보낼 수 있나요?
직접적으로는 불가능합니다. SSE 스트림은 UTF-8 텍스트입니다. 이진 데이터를 전송하기 위해 base64 인코딩 (base64-encode)을 사용할 수 있지만, 이는 페이로드 (payload) 크기를 약 1/3 정도 증가시킵니다. 따라서 실제 이진 데이터 처리량 (throughput)이 중요하다면 WebSocket이나 WebTransport가 더 적합합니다.
Vercel AI SDK는 스트리밍을 위해 무엇을 사용하나요?
Server-Sent Events를 사용합니다. AI SDK 5 버전 기준으로, Vercel은 서버에서 클라이언트로의 스트리밍을 SSE로 표준화했으며, 이는 OpenAI 및 Anthropic API가 토큰을 전달하는 방식이기도 합니다. 만약 LLM 출력을 스트리밍하고 있다면, 내부적으로 거의 확실하게 SSE를 사용하고 있는 것입니다.
2026년에 WebTransport는 프로덕션 환경에서 사용할 준비가 되었나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기