Files
llm-wiki/wiki/concepts/streaming-response-patterns.md

10 KiB

title, source_type, status, confidence, tags, related_projects, last_reviewed
title source_type status confidence tags related_projects last_reviewed
Streaming Response Patterns (SSE vs WebSocket vs Long-Polling vs Chunked) llm-generated draft medium
streaming
sse
websocket
http
backend
ca-skeleton
2026-06-04

Streaming Response Patterns (SSE vs WebSocket vs Long-Polling vs Chunked)

Layer: wiki/concepts/ — 일반 개념. ca-skeleton 이 이 개념을 미지원으로 결정하고 ArchUnit 으로 차단한 사실은 wiki/projects/ca-tmpl/streaming-response-support 참조.

Summary

HTTP 의 기본 통신 모델은 request-response(클라이언트가 묻고 서버가 한 번 답함)다. 이를 넘어 서버가 클라이언트로 데이터를 지속적으로/능동적으로 보내려면 별도 메커니즘이 필요하다 — 대표적으로 SSE(서버→클라이언트 단방향 push), WebSocket(양방향 full-duplex), long-polling(요청을 응답 없이 오래 붙잡아 둠), chunked transfer encoding(크기 미상 응답을 조각으로 흘려보냄)이 있다. 핵심 구분 축은 통신 방향(단/양방향)통신 모델이 request-response 를 유지하는가, server-push 로 바뀌는가 다.

Standard (공식 정의)

  • SSE (Server-Sent Events): MIME type text/event-stream, UTF-8 인코딩 필수. data: / event: / id: / retry: 필드를 가진 line-based text protocol. 클라이언트 측 API 는 EventSource(브라우저 Window/Worker context 전용 — 서버는 직접 text/event-stream 응답을 구현해야 함). 재연결 시 Last-Event-ID 헤더로 마지막 수신 event 를 서버에 전달. (WHATWG HTML §9.2)
  • WebSocket: 단일 TCP 연결 위의 full-duplex(양방향) 통신 — 각 side 가 독립적으로 언제든 송신 가능. HTTP Upgrade handshake(GET + Upgrade: websocket101 Switching Protocols)로 연결을 수립하고, handshake 이후 TCP 는 HTTP 가 아닌 WebSocket 프레임 전송에 쓰인다. HTTP 와의 유일한 관계는 handshake 가 HTTP Upgrade 로 해석되는 것뿐인 독립 프로토콜. (IETF RFC 6455 §1.2, §1.7)
  • Chunked transfer encoding: 크기를 알 수 없는 content stream 을 length-delimited buffer 의 연속으로 전송 — 전체 크기 없이 connection 을 유지하며 메시지 완료를 수신자가 알 수 있게 함(Transfer-Encoding: chunked, last-chunk = size 0). HTTP/1.1 한정 (HTTP/2 는 DATA frame 으로 별도 framing, Transfer-Encoding 자체 금지). (IETF RFC 9112 §7.1)
  • Long-polling: 클라이언트가 요청을 보내고 서버가 이벤트가 생길 때까지 응답을 지연시키는 패턴 — RFC 6455 는 WebSocket 의 탄생 배경으로 "HTTP polling/long-polling 은 HTTP 의 남용(abuse)이며 서버가 클라이언트마다 여러 TCP 연결을 유지해야 했다"고 기술한다. (RFC 6455 §1.1)
  • Spring MVC(servlet) 매핑: request.startAsync() 로 Servlet/filter 는 exit 하고 response 만 열어 둠. 응답 타입별로 — StreamingResponseBody(message conversion 우회, OutputStream 직접 write, 파일 다운로드 용), ResponseBodyEmitter(객체 stream emit, 각 객체를 HttpMessageConverter 로 직렬화), SseEmitter(ResponseBodyEmitter 의 subclass, W3C SSE 포맷). (Spring MVC vendor doc)

한계 / 주의점

  • "streaming" 이라는 단어가 두 개의 다른 것을 가리킨다: ① 통신 모델 자체 가 server-push 로 바뀌는 것(SSE/WebSocket) ② request-response 모델을 유지한 채 응답 body 만 조각 전송 하는 것(StreamingResponseBody / chunked 다운로드). 둘은 운영 부담·계약이 전혀 다르므로 묶어서 다루면 안 된다.
  • SSE 는 단방향: 서버→클라이언트만. 클라이언트→서버 메시지는 별도 일반 HTTP 요청으로. 양방향이 필요하면 WebSocket.
  • WebSocket 은 기존 HTTP 인프라와 자동 호환되지 않는다: HTTP 와 독립 프로토콜이라 reverse proxy(Nginx 등)에 Upgrade 처리 설정이 별도로 필요. envelope/필터/미들웨어 같은 기존 request-response 자산도 그대로 못 씀.
  • server-push 는 운영 비용을 키운다: connection 수 관리, 서버 재시작 시 동시 재연결(thundering herd), 멀티 서버 fan-out, timeout/heartbeat/reconnect, load balancer sticky session 등. 단발 request-response 에는 없던 부담.
  • chunked 는 HTTP/1.1 전용: HTTP/2·HTTP/3 에서 Transfer-Encoding: chunked 는 금지(별도 framing). 브라우저의 trailer section 지원도 일반화 보장 안 됨.
  • YAGNI 경계: 실제 server-push use case 가 없으면 스트리밍 도입은 speculative generality — request-response + 비동기 우회(LRO polling, webhook)로 대부분 충분.

