WEEK 03 · 스트리밍 UI

스트리밍 UI는 어디서 깨지는가

SSE · 청크 파싱 · 한글 디코딩 · React 상태

체감 속도는 통신·파싱·디코딩·상태에서 결정된다.

  • SSE
  • WebSocket
  • 스트리밍
  • 토큰
  • 버퍼링 파싱
  • TextDecoder
BIG IDEA

LLM 응답은 토큰 단위로 도착하므로 UI도 그 단위로 받아 즉시 그려야 한다. SSE는 토큰을 HTTP 위 단방향으로 흘려보내는 규격이고, 조각난 JSON과 잘린 한글은 버퍼링 파싱과 TextDecoder 스트리밍 모드로 복원한다. React 상태는 함수형 업데이트로 누적해야 토큰을 잃지 않는다.

스트리밍은 왜 체감 속도를 바꾸는가

전체 완료 시간은 같다. 달라지는 것은 첫 글자가 보이는 시점이다.

일반 응답 방식은 LLM이 답변 전체를 만들 때까지 기다린다. 그동안 화면에는 아무 반응이 없다. 답변이 길수록 사용자는 빈 화면을 오래 본다.

스트리밍 방식은 토큰이 생성되는 즉시 화면으로 보낸다. 첫 글자가 빨리 도착하므로 같은 시간을 기다려도 체감 대기 시간이 짧다. 그래서 UX 측면에서 스트리밍은 필수다.

토큰 (Token)
모델이 한 번에 만들어 내는 텍스트 조각이다. 스트리밍에서는 이 조각이 전송 단위가 된다.
체감 대기 시간
첫 글자가 나타나기까지 걸리는 시간이다. 전체 응답 완료 시간과 구분해야 한다.
일반 응답 (Non-streaming)
전체 답변을 완성한 뒤 한 번에 받는 방식이다. 완성 전까지 화면이 비어 있다.
FIG. 01 일반 응답 vs 스트리밍 응답

일반 응답

  • 전체 완성까지 화면 정지
  • 답변이 길수록 대기가 길다
  • 첫 글자가 늦게 보인다

스트리밍

  • 토큰 생성 즉시 표시
  • 첫 글자가 빨리 도착한다
  • 체감 속도가 높다

총 소요 시간은 비슷하고, 첫 글자까지의 시간만 달라진다.

SSE는 왜 LLM 응답에 맞는가

LLM 응답은 서버가 일방적으로 보내는 데이터라 단방향 SSE로 충분하다.

SSE는 HTTP를 그대로 쓰면서 서버에서 클라이언트로만 데이터를 보낸다. 구현이 단순하고 알림이나 LLM 응답 전달에 잘 맞는다.

WebSocket은 별도 프로토콜로 양방향 통신을 한다. 사용자끼리 대화하는 채팅방이나 게임처럼 양쪽이 계속 주고받아야 할 때 필요하다.

SSE (Server-Sent Events)
HTTP 기반 단방향 스트림 규격이다. 연결을 열어 두고 서버가 이벤트를 계속 보낸다.
WebSocket
양방향 통신을 위한 별도 프로토콜이다. 클라이언트도 원할 때 서버로 데이터를 보낼 수 있다.
SSE 이벤트 형식
data: 줄에 내용을 담고 빈 줄로 이벤트를 구분한다. 스트림이 끝날 때는 [DONE] 같은 종료 신호를 보낸다.
SSE와 WebSocket 비교
항목SSEWebSocket
방향서버 → 클라이언트 단방향양방향
프로토콜HTTP별도 프로토콜
구현 난이도비교적 간단상대적으로 복잡
적합한 사례알림, LLM 응답 전달채팅방, 게임

쪼개진 조각은 어디서 깨지는가

네트워크 조각은 JSON 경계도 글자 경계도 지켜주지 않는다. 두 곳을 따로 막아야 한다.

스트림은 한 번에 한 덩어리(Chunk)씩 도착한다. 그 덩어리는 JSON이 완성된 상태일 수도, 중간에서 잘린 상태일 수도 있다. 잘린 JSON을 그대로 파싱하면 오류가 난다. 버퍼에 모아 두고 줄이 완성될 때만 파싱하는데 이것이 버퍼링 파싱이다.

한글은 한 글자가 여러 바이트로 이루어진다. 바이트 중간에서 조각이 끊기면 글자가 깨진다. TextDecoder의 스트리밍 모드는 남은 바이트를 보관했다가 다음 조각과 합쳐 디코딩한다.

Chunk
네트워크로 한 번에 도착하는 바이트 덩어리다. JSON이나 문자의 경계와 일치하지 않는다.
버퍼링 파싱 (Buffering Parsing)
들어온 조각을 버퍼에 누적하고, 완성된 줄만 JSON으로 파싱하는 기법이다.
멀티바이트 (Multi-byte)
한 글자가 여러 바이트로 표현되는 문자다. 한글이 대표적이며 조각이 나뉘면 깨진다.
TextDecoder 스트리밍 모드
끊긴 바이트를 내부에 보관하고 다음 조각과 이어 붙여 디코딩한다. 한글 처리에는 반드시 필요하다.
FIG. 02 조각이 화면이 되기까지
01 스트림 읽기 fetch + getReader()
02 TextDecoder 잘린 한글 이어 붙이기
03 버퍼 누적 완성된 줄만 분리
04 JSON.parse 토큰 추출
05 상태 누적 setMessages(prev)

