pulse.huny.dev

HunyDev
Designing a Robust Low-Latency Streaming API for Live TTS

Designing a Robust Low-Latency Streaming API for Live TTS

“TTS/오디오/라이브 합성” 같은 스트리밍 API를 **빠른 TTFB(Time‑to‑First‑Byte)**와 중단‑복구(Resume) 중심으로 설계하는, 클라우드 벤더 무관(Cloudflare/AWS 모두 적용) 블루프린트

Hun Jang
Hun Jang Nov 28, 2025

저지연 TTS 스트리밍 설계 청사진

아래는 “TTS/오디오/라이브 합성” 같은 스트리밍 API를 **빠른 TTFB(Time‑to‑First‑Byte)**와 중단‑복구(Resume) 중심으로 설계하는, 클라우드 벤더 무관(Cloudflare/AWS 모두 적용) 블루프린트다. 배경 → 핵심 규격 → 헤더/청크/백프레셔 → 재시도/Resume 규칙 → Cloudflare Workers & AWS API GW+Lambda 예제로 바로 붙여 쓸 수 있게 정리했다.

1) 배경 요약

  • 문제: 오디오/텍스트 스트리밍에서 CDN이 응답을 붙잡거나(“오리진 완성 대기”), 과한 캐시가 “라이브 합성”을 정지시키는 일이 잦다. 재생중 네트워크 끊김 시 이어받기도 필요.
  • 목표:
    • 클릭 후 1–4KB의 초기 프라임 청크로 플레이어를 즉시 기동(TTFB 극대화)
    • 이후 16–64KB 고정/준고정 청크로 안정적인 재생
    • Range/offset 재개명시적 캐시 제어로 CDN의 개입을 최소화
    • 역압(backpressure) 대응으로 서버/클라이언트 모두 버퍼 폭주 방지

2) 전송 전략 한 장 요약

  • 전송 프로토콜: HTTP/1.1(광범위 호환) 또는 HTTP/2(헤더 효율). H3/QUIC도 OK.
  • 콘텐츠 타입: audio/mpeg, audio/ogg, application/octet-stream 등 서비스에 맞게.
  • 초기 프라임 청크: 1–4KB (TTFB를 위한 최소 페이로드)
  • 지속 청크 크기: 16–64KB (네트워크/플레이어에 따라 32KB가 무난)
  • 전송 인코딩: Transfer-Encoding: chunked(HTTP/1.1) 또는 HTTP/2 데이터 프레임
  • 재개(Resume): Accept-Ranges: bytes + Range: bytes=<start>- 지원, 응답은 206 Partial Content
  • 진행 메타: X-Start-Offset, X-End-Offset, X-Stream-Id(선택) 등 커스텀 메타로 디버깅/재시작 단서 제공
  • 캐시 제어(라이브 합성):
    • Cache-Control: no-store, max-age=0, must-revalidate
    • Pragma: no-cache(구형 우회)
    • 주의: no-cache만 쓰면 “재검증 후 캐시”라서 CDN이 멈춰 세울 수 있음 → 반드시 no-store
  • 커넥션 유지: Connection: keep-alive
  • CORS(웹 플레이어):
    • Access-Control-Allow-Origin: *(또는 특정 도메인)
    • Access-Control-Expose-Headers: Accept-Ranges, Content-Range, X-Start-Offset, X-End-Offset

3) 표준 헤더 세트 (붙여넣기용)

요청 (재개 지원 클라이언트):

초회 응답(200 OK, 스트림 시작):

재개 응답(206 Partial Content):

합성 길이를 미리 모르면 Content-Length/total을 생략하고 <unknown> 취급. 완결 시 마지막 청크로 EOF.

4) 청크 사이징 & 백프레셔

  • 1–4KB 프라임: 플레이어가 “재생 가능” 상태로 전환하는 데 필요한 최소 바이트를 즉시 전송.
  • 이후 16–64KB: RTT 및 대역폭 고려. 일반 웹/모바일은 32KB 전후가 무난.
  • 백프레셔(서버):
    • Node/Workers: controller.desiredSize 또는 stream.write()의 backpressure 신호를 확인해 일시 await.
    • Lambda: SDK/런타임의 플러시 타이밍(버퍼 임계) 관찰, 생성기/AsyncIterable로 천천히 밀어내기.
  • 백프레셔(클라이언트): Fetch ReadableStream을 소비하며 플레이어 버퍼 상태에 맞춰 pause/resume.

5) 재시도/Resume 규칙 (클라이언트 지침)

  • 재시도 지수백오프: 200/206 수신 실패 시 100ms → 250ms → 500ms → 1s …
  • 오프셋 계산: 플레이어/디코더가 “소비 완료한 바이트”를 주기적으로 기록하고, 재요청 시 Range: bytes=<lastAck>-.
  • 서버 보정: 서버는 내부 청크 경계와 상관없이 요청 offset부터 인코딩/파일을 재개. 불일치 시 가장 가까운 안전 경계로 스냅(예: 프레임 경계) 후 X-Start-Offset로 고지.
  • 중복 안전성: 동일 오프셋 중복 수신은 플레이어가 무해하게 드롭하도록 설계.

6) Cloudflare Workers 예시 (바로 사용 가능)

Workers 팁

  • Cloudflare에서 오디오 스트림 캐시 금지: no-store 필수.
  • R2 원본을 끼우면 Range 지원을 직접 흉내내거나 R2 range 옵션 사용.
  • Durable Objects에 합성 상태/오프셋 메타를 둬 재개 정확도↑.

7) AWS API Gateway + Lambda (Node.js 런타임) 예시

전제: REST API + Lambda 프록시 통합, 대용량 스트리밍은 Lambda Response Streaming(+) 또는 ALB/Lambda 조합 고려.

AWS 팁

  • API Gateway(REST) 기본 캐시 금지 + CloudFront 앞단이라면 오리진 응답에 no-store.
  • Lambda Response Streaming(또는 ALB 프록시)으로 TTFB 개선. 일반 프록시(버퍼링) 경로는 피함.
  • SnapStart/PC 사용 시 초기 TTFB 좋지만, 청크 플러시 타이밍은 네트워크 경로에 따라 달라짐 → 작은 PRIME 먼저 쓰고 바로 flush.

8) 플레이어(웹) 구현 스니펫 (재개 포함)

9) 장애/재시도 시맨틱

  • 클라이언트: networkerror/stall > 1.5s → 현재 offset으로 재접속. 최대 N회 지수 백오프.
  • 서버: 요청 offset이 내부 체크포인트보다 과거면 가장 가까운 체크포인트에서 시작하고 X-Start-Offset로 공지.
  • 관찰성: X-Stream-Id, X-Start-Offset, 주기적 X-End-Offset 로깅 → CloudWatch/Workers Log로 합성 지연·중단 포인트 추적.

10) 체크리스트

초기 1–4KB 프라임 청크 즉시 플러시

지속 청크 16–64KB

Accept-Ranges: bytes + Range 처리 + 206

Cache-Control: no-store (CDN 정지 방지)

CORS + Access-Control-Expose-Headers

백프레셔 신호에 따라 await/슬라이스

재시도 시맨틱(오프셋 저장/복구)

로그에 stream_id, start/end_offset, latency

You might also like

BlogPro logo
Made with BlogPro