Project Application

  • wiki/projects/ca-tmpl/streaming-response-support — ca-skeleton 이 이벤트/server-push 스트리밍을 미지원으로 결정 하고 ArchUnit import-ban 3개(no_sse_emitter / no_response_body_emitter / no_websocket_handler)로 강제. StreamingResponseBody(다운로드)는 차단 제외.

Claim-backed Knowledge

이 개념 문서의 핵심 설명은 raw source claim 으로 뒷받침된다. 공식 standard / vendor doc / 회사 사례를 분리한다.

Knowledge Point Supporting Claims Confidence Notes
SSE 는 text/event-stream(UTF-8) line-based protocol, data:/event:/id:/retry: 필드 raw/official-docs/whatwg-html-server-sent-events.md#WHATWG-SSE-C1, #WHATWG-SSE-C2 high WHATWG HTML (official-standard)
SSE 재연결은 Last-Event-ID 헤더로 마지막 event 전달, retry: 로 대기시간 설정 #WHATWG-SSE-C3, #WHATWG-SSE-C4 high 서버 활용은 구현 책임 (MAY 수준)
EventSource 는 브라우저 클라이언트 API — 서버는 text/event-stream 을 직접 구현 #WHATWG-SSE-C5 high Spring 서버 측에 EventSource 직접 적용 불가
WebSocket 은 단일 TCP 위 full-duplex, 양 side 독립 송신 raw/official-docs/rfc6455-websocket.md#RFC6455-C1 high RFC 6455 (official-standard)
WebSocket 은 HTTP Upgrade handshake(101) 이후 HTTP 와 독립 프로토콜 #RFC6455-C3, #RFC6455-C5 high reverse proxy 자동 호환 아님 — 별도 설정 필요
WebSocket 탄생 배경 = HTTP polling/long-polling 의 "HTTP 남용" + 클라이언트당 다중 TCP #RFC6455-C2 high "항상 polling 보다 우수" 는 아님 — 희소 업데이트엔 SSE/polling 적합
chunked = 크기 미상 stream 을 length-delimited buffer 로, HTTP/1.1 한정 raw/official-docs/rfc9112-http-1-1-chunked-transfer.md#RFC9112-CHUNK-C1 high HTTP/2 에선 Transfer-Encoding 금지
SseEmitter = ResponseBodyEmitter subclass, W3C SSE 포맷 / StreamingResponseBody = 파일 다운로드용 raw/official-docs/spring-mvc-async-streaming.md#SPRING-ASYNC-C4, #SPRING-ASYNC-C2, #SPRING-ASYNC-C3 high Spring vendor doc — server-push(SSE) vs 다운로드(StreamingResponseBody) 구분
SSE 멀티서버 운영 시 thundering herd(재시작 시 동시 재연결 CPU spike), 해결로 random jitter raw/company-tech-blogs/sse-realtime-notification-woowahan.md#WOOWA-SSE-C2, #WOOWA-SSE-C3 medium 우아한형제들 사례 (company-case-study) — 공식 best practice 아님, 규모별 심각도 다름

내가 설명할 수 있어야 하는 것

  • SSE / WebSocket / long-polling / chunked 각각의 공식 정의와 통신 방향(단/양방향).
  • "streaming" 이 통신 모델 변경(server-push)응답 body 청크 전송(다운로드) 두 개를 가리킨다는 점, 그리고 왜 둘을 구분해야 하는지.
  • WebSocket 이 왜 기존 HTTP 인프라(envelope, proxy)와 자동 호환되지 않는가.
  • 언제 스트리밍이 가치 있고(실시간 push, LLM token streaming), 언제 request-response + 비동기 우회(LRO polling, webhook)로 충분한가.
  • 우아한형제들 SSE/WebSocket 운영 부담 사례를 일반 법칙처럼 말하면 안 되는 지점.

Interview Questions

  • SSE 와 WebSocket 의 차이는? 어떤 상황에 각각을 고르나?
  • 서버가 클라이언트에 능동적으로 데이터를 보내야 할 때, 스트리밍 없이 해결하는 방법은? (LRO polling, webhook)
  • StreamingResponseBodySseEmitter 는 둘 다 "스트리밍" 인데 무엇이 다른가?
  • WebSocket 을 도입하면 reverse proxy/load balancer 설정이 왜 달라지나?
  • 스트리밍을 도입하지 않기로 결정한다면, 그 결정을 코드 레벨에서 어떻게 강제할 수 있나?

Do Not Overclaim

  • 회사 기술 블로그(우아한형제들) 사례 = 공식 best practice 아님. thundering herd / jitter / fan-out 은 그 회사 규모·스택(WebFlux + Coroutine + Kafka 등) 특화이며 일반 법칙으로 단정 금지.
  • "WebSocket 이 polling 보다 항상 우월" → 금지. RFC 6455 자체가 희소 업데이트엔 다른 선택이 적합할 수 있다고 시사.
  • "SseEmitter 가 Last-Event-ID replay 를 자동 지원" → 금지. 서버 측 event store 를 별도 구현해야 함 (vendor doc 주의).
  • 개념 문서는 구현 등급을 매기지 않는다. 실제 구현/검증 여부는 wiki/projects/ca-tmpl/streaming-response-support 에서 판정.

Sources