05 — Low Latency Streaming (LL-HLS, LL-DASH, vs WebRTC)
이 문서가 답하는 질문: 라이브 지연을 30초 → 3초 → 0.5초로 줄이려면 무엇이 바뀌어야 하나? 표준: HLS v9 (LL-HLS, 2020), DASH-IF Low-Latency Live (LL-DASH). 핵심 한 줄: 지연을 줄이는 길은 셋이다 — segment를 짧게 (전통), segment를 part로 쪼개고 push (LL-HLS/DASH), 또는 세그먼트 자체를 버린다 (WebRTC).
한 줄 답 (Pyramid Top)
전통 HLS의 라이브 지연(20
30초)은 *세그먼트 길이 × 23*에서 온다. 줄이는 방법은: (1) 세그먼트를 더 짧게 (1초) — 한계 68초 지연. (2) 세그먼트를 part로 쪼개고 chunked push (LL-HLS, LL-DASH) — 한계 25초 지연. (3) 세그먼트를 포기하고 RTP/SRTP를 쓴다 (WebRTC) — 0.1~0.5초.
Why — 왜 지연이 생기는가 (지연 예산 분해)
전통 HLS의 30초 지연을 어디서 잃는지 분해하면:
| 단계 | 지연 (초) | 이유 |
|---|---|---|
| 카메라 → 인코더 | 0.1~0.5 | 캡처 + GOP 1개분 인코딩 |
| 인코더 → packager | 0.1 | 한 GOP 인코딩 후 컨테이너 작성 |
| Segment 완성 대기 | 6 | 6초 segment를 완성해야 발행 가능 |
| Origin → CDN 전파 | 1~3 | 첫 fetch 전엔 캐시 없음 |
| Player 매니페스트 폴링 | 3~6 | TARGETDURATION/2 주기 |
| Player 버퍼 (rebuffer 회피) | 6~12 | 보통 2 segment 분량 |
| 디코드 + 디스플레이 | 0.1~0.3 | 거의 무시 |
| 합계 | ~30s |
굵은 항목 셋(segment 완성 6초 + 폴링 36초 + 버퍼 612초)이 절반 이상. LL의 핵심은 이 셋을 다 줄이는 것.
How — LL-HLS의 4가지 변경점 (Apple, 2019/2020 도입)
1) Partial Segment (#EXT-X-PART)
전통 HLS는 6초 segment를 완성해야 매니페스트에 추가했다. LL-HLS는 segment를 작은 part(보통 0.2~1초)로 쪼개 생성 즉시 노출한다.
#EXTM3U
#EXT-X-VERSION:9
#EXT-X-TARGETDURATION:6
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.5,HOLD-BACK=12
#EXT-X-MEDIA-SEQUENCE:1240
#EXTINF:6.0,
seg_01240.m4s
#EXTINF:6.0,
seg_01241.m4s
#EXTINF:6.0,
seg_01242.m4s
# 진행 중인 segment의 part들:
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.1.m4s"
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.2.m4s"
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.3.m4s"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg_01243.4.m4s"| 태그 | 의미 |
|---|---|
#EXT-X-VERSION:9 | LL-HLS는 v9+ |
#EXT-X-PART-INF:PART-TARGET | part의 목표 길이 (초) |
#EXT-X-PART:DURATION=...,URI="...",INDEPENDENT=YES | 한 part. INDEPENDENT=YES는 IDR로 시작 (시크 가능 지점) |
#EXT-X-PRELOAD-HINT | 아직 publishing 안 된 다음 part의 URI를 미리 안내 → 플레이어가 blocking GET 가능 |
#EXT-X-SERVER-CONTROL | LL 동작 파라미터 |
CAN-BLOCK-RELOAD=YES | 서버가 blocking GET을 지원함을 광고 |
PART-HOLD-BACK=1.5 | 라이브 엣지로부터 1.5초 뒤를 권장 시청 위치 |
HOLD-BACK=12 | 비-LL 클라이언트의 권장 위치 (= 12초 뒤) |
→ 한 segment(6초)가 part 18개로 쪼개진다 (0.33초 × 18). 18배 더 자주 매니페스트가 갱신된다.
2) Blocking Playlist Reload
전통: 매니페스트를 polling. 새 segment가 없으면 빈손으로 돌아옴.
LL-HLS: 플레이어가 _HLS_msn=N&_HLS_part=K라는 기대값을 쿼리에 박아 GET. 서버는 그 시점이 올 때까지 응답을 미룬다.
GET /media.m3u8?_HLS_msn=1243&_HLS_part=5
← 서버: msn=1243, part=5가 publishing 될 때까지 hold (long polling)
← publishing 즉시 응답 200→ 폴링 오버헤드 0. 새 part 등장 즉시 클라이언트에 도달.
3) Delta Update
매니페스트가 매 part마다 갱신되면 텍스트가 비대해진다. LL-HLS는 delta만 보내는 옵션.
GET /media.m3u8?_HLS_skip=YES
→ 응답에 #EXT-X-SKIP:SKIPPED-SEGMENTS=N (앞부분 N개 segment 생략)#EXTM3U
#EXT-X-VERSION:9
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1240
#EXT-X-SKIP:SKIPPED-SEGMENTS=240 ← 1000~1239의 240개 생략
#EXTINF:6.0,
seg_01240.m4s
...플레이어는 이전 매니페스트와 합쳐 전체 상태를 재구성. 텍스트 전송량 ↓.
4) Rendition Reports
플레이어가 ABR 전환 시 다른 variant의 매니페스트도 polling해야 했다. LL-HLS는 같은 매니페스트 안에 다른 variant의 최신 msn/part 정보를 동봉.
#EXT-X-RENDITION-REPORT:URI="../720p/index.m3u8",LAST-MSN=1243,LAST-PART=5
#EXT-X-RENDITION-REPORT:URI="../480p/index.m3u8",LAST-MSN=1243,LAST-PART=5→ ABR 전환 시 상대 매니페스트의 위치를 이미 알고 blocking GET → 더 빠른 전환.
How — LL-DASH (chunked CMAF)
DASH는 CMAF chunked transfer를 표준화했다 — 별도 매니페스트 변경 없이.
MPD에 LL 메타 추가
<MPD type="dynamic"
availabilityStartTime="2026-05-10T10:00:00Z"
minimumUpdatePeriod="PT2S"
suggestedPresentationDelay="PT2S"
...>
<ServiceDescription>
<Latency target="2000" min="1000" max="5000"/>
<PlaybackRate min="0.95" max="1.05"/>
</ServiceDescription>
...
<SegmentTemplate
timescale="90000"
duration="540540" {/* 6초 segment */}
initialization="$RepresentationID$/init.mp4"
media="$RepresentationID$/seg_$Number%05d$.m4s"
availabilityTimeOffset="5.7"
availabilityTimeComplete="false"
startNumber="1"/>
...
</MPD>| 속성 | 의미 |
|---|---|
availabilityTimeComplete="false" | segment가 완성 전에 부분을 받을 수 있음을 광고 |
availabilityTimeOffset="5.7" | segment 시작 시각 0.3초 후부터 부분이 나오기 시작 (6 - 5.7 = 0.3) |
Latency target="2000" | 권장 지연 2초 |
PlaybackRate min/max | latency 따라잡기용 재생 속도 미세 조정 (0.95~1.05x) |
인코더의 chunked HTTP transfer
PUT /seg_00501.m4s HTTP/1.1
Transfer-Encoding: chunked
200
[moof][mdat] ← chunk 1 (200ms)
200
[moof][mdat] ← chunk 2 (200ms)
...
0 ← endCDN은 chunk를 받는 즉시 forward (HTTP/1.1 chunked passthrough). 플레이어는 한 segment의 일부를 받아 디코딩 시작.
→ HLS의 partial segment와 기능은 동등. 차이: HLS는 part마다 별도 URL, DASH는 같은 URL에 chunked 응답.
What — 지연 비교 표
| 방식 | Segment 길이 | Glass-to-Glass | 채택 |
|---|---|---|---|
| 전통 HLS (TS, 10s) | 10s | 25~45s | 전통 OTT |
| HLS (fMP4, 6s) | 6s | 18~30s | 일반 OTT (Netflix VOD 라이브) |
| HLS short segment (2s) | 2s | 6~10s | 빠른 라이브 |
| LL-HLS | 6s segment + 0.33s part | 2~5s | YouTube Live, Twitch (실험) |
| LL-DASH | 6s segment + chunked | 2~5s | DAZN, BBC iPlayer |
| WebRTC | 패킷 단위 | 0.1~0.5s | 화상회의, Twitch interactive |
| SRT/RIST (전송 only) | 패킷 단위 | 0.5~2s | 방송 contribution |
What — WebRTC와 LL-HLS는 어떤 분기인가
같은 “라이브” 도메인이지만 풀려는 문제가 다르다.
| 축 | LL-HLS / LL-DASH | WebRTC |
|---|---|---|
| 모델 | HTTP pull (CDN-friendly) | UDP push (peer-to-peer 또는 SFU) |
| 확장성 | 1억 명 OK (CDN edge) | SFU별 ~수만 명 |
| 지연 | 2~5s | 0.1~0.5s |
| 방화벽 | 80/443 통과 | UDP 차단되면 fallback (TCP relay) |
| DRM | 정상 (CENC/CBCS) | 별도 E2E (SFRAME 등 실험) |
| 왕복 | 단방향 | 양방향 (인터랙티브) |
| 품질 제어 | ABR (variant 사전 인코딩) | simulcast/SVC + 서버 선택 |
| 사용처 | 스포츠/뉴스 라이브 | 화상회의, 강사-수강생, 라이브 인터랙션 |
규칙: 대규모 일방향 라이브 → LL-HLS/DASH. 인터랙션 → WebRTC. 둘 사이의 절충은 거의 없다 (양립 불가).
What — Glass-to-Glass 지연 측정
운영에서 LL을 검증하려면 실측이 필요하다. 일반적인 두 방법:
NTP 시각 합성 (권장)
화면에 NTP 시계를 오버레이로 합성하고, 시청 측 클라이언트도 NTP로 시각을 알면 영상에 보이는 시각 vs 현재 시각의 차이가 latency.
EXT-X-PROGRAM-DATE-TIME / availabilityStartTime
매니페스트 자체에 wall-clock이 박혀 있어 대략적 측정 가능.
#EXT-X-PROGRAM-DATE-TIME:2026-05-10T10:30:15.123Z
#EXTINF:6.0,
seg_01243.m4s플레이어가 이 segment를 디스플레이하는 wall-clock과 PDT를 비교.
What-if — 잘못 쓰면 어떻게 깨지는가
| 증상 | 원인 | 처방 |
|---|---|---|
| LL인데 지연이 그대로 | CDN이 chunked transfer 미지원 | CloudFront/Akamai LL 호환 origin 설정 |
| Player가 LL 매니페스트 거부 | 플레이어 버전이 v9 미만 | hls.js 1.x+, Shaka 4.x+ 사용 |
INDEPENDENT=YES part가 없음 | 인코더가 part 경계에 IDR 미배치 | g=part_size 또는 force_key_frames per part |
| ABR이 part 단위로 폭주 진동 | switch 빈도가 너무 잦음 | switchInterval 조정 (≥ 4초) |
| Blocking GET가 timeout | 네트워크 NAT의 idle timeout | 매니페스트 응답을 keep-alive로 |
| 한 사용자만 6초 지연 | 그 클라이언트가 LL 미지원 | HOLD-BACK 값으로 graceful degrade |
| 라이브 시작 후 첫 1분 unstable | 서버가 part-target 못 맞춤 | 인코더 GOP 설정 검증 |
Insight — 흥미로운 이야기
“LL-HLS는 사실 기존 CDN을 안 바꾸려고 만든 것”
WebRTC가 이미 0.5초 지연을 푼 시점이었지만, 기존 HLS 인프라(Akamai 같은 거대 CDN)를 그대로 쓸 수 있어야 했다. LL-HLS의 blocking GET은 사실 long-polling이고, part는 짧은 segment의 변형이다 — 모두 HTTP 위에 동작 가능. 인프라 변경 비용을 최소화한 것이 핵심.
“Apple과 LHLS의 분쟁”
2018~2019년 Periscope/Twitch가 unofficial LHLS(Low-latency HLS)를 먼저 제안했다 — chunked transfer 기반. Apple이 이를 받아들이지 않고 별도 표준(LL-HLS, partial segment)을 제안. 한동안 두 표준이 경쟁했고, 결국 Apple의 표준이 채택되었다. 표준의 정치학.
“PlaybackRate ±5%의 비밀”
LL 라이브에서 클라이언트가 라이브 엣지로부터 너무 멀어지면, 재생 속도를 0.95~1.05배로 미세 조정해 다시 따라잡는다. 시청자는 눈치 못 챈다 — 사람의 청지각은 ±5% pitch 변화에 둔감. 이 트릭이 Latency target을 지키기 위한 마지막 무기다.
“WebRTC는 사실 일방향 라이브에 잘 안 맞다”
WebRTC는 peer-to-peer가 본질이다. 1만 명 라이브를 하려면 SFU(Selective Forwarding Unit)가 필요한데, SFU는 서버 클러스터고 비싸다. 같은 1만 명을 LL-HLS로 하면 CDN edge가 공짜로 분산해준다. 그래서 대규모 라이브는 LL-HLS, 소규모 인터랙션은 WebRTC로 자연스럽게 나뉜다.
한 단락 요약 + Mermaid
Low-latency 스트리밍은 segment를 part로 쪼개 push하면서 매니페스트 폴링을 long-polling으로 바꾼 결과다. LL-HLS의
#EXT-X-PART와 LL-DASH의 chunked transfer는 같은 메커니즘의 다른 표현. 0.5초 미만이 필요하면 segment 자체를 포기하고 WebRTC로 가야 한다. 둘 사이에는 절충이 거의 없다.
→ 다음 파일 06-segmentation-and-manifest.md: segment 길이 자체의 trade-off — 2/4/6/10초의 trade off.