디코딩과 파싱을 나눠야 JSON 오류와 한글 깨짐을 각각 막을 수 있다.

서버는 전달만, 화면은 누적으로

Edge Function은 가공하지 않고 흘려보내고, React는 이전 상태에 이어 붙인다.

서버가 데이터를 가공하면 그만큼 지연이 생긴다. Edge Function은 받은 스트림을 그대로 브라우저로 넘기는 릴레이 역할만 한다. 서버 자원 부담도 줄어든다. 브라우저는 fetch로 응답을 받고 getReader()로 스트림을 직접 읽어, 읽은 조각을 파싱해 화면 상태에 붙인다.

토큰은 초당 수십 번 들어온다. 이전 상태를 직접 읽어 더하면 중간 값이 덮여 사라질 수 있다. setMessages(prev => ...)처럼 이전 값을 인자로 받아야 안전하다.

Edge Function (Relay)
데이터를 가공하지 않고 그대로 전달하는 역할이다. 지연과 서버 부담을 줄인다.
fetch + getReader()
fetch로 받은 응답 본문을 스트림으로 읽는 클라이언트 구현 방식이다.
함수형 업데이트 (prev)
setMessages(prev => ...) 형태로 이전 상태를 인자로 받는 업데이트다. 연속 갱신에서 토큰을 잃지 않는다.

무엇을 언제 저장하고 얼마나 보낼까

저장은 두 번, 사용자 메시지는 즉시, 어시스턴트 메시지는 완료 후 한 번이다.

사용자 메시지는 보내는 즉시 저장한다. 어시스턴트 메시지는 스트림이 완전히 끝난 뒤 최종 완성본만 저장한다. 토큰마다 저장하면 낭비다.

대화가 길어지면 프롬프트 토큰이 커진다. 비용이 늘고 응답 품질도 떨어진다. 이것을 컨텍스트 윈도우 관리라고 한다. 최근 메시지만 보내기, 이전 대화를 요약해 넣기, RAG로 필요한 부분만 가져오기 같은 전략이 있다.

저장 시점
사용자 메시지는 전송 즉시, 어시스턴트 메시지는 스트림 종료 후 최종본만 저장한다.
컨텍스트 윈도우 (Context Window)
모델이 한 번에 처리할 수 있는 최대 입력+출력 토큰 범위다. 대화가 길어지면 비용과 성능 문제가 생긴다.
메시지별 저장 전략
대상저장 시점이유
사용자 메시지전송 즉시요청 자체를 먼저 남긴다
어시스턴트 메시지스트림 종료 후토큰마다 저장하면 중복·낭비

시험 직전 체크

EXAM POINTS
  1. 스트리밍은 전체 완료 시간이 아니라 첫 글자까지의 시간을 줄여 체감 속도를 높인다.
  2. SSE는 HTTP 기반 단방향, WebSocket은 별도 프로토콜 양방향이다.
  3. 잘린 JSON은 버퍼링 파싱으로 완성된 줄만 처리한다.
  4. 한글 멀티바이트는 TextDecoder 스트리밍 모드로 이어 붙여야 깨지지 않는다.
  5. Edge Function은 가공 없이 relay만 하고, React 상태는 prev로 누적한다.
  6. 사용자 메시지는 즉시, 어시스턴트 메시지는 완료 후 최종본만 저장한다.

헷갈리는 것만

COMMON TRAPS
스트리밍을 쓰면 답변 생성이 빨라진다?

아니다. 전체 완료 시간은 같고 첫 글자가 빨리 보일 뿐이다.

한글이 깨질 때 조각을 잘라 넘기면 해결되나?

아니다. 잘린 바이트는 복구되지 않는다. TextDecoder 스트리밍 모드로 보관해야 한다.

setMessages([...messages, token])로 이어 붙이면?

이전 상태가 오래된 값일 수 있다. prev를 받는 함수형 업데이트를 써야 한다.

LLM 채팅에는 WebSocket이 필수인가?

아니다. 서버에서 클라이언트로만 보내면 되므로 SSE로 충분하다.

KEYWORDS

  • SSE
  • WebSocket
  • 스트리밍
  • 토큰
  • 버퍼링 파싱
  • TextDecoder
  • 멀티바이트
  • Edge Function
  • 함수형 업데이트
  • 컨텍스트 윈도우

연결해서 기억하기

  • 1주차에서 배운 토큰 개념이 여기서는 화면에 그리는 단위로 쓰인다.
  • 4주차에서는 컨텍스트 윈도우를 채울 문서를 고르는 검색 문제로 넘어간다.
  • 여기서 다룬 지연·비용 문제는 뒤에서 검색으로 컨텍스트를 줄이는 방식으로 이어진다.