TL;DR

  • SSE는 HTTP 응답 스트림 위에서 서버가 클라이언트로 이벤트를 보내기 위한 이벤트 스트림 프레이밍 규격이다.
  • NDJSON은 JSON 값을 LF(\n) 또는 CRLF(\r\n) 줄 구분자로 하나씩 나열하는 줄 단위 데이터 포맷이다.
  • 주요 상용 LLM API에서는 보통 SSE 이벤트의 data: 필드 안에 JSON payload를 담아 전송한다.
  • 구조는 NDJSON과 비슷해 보이지만, data: 필드와 SSE 이벤트 프레이밍을 사용하는 순간 전체 스트림은 NDJSON이 아니라 SSE다.

SSE vs NDJSON 비교

두 기술은 모두 줄 단위로 읽히는 스트리밍 형태를 만들 수 있지만, 작동하는 레이어와 목적이 다르다. SSE는 HTTP 기반 이벤트 스트림의 프레이밍 규칙이고, NDJSON은 전송 방식과 독립적인 데이터 직렬화 포맷이다.

1. 기술적 차이 비교

항목SSE (Server-Sent Events)NDJSON (Newline Delimited JSON)
역할HTTP 이벤트 스트림 프레이밍 규격줄 단위 데이터 포맷
미디어 타입text/event-streamapplication/x-ndjson
핵심 목적서버에서 클라이언트로 이벤트를 순차 전달대용량 데이터를 줄 단위 JSON 값으로 직렬화
기본 구조data:, event:, id: 등 필드 줄 + 빈 줄로 이벤트 경계 표시순수 JSON 값 + LF(\n) 또는 CRLF(\r\n)
연결성지속적인 HTTP 응답 스트림을 전제로 함파일, HTTP 응답 스트림, TCP 등 전송 방식과 독립적
기능이벤트 타입, 이벤트 ID, 재연결 힌트, EventSource 기반 자동 재연결줄 단위 스트리밍 파싱 용이성

2. 순수 NDJSON 스트리밍 vs SSE 스트리밍

SSE 프레이밍을 쓰지 않고도 HTTP 응답 스트림으로 NDJSON 레코드를 한 줄씩 전송할 수 있다. 이 경우 스트림의 의미는 SSE 이벤트가 아니라 “줄마다 하나의 JSON 값”이라는 NDJSON 규칙에서 나온다.

기능SSE 스트리밍순수 NDJSON 스트리밍
포맷data: {…}\n\n{“…”: “…”}\n
브라우저 APIEventSource (표준 지원)fetch() + ReadableStream 직접 구현
자동 재연결지원 (브라우저 내장 기능)직접 구현 필요
이벤트 구분event: 필드로 명시적 구분 가능JSON 내부 필드로 직접 정의해야 함
전송 오버헤드필드명(data: ) 등 소폭의 오버헤드오버헤드 거의 없음 (순수 데이터만 전송)

실제 전송 예시

  • SSE 방식:
    data: {"delta": "안"}
     
    data: {"delta": "녕"}
  • 순수 NDJSON 방식:
    {"delta": "안"}
    {"delta": "녕"}

3. LLM Streaming에서의 SSE + JSON payload

주요 상용 LLM API(OpenAI, Anthropic 등)는 보통 SSE 이벤트의 data: 필드 안에 JSON payload를 싣는다. 이는 순수 NDJSON이 아니라 text/event-stream으로 해석되는 SSE 스트림이다.

  • 프레이밍 (SSE): HTTP 연결을 열어두고 data:, event: 같은 필드 줄과 빈 줄로 이벤트를 구분한다.
  • 내용물 (JSON payload): data: 필드 내부에 직렬화된 JSON payload를 담는다.

실제 전송 예시

event: message
data: {"id": "123", "choices": [{"delta": {"content": "Hello"}}]}
 
event: message
data: {"id": "123", "choices": [{"delta": {"content": " World"}}]}

용어 정리

  • SSE는 payload 형식을 강제하지 않는다. data: 안에는 일반 텍스트, JSON 문자열, [DONE] 같은 관습적 종료 표식이 들어갈 수 있다.
  • NDJSON은 transport를 강제하지 않는다. 파일로 저장할 수도 있고, HTTP 응답 스트림으로 보낼 수도 있다. 중요한 조건은 각 줄이 독립적인 JSON 값이고, 줄 구분자는 LF(\n) 또는 CRLF(\r\n)여야 한다는 점이다.
  • data: {...}\n\n는 NDJSON이 아니다. JSON처럼 보이는 payload가 있어도 data: 필드와 빈 줄 이벤트 경계를 쓰면 전체 스트림은 SSE 프레이밍을 따른다.

Connections