라이브 스트리밍 송출 파이프라인 — RTMP 수신부터 ABR·m3u8 실시간 갱신, 그리고 6초 지연의 정체
유튜브 라이브는 왜 항상 6초, 길게는 30초씩 늦게 보일까요? 흔히 느린 서버 탓이라 생각하지만, 진짜 원인은 영상을 작은 파일로 잘라 HTTP로 주기적으로 가져가는 HLS의 구조 자체에 있습니다. 이 글은 OBS가 RTMP를 쏘는 순간부터 시청자 화면에 픽셀이 그려질 때까지의 4단 파이프라인을 따라가며, 세그먼트 길이와 플레이어 버퍼가 어떻게 지연을 쌓는지, AWS MediaLive와 MediaConvert는 왜 역할이 다른지, 그리고 같은 라이브라도 양방향 대화엔 왜 WebRTC가 답인지를 풀어냅니다.
목차(37개 항목)
- 0. 핵심 명제 — 지연은 버그가 아니라 아키텍처의 비용이다
- 1. 큰 그림 — 4단 파이프라인
2. 1단계 — RTMP 수신(Ingest)
- 4. ★ AWS MediaLive vs MediaConvert — 역할이 완전히 다르다
5. 3단계 — HLS 패키징(세그먼트 + m3u8 실시간 갱신)
6. 4단계 — CDN 배달 + 플레이어
7. ★ 6~10초 지연의 정체 — 그리고 정정
8. HLS vs WebRTC — 같은 "라이브"의 정반대 설계
- 9. 실무 의사결정 — 어떤 파이프라인을 고를까
- 10. 한 장 요약 — 암기 카드
- 11. 한 줄 결론
- 관련 글
- 참고 자료
"유튜브 라이브는 왜 6~10초씩 늦게 보이나요? 우리도 그 정도면 충분한데, 서버에서 도대체 무슨 일이 일어나는 건가요?". 라이브 커머스나 스트리밍 PoC 자리에서 자주 나오는 질문입니다. 답은 "세그먼트 단위로 잘라 파일로 배달하기 때문"인데, 이 한 문장 뒤에는 RTMP 수신 → 트랜스코딩 → HLS 패키징 → CDN 배달이라는 4단 파이프라인과, 그 파이프라인의 구조 자체가 만들어내는 지연이 숨어 있습니다.
이 글은 #40(프로토콜 선택)과 #37/#38(GOP·ABR 정렬)에서 다룬 내용을 전제로, 그 사이에 비어 있던 "서버 파이프라인의 구체적 동작"과 "6초 지연의 정체" 에 집중합니다. OBS가 RTMP를 쏘는 순간부터 시청자 화면에 픽셀이 그려질 때까지, 각 단계가 무엇을 하고 어디서 시간이 쌓이는지 분해합니다.
0. 핵심 명제 — 지연은 버그가 아니라 아키텍처의 비용이다
HLS 라이브의 6
10초(실측 glass-to-glass는 흔히 1830초) 지연은 어딘가 느린 코드 때문이 아니다. "영상을 일정 길이의 파일(세그먼트)로 잘라서, 플레이어가 HTTP로 주기적으로 가져가는" 구조 자체가 만드는 비용이다. 세그먼트 길이 × 플레이어 버퍼(보통 3개 선다운로드)가 지연의 하한선을 정하고, 인코딩·패키징·네트워크가 그 위에 더 쌓인다. 이 구조 덕분에 CDN 캐싱·무한 확장·VOD 호환을 공짜로 얻는다. 지연과 확장성은 같은 동전의 양면이다.
흔한 오해와 정정:
| 오해 | 정확한 이해 |
|---|---|
| 지연 = 서버가 느려서 | ❌ 구조적 비용. 세그먼트 단위 배달 + 플레이어 버퍼가 하한선 |
| "6초 세그먼트면 6초 지연" | ❌ 세그먼트 길이는 지연의 한 항. 버퍼·인코딩·CDN 합산하면 보통 18~30초 |
| ABR은 화질을 자동으로 "올려"준다 | ⚠️ 플레이어가 대역폭 보고 렌디션을 선택할 뿐. 서버는 여러 화질을 미리 만들어 둠 |
| 단일 인코더가 모든 화질을 동시에 만든다 | ⚠️ 벤더마다 다름. "병렬 렌디션 생성 지원"이 정확한 표현 (아래 정정 참고) |
| MediaLive와 MediaConvert는 비슷한 거 | ❌ 라이브(실시간) vs 파일(VOD). 역할이 완전히 다름 |
| 라이브인데 seek(되감기)가 되는 건 신기한 일 | ⚠️ 과거 .ts 세그먼트는 이미 파일로 존재 → 그냥 과거 파일 요청일 뿐 |
1. 큰 그림 — 4단 파이프라인
라이브 스트리밍 서버의 일은 결국 네 단계로 정리됩니다. 송신자가 한 줄기 영상을 쏘면, 서버가 그걸 여러 화질로 복제하고, 작은 파일로 잘라서, 전 세계 CDN에 뿌립니다.
핵심은 "1줄기 입력 → N줄기 출력 → 작은 파일 더미 → HTTP 배달" 이라는 변환입니다. 각 화살표마다 시간이 쌓이고, 그 합이 곧 지연입니다. 단계별로 분해합니다.
2. 1단계 — RTMP 수신(Ingest)
왜 송신은 아직도 RTMP인가
OBS, 방송용 인코더, 모바일 송출 앱 대부분이 서버로 영상을 올릴 때 RTMP(Real-Time Messaging Protocol)를 씁니다. 2000년대 플래시 시절 프로토콜인데, 송출(contribution) 구간에서는 여전히 사실상 표준입니다.
| 항목 | 내용 |
|---|---|
| 전송 | TCP 기반 (포트 1935), 단일 지속 연결 |
| 비디오 코덱 | 주로 H.264 (송신 측 인코더가 이미 인코딩) |
| 오디오 코덱 | 주로 AAC |
| 방향 | 송신자 → 서버 (단방향 push) |
| 지연 기여 | 작음 (보통 수백 ms ~ 1초대, 인코딩 + 첫 버퍼) |
⚠️ 중요한 사실: 송신 측(OBS)이 이미 H.264로 인코딩해서 보냅니다. 서버는 "이미 압축된 1080p H.264 1줄기"를 받는 것이지, 날것의 픽셀(raw frame)을 받는 게 아닙니다. 이게 다음 단계에서 "왜 디코드부터 하는가"의 출발점입니다.
RTMP의 한계와 후속 프로토콜 — 왜 "송출은 RTMP, 시청은 HLS"가 됐나
이 분업은 처음부터 설계된 게 아니라, Flash의 흥망성쇠가 만든 역사적 타협입니다. 히스토리를 알아야 "왜 굳이 RTMP로 받아서 HLS로 바꾸는 번거로운 구조를 쓰는가"가 이해됩니다.
① Flash 전성기 — RTMP가 송출과 시청을 모두 담당하던 시절
RTMP(Real-Time Messaging Protocol)는 2002년 매크로미디어(이후 2005년 Adobe가 인수)가 Flash Player 전용 스트리밍 프로토콜로 만들었습니다. 당시엔 PC 브라우저 대부분에 Flash Player가 깔려 있었기 때문에, 송출도 RTMP, 시청도 RTMP(Flash 플레이어) 라는 단일 프로토콜 구조가 가능했습니다. 별도 변환이 필요 없었죠.
② Flash의 몰락 — 시청 레그가 끊기다
- 2010년: Steve Jobs가 "Thoughts on Flash" 공개서한으로 iOS에서 Flash를 끝까지 거부 (배터리·보안·성능 이유). iPhone/iPad에서 RTMP 시청이 원천 불가.
- HTML5
<video>태그의 부상 + Flash의 고질적 보안 취약점 → 브라우저들이 Flash를 단계적으로 차단. - 2017년 Adobe가 Flash Player 2020년 말 지원 종료를 공식 발표 → 2020년 12월 31일 EOL, 2021년 1월부터 주요 브라우저가 실행 차단.
즉 시청용 RTMP는 죽었습니다. 브라우저는 더 이상 RTMP를 직접 재생할 수 없게 됐습니다.
③ 업계의 대응 — 레그를 분리해서 살렸다
핵심 통찰: 송출 레그와 시청 레그를 분리해서, 죽은 건 갈아끼우고 멀쩡한 건 그대로 뒀습니다.
| 레그 | 처리 방식 | 이유 |
|---|---|---|
| 송출(ingest) | RTMP 유지 | OBS·XSplit·하드웨어 인코더 등 생태계가 이미 RTMP에 표준화됨. 굳이 안 바꿈 |
| 시청(playback) | HTTP 기반 세그먼트 스트리밍으로 교체 | Flash 없이 브라우저·모바일 네이티브로 재생 가능해야 함 |
시청 레그를 대체한 것이 HLS(Apple, 2009) 와 MPEG-DASH(ISO 표준, 2012) 입니다. 둘 다 영상을 짧은 조각으로 잘라 평범한 HTTP로 내려받는 방식이라, 특별한 플러그인 없이 동작합니다.
- Safari/iOS: HLS를 네이티브로 재생 (Apple이 만든 표준이니까).
- 그 외 브라우저(Chrome/Firefox 등): MSE(Media Source Extensions) 라는 HTML5 API 위에서 hls.js / dash.js 같은 JS 라이브러리가 세그먼트를 받아
<video>에 밀어 넣어 재생. Flash 없이 순수 웹 기술로 해결.
그 사이를 잇는 게 바로 서버입니다 — RTMP로 받아서(ingest) HLS/DASH로 다시 포장해(repackage) 내보내는(playback) 변환 브리지가 됐고, 이게 이 글 전체가 다루는 파이프라인입니다.
④ 송출 레그의 차세대 — RTMP도 영원하진 않다
RTMP는 시청에선 죽었지만 송출에선 여전히 1위입니다. 다만 TCP 기반이라 불안정망에서 약하고 저지연에 불리해, 송출 레그에도 후속 주자가 등장했습니다.
- SRT(Secure Reliable Transport): Haivision이 2017년 오픈소스화. UDP 기반 + 패킷 손실 복구(ARQ)로 불안정한 인터넷 회선에서 RTMP보다 강함.
- WebRTC ingest(WHIP): 초저지연 송출을 위한 표준화 진행 중.
그럼에도 호환성과 생태계 관성 때문에 RTMP가 여전히 가장 넓게 깔려 있습니다. (프로토콜 선택의 큰 그림은 #40, HLS 세그먼트 구조의 내부는 #15 참고)
3. 2단계 — 트랜스코딩(디코드 → ABR 멀티 인코딩)
여기가 서버에서 가장 무거운 단계입니다. CPU/GPU를 가장 많이 쓰고, 지연도 가장 많이 쌓일 수 있습니다.
왜 받은 걸 다시 인코딩하나 — 디코드 → 멀티 인코딩
송신자는 1080p H.264 1줄기만 보냅니다. 그런데 시청자는 제각각입니다 — 누구는 기가 와이파이, 누구는 지하철 LTE. 모두에게 1080p를 강요하면 약한 네트워크에서는 영상이 끊깁니다. 그래서 서버는 하나의 입력을 여러 화질로 다시 만듭니다(ABR Ladder).
순서가 핵심입니다:
- 디코드: 받은 H.264를 풀어서 raw YUV 프레임으로 되돌린다. (압축 상태로는 화질을 못 바꾸니까)
- 멀티 인코딩: 그 raw 프레임을 입력으로, 해상도/비트레이트별로 각각 다시 인코딩한다.
이걸 "decode-once, encode-many"라고 부릅니다. 디코딩은 한 번만, 인코딩은 렌디션 수만큼.
디코드 안에서 — RTMP 언래핑부터 raw 프레임까지
"디코드"는 한 단계가 아니라 언래핑 → ES 추출 → 디코딩의 3단입니다.
핵심은 ③에서 나온 게 비압축 raw 프레임이라는 점입니다. 압축 상태(H.264)로는 해상도·비트레이트를 못 바꾸니, 일단 생픽셀로 되돌려 놓고(그래서 메모리·CPU를 많이 먹습니다) 그걸 원본 삼아 다시 인코딩합니다. 이 raw 프레임들은 인코더로 들어가기 전 프레임 큐에 잠깐 쌓입니다.
인코드 안에서 — 스케일러 + 렌디션별 인코더 + 키프레임 정렬
raw 프레임 한 벌이 나오면, 렌디션마다 스케일 → 인코딩을 따로 겁니다.
- 스케일러: 생픽셀을 목표 해상도로 리샘플링(libswscale 또는 하드웨어 스케일러).
- 렌디션별 인코더: 각 (해상도+비트레이트)마다 독립 인코더 인스턴스. 레이트 컨트롤로 목표 비트레이트를 맞추고, GOP 길이를 정합니다.
- 키프레임 정렬: 모든 렌디션이 같은 시간 위치에 키프레임(IDR)을 찍도록 강제합니다. 그래야 세그먼트 경계가 렌디션마다 일치해, 플레이어가 화질을 바꿀 때 끊기지 않습니다. (이 정렬의 산수는 #37·#38)
각 인코더가 뱉는 출력은 다시 H.264 ES(NAL 줄기) 입니다. 이게 다음 단계(패키징)의 입력입니다.
오디오 경로 — AAC는 어디로 가나, 그리고 A/V 싱크
지금까지 비디오만 따라왔지만, 오디오(보통 AAC)도 같은 파이프라인을 나란히 흐릅니다.
- 갈래가 갈린다: 디먹스에서 비디오/오디오가 분리됩니다. 비디오는 N개 화질로 불어나지만, 오디오는 보통 단일 비트레이트 하나(예: AAC 128k)만 만들어 모든 비디오 렌디션이 공유합니다(화질은 여러 개, 소리는 하나). 일부 구성만 2~3개 오디오 비트레이트를 둡니다.
- 코덱·설정이 맞으면 재인코딩 없이 패스스루하기도 합니다(비디오보다 훨씬 가벼움).
A/V 싱크가 까다로운 이유: 영상 프레임과 오디오 프레임은 길이가 안 맞습니다. AAC 한 프레임 = 1024 샘플 → 48kHz면 약 21.3ms인데, 30fps 비디오 프레임은 33.3ms라 딱 떨어지지 않습니다. 그래서 "프레임 개수"가 아니라 타임스탬프(PTS)로 맞춥니다 — 같은 시계 위에서 영상·소리가 각자 PTS를 달고, 플레이어가 그 PTS 기준으로 정렬해 재생합니다. 세그먼트는 비디오 IDR에서 자르므로 오디오는 그 경계에 가장 가까운 프레임에서 갈리고, 이 미세한 어긋남은 컨테이너 타임스탬프로 흡수됩니다(바로 다음 절).
정정: "단일 인코더가 모든 화질을 동시에" 는 벤더마다 다르다
정정: "하나의 인코더 인스턴스가 1080p/720p/480p를 동시 병렬로 인코딩한다"는 흔한 설명은 부분적으로만 맞습니다. 현대 클라우드 트랜스코딩 플랫폼(AWS Elemental MediaConvert/MediaLive 등)은 단일 소스로부터 여러 렌디션을 병렬로 생성하는 것을 지원합니다. 그러나 "단일 인코더 인스턴스 하나가 모든 렌디션을 동시에 처리"하는지는 실제 구현(벤더/구성)에 따라 다릅니다. 일반적으로 각 해상도는 분리된 인코딩 작업(transcoding job)으로 처리되며, 라이브의 병렬 처리 능력은 할당된 리소스에 좌우됩니다. 정확한 표현은 "단일 입력으로부터 병렬 렌디션 생성 지원" 입니다. ([NEEDS VERIFICATION] — 특정 벤더의 "단일 인스턴스 동시 인코딩" 여부는 해당 제품 문서로 확인 필요)
| 흔한 단정 | 정확한 이해 |
|---|---|
| "인코더 1개가 3개 화질 동시 처리" | 벤더/구성마다 다름. 분리된 잡으로 처리되는 경우가 일반적 |
| "라이브 ABR = 실시간 가능" | ✅ 라이브 ABR은 실시간 처리를 목표로 설계됨(단, 할당 리소스가 충분해야 실시간 유지) |
| "VOD ABR = 실시간" | ❌ VOD는 보통 배치(오프라인) 처리 |
⚠️ ABR Ladder는 화질을 "올리는" 게 아니다
자주 헷갈리는 지점입니다. 서버는 여러 화질을 미리 다 만들어 둡니다. "올린다/내린다"를 결정하는 주체는 플레이어입니다. 플레이어가 자기 대역폭을 측정해서 "지금은 720p를 받을 수 있겠다"고 선택할 뿐입니다.
여기서 ABR Ladder의 키프레임 정렬(렌디션 전환이 매끄럽게 되려면 GOP 경계가 맞아야 함)이 중요해지는데, 그 산수는 #37·#38에서 이미 다뤘으니 여기선 "서버가 여러 화질을 동시에 만든다"는 사실만 짚고 넘어갑니다.
4. ★ AWS MediaLive vs MediaConvert — 역할이 완전히 다르다
트랜스코딩을 직접 구현하기보다 클라우드 서비스를 쓰는 경우가 많은데, AWS에서 가장 자주 헷갈리는 두 서비스가 MediaLive와 MediaConvert입니다. 이름이 비슷해서 섞어 쓰기 쉽지만 역할이 완전히 다릅니다.
한 줄 구분: MediaLive = 라이브(실시간) 트랜스코딩. MediaConvert = 파일 기반(VOD) 트랜스코딩.
| 항목 | AWS Elemental MediaLive | AWS Elemental MediaConvert |
|---|---|---|
| 처리 대상 | 라이브 입력 소스 (카메라, 라이브 이벤트, RTMP/SRT 등) | S3에 있는 비디오 파일 |
| 처리 방식 | 실시간 스트리밍 인코딩 | 배치(batch) 오프라인 트랜스코딩 |
| 최적화 | 실시간 저지연 라이브 | 파일 변환 품질/효율 |
| 대표 용도 | 생방송 — 스포츠 중계, 웨비나, 라이브 커머스 | VOD — 온디맨드 비디오, 콘텐츠 사전 준비 |
| 입력 흐름 | 끊임없이 들어오는 라이브 스트림 | 이미 완성된 파일 1개를 변환 |
이 글의 주제(라이브 파이프라인)에 해당하는 건 MediaLive 쪽입니다. 같은 영상이라도 "지금 흐르고 있는 라이브"냐 "이미 끝나서 파일로 저장된 VOD"냐에 따라 도구가 갈립니다.
⚠️ 실무 함정: "라이브 다시보기"를 만든다고 MediaConvert부터 떠올리면 안 됩니다. 라이브 중에는 MediaLive가 세그먼트를 만들고, 방송이 끝난 뒤 그 세그먼트들을 모아 VOD 자산으로 정리할 때 MediaConvert를 쓰는 식으로 단계가 다릅니다.
5. 3단계 — HLS 패키징(세그먼트 + m3u8 실시간 갱신)
트랜스코딩이 만든 인코딩 스트림을, 이제 작은 파일들 + 목차 파일로 만듭니다. 이게 패키징입니다.
세그먼트화 — 영상을 토막낸다
연속된 영상을 일정 길이(예: 6초)의 파일로 자릅니다. 이 파일이 세그먼트(.ts 또는 fMP4의 .m4s)입니다.
패키징 안에서 — ES를 컨테이너에 담고, IDR에서 자른다
인코더가 뱉은 H.264 ES(NAL 줄기)는 그대로 파일로 못 씁니다. 컨테이너로 감싸고(packetize) → 토막내고(segment) → 목차에 적는(playlist) 3단을 거칩니다.
여기서 한 가지 실무 함정: 플레이리스트 기록은 원자적(atomic)이어야 합니다. 서버가 m3u8을 새로 쓰는 도중에 플레이어가 읽으면 반쪽짜리 목차를 받습니다. 그래서 보통 임시 파일에 다 쓴 뒤 rename으로 교체합니다. 또 마스터 플레이리스트에는 렌디션별 BANDWIDTH·RESOLUTION·CODECS가 적혀, 플레이어가 첫 화질을 고를 근거가 됩니다.
타임스탬프가 파이프라인을 관통한다 — PTS·DTS·PCR
영상과 소리를 "언제 보여줄지"는 픽셀이 아니라 타임스탬프가 결정합니다. 이 타이밍 정보가 파이프라인 전 구간을 관통합니다.
- PTS(Presentation TS): 이 프레임을 화면에 표시할 시각. DTS(Decoding TS): 디코딩할 시각.
- B-frame이 있으면 디코딩 순서 ≠ 표시 순서라 DTS ≠ PTS가 됩니다.
- MPEG-TS는 PTS/DTS를 33비트·90kHz 클록으로 싣고, PCR(Program Clock Reference)을 기준 시계로 흘려보내 디코더 시계를 맞춥니다.
라이브에서 실무적으로 중요한 두 가지: ① B-frame을 쓰면 디코드 재정렬(reorder) 지연이 붙어 라이브에선 이를 줄이거나 끄는 경우가 많습니다. ② 이 HLS 스트림을 단일 타임스탬프만 가진 경로(WebRTC RTP 등)로 변환할 때 DTS/PTS 분리가 충돌을 일으킵니다 — 이 추적은 #26·#42에서 자세히 다뤘습니다.
m3u8 — 세그먼트들의 목차
플레이어는 어느 세그먼트가 있는지 알아야 합니다. 그 목차가 m3u8 미디어 플레이리스트입니다. 라이브에서는 이 파일이 계속 갱신됩니다.
라이브 미디어 플레이리스트 예시:
새 세그먼트가 인코딩되면, 서버는 그걸 m3u8에 추가(append) 합니다:
#EXTINF: 각 세그먼트의 재생 길이(초)#EXT-X-MEDIA-SEQUENCE: 첫 세그먼트의 시퀀스 번호. 새 세그먼트가 추가되고 오래된 게 빠지면 단조 증가- 라이브에는
#EXT-X-ENDLIST가 없습니다 (방송이 안 끝났다는 신호). VOD에는 있습니다.
이건 RFC 8216(HTTP Live Streaming)에 정의된 표준 메커니즘입니다. 플레이리스트는 EXT-X-MEDIA-SEQUENCE로 식별되고, 시퀀스 번호는 단조 증가해야 합니다.
플레이어는 어떻게 새 영상을 아는가 — 주기적 재요청(polling)
여기가 HLS의 핵심 동작입니다. 서버가 밀어주는(push) 게 아니라, 플레이어가 주기적으로 m3u8을 다시 요청(pull) 합니다.
플레이어는 보통 타깃 구간(TARGETDURATION) 정도의 주기로 플레이리스트를 다시 받아 새 세그먼트를 감지합니다. 이 "주기적 재요청" 구조가 곧 다음 장에서 다룰 지연의 한 축입니다.
6. 4단계 — CDN 배달 + 플레이어
왜 CDN으로 무한 확장되는가
여기가 HLS가 WebRTC와 결정적으로 갈리는 지점입니다. HLS의 세그먼트와 m3u8은 그냥 HTTP 파일입니다. 즉, 일반 웹 파일과 똑같이 CDN에 캐싱됩니다.
- 시청자가 100만 명이어도 Origin은 세그먼트를 한 벌만 만들면 됩니다.
- 같은
seg13.ts를 수많은 시청자가 요청 → CDN Edge가 캐시된 복사본을 뿌림. - 이게 "HLS는 시청자 수에 대해 사실상 무한 확장된다"의 메커니즘입니다.
🟢 장점: 기존 웹 CDN 인프라를 그대로 활용 → 비용 효율 + 무한 확장 + 방화벽 통과(HTTP/HTTPS라 포트 문제 없음). 🔴 대가: 파일 단위 캐싱이므로 "파일이 완성될 때까지" 기다려야 함 → 지연.
CDN 안에서 — origin/edge와 캐시 TTL의 분업
CDN이 "그냥 캐싱"이라지만, 무엇을 얼마나 오래 캐싱하느냐가 라이브에선 결정적입니다.
⚠️ 여기서 가장 흔한 라이브 사고: m3u8에 세그먼트처럼 긴 TTL을 걸면, Edge가 옛 목차를 계속 돌려줘 플레이어가 새 세그먼트를 영영 못 보고 멈춥니다. "방송은 도는데 시청자만 멈춘다"의 단골 원인이 이 캐시 설정입니다. 세그먼트는 불변이라 오래 캐싱, 목차는 자주 바뀌니 짧게 — 이 분업이 핵심입니다.
플레이어 안에서 — MSE로 받아서 디코드·렌더까지
브라우저가 m3u8과 세그먼트를 받아 화면에 그리는 과정도 단계가 있습니다.
- MSE는 JS가
<video>에 미디어 바이트를 직접 먹이는 API입니다. Chrome/Firefox 등은 hls.js가 MSE 위에서 HLS를 재생하고, Safari/iOS는 HLS를 네이티브로 재생해 MSE가 따로 필요 없습니다. (#47에서 다룬 "Flash 이후 표준"의 실체) - ABR 결정은 플레이어 몫입니다. 서버는 모든 렌디션을 올려둘 뿐, 매 세그먼트 경계에서 "지금 처리량이면 720p" 식으로 고르는 건 ④⑤의 클라이언트 로직입니다.
플레이어 버퍼
플레이어는 끊김을 막기 위해 세그먼트를 여러 개 미리 다운로드(선다운로드) 해 둡니다. 보통 3개 정도를 버퍼에 채운 뒤 재생을 시작합니다. 이 버퍼가 지연의 또 다른 큰 축입니다.
7. ★ 6~10초 지연의 정체 — 그리고 정정
이제 첫머리 질문에 답합니다. 지연은 한 군데서 오는 게 아니라 여러 단계에 걸쳐 쌓입니다.
정정: "6~10초"는 세그먼트 길이지 총 지연이 아니다
정정: "HLS 지연 = 세그먼트 길이 × 세그먼트 수"라는 단순 산식은 부분적으로만 맞습니다. 6~10초는 보통 세그먼트 길이를 가리키는 값이고, 실제 총 지연(glass-to-glass)은 흔히 18~30초(약 20초) 입니다. 세그먼트 길이는 지연의 한 항목일 뿐이고, 세그먼트 인코딩 시간, 플레이어 버퍼 정책(보통 3 세그먼트), 네트워크/CDN 전파 지연 등 여러 요인의 합입니다. (Apple은 권장 세그먼트 길이를 10초에서 6초로 줄였지만, 그래도 총 지연은 훨씬 큽니다.)
지연 분해표
| 단계 | 지연 기여 | 이유 |
|---|---|---|
| ① 캡처 + 송신 인코딩 | 수백 ms ~ 1초 | OBS 인코더 버퍼 |
| ② RTMP 업링크 | 수백 ms | TCP, 네트워크 |
| ③ 서버 디코드 + 멀티 인코딩 | 수백 ms ~ 수초 | ABR 렌디션 생성 |
| ④ 세그먼트 완성 대기 | = 세그먼트 길이 (6초) | 6초가 차야 파일이 완성됨 |
| ⑤ CDN 전파 | 수백 ms ~ 수초 | Origin → Edge 캐싱 |
| ⑥ 플레이어 버퍼 | = 세그먼트 길이 × 버퍼 세그먼트 수(보통 3, ≈18초) | 끊김 방지용 선다운로드 |
| ⑦ 디코딩 + 재생 | 수십 ms | 클라이언트 |
지배 변수 두 개
지연을 결정하는 건 사실상 ④와 ⑥, 둘 다 세그먼트 길이의 함수입니다.
⚠️ 트레이드오프: 세그먼트를 짧게 하면 지연은 줄지만, 파일 개수가 폭증해 HTTP 요청 오버헤드가 커지고 CDN 캐시 효율이 떨어집니다. 그래서 무작정 줄일 수 없습니다. 이 한계를 구조적으로 깨려는 게 LL-HLS(부분 세그먼트 + 청킹으로 2~5초)이고, 아예 다른 패러다임이 WebRTC(1초 미만)입니다. (#40 비교 참고)
seek(되감기)는 왜 라이브에서도 되는가
"라이브인데 어떻게 30초 전으로 되감아지나?" — 신기할 것 없습니다. 과거 세그먼트(.ts)는 이미 파일로 만들어져 CDN에 캐싱돼 있기 때문입니다.
라이브의 seek 가능 범위는 m3u8 슬라이딩 윈도우에 남아 있는 세그먼트 + DVR 윈도우(과거 세그먼트를 얼마나 보존하느냐) 설정에 달려 있습니다. 보존을 길게 하면 더 많이 되감을 수 있습니다.
LL-HLS — 같은 파이프라인을 잘게 쪼개 지연을 줄인다
지연의 지배 변수가 "세그먼트 길이 × 버퍼"였으니, 줄이는 길도 거기 있습니다. 세그먼트를 통째로 기다리지 말고 더 잘게 쪼개는 것 — 그게 LL-HLS(Low-Latency HLS)입니다. 코덱·CDN 구조는 그대로 두고 패키징·전달 방식만 바꿉니다.
- Partial Segment (
EXT-X-PART): 6초가 다 차길 기다리지 않고 200~500ms짜리 "조각의 조각"을 먼저 공개. 패키저가 세그먼트를 닫기 전에 파트부터 내보냅니다. - Blocking Playlist Reload: 플레이어가 "다음 파트 나오면 응답해"라고 요청하면, 서버가 새 파트가 생길 때까지 응답을 붙들었다가(hold) 즉시 돌려줍니다 → 주기적 polling의 대기 지연 제거.
- Preload Hint (
EXT-X-PRELOAD-HINT): 곧 나올 파트의 URL을 미리 알려줘 플레이어가 선요청.
참고: 초기 LL-HLS 초안의 HTTP/2 Push는 2020년 스펙에서 제거됐습니다. (#40에 정정 기록)
이걸로 글래스-투-글래스를 2~5초대로 끌어내립니다. 대가는 파트 단위로 파일·요청 수가 폭증해 origin/CDN 부하와 구성 복잡도가 올라간다는 것. "그래서 HLS냐 LL-HLS냐 WebRTC냐"의 의사결정 프레임은 #40에서 지연 예산 기준으로 정리했습니다.
8. HLS vs WebRTC — 같은 "라이브"의 정반대 설계
왜 유튜브 라이브(HLS류)는 느려도 괜찮고, 화상회의(WebRTC)는 빨라야 할까요. 두 시스템이 정반대 트레이드오프를 택했기 때문입니다.
| 항목 | HLS 라이브 파이프라인 | WebRTC |
|---|---|---|
| 전송 단위 | 파일(세그먼트) | RTP 패킷 |
| 배달 방식 | HTTP pull (플레이어가 폴링) | 실시간 push |
| 지연 | 18 | 1초 미만 |
| 확장성 | CDN 캐싱으로 사실상 무한 | SFU/서버 부하에 비례 |
| 상호작용 | 단방향 시청 (지연 OK) | 양방향 대화 (지연 치명적) |
| 인프라 | 기존 웹 CDN 재활용 | 전용 미디어 서버(SFU) 필요 |
비유를 기술로 환원하면: HLS는 영상을 파일화해서 일반 웹 인프라(CDN)에 얹기 때문에 캐싱·확장이 공짜로 따라오지만, "파일이 완성될 때까지 기다리는" 구조적 지연을 떠안습니다. WebRTC는 패킷을 실시간 포워딩하므로 지연이 거의 없지만, 캐싱이 불가능해 시청자 수만큼 서버 자원이 듭니다.
정정: WebRTC 서버(SFU)의 확장 한계
라이브 = HLS, 대화 = WebRTC라는 큰 그림에서, WebRTC 쪽 확장이 왜 까다로운지 한 번 정리해 둡니다.
SFU(Selective Forwarding Unit) 는 받은 RTP 패킷을 디코딩/재인코딩 없이 그대로 포워딩합니다(재암호화 제외). 그래서 합성(transcoding)하는 MCU보다 저지연·저비용입니다. 하지만 무한히 크지는 못합니다.
| 흔한 단정 | 정정 |
|---|---|
| "단일 SFU = 수백~2000명 한계, 대역폭 때문" | ⚠️ 부분적 사실. 단일 SFU는 보통 500~1000명을 안전 지원(구현에 따라 그 이상도). 한계 원인은 대역폭뿐 아니라 CPU·메모리·네트워크 I/O·패킷 처리 능력의 복합 |
| "아웃바운드 = N×(N-1)" | ✅ 풀메시 기준 산식은 맞음 (참가자 N명이면 각자 N-1개 수신). 단 이게 한계의 단일 원인은 아님 |
| "수만 명은 불가능" | ❌ SFU Cascading(지역별 SFU 계층 연결)으로 수만~수십만 뷰어까지 확장. 서버 간 전용망의 추가 지연은 인접 권역이면 수십 ms, 대륙 간이면 100ms 안팎까지 — 결국 지역 간 거리(RTT)에 좌우됨 |
즉 HLS는 "파일 + CDN"으로, WebRTC는 "SFU Cascading"으로 확장합니다. 같은 "많은 시청자"라는 목표를 정반대 메커니즘으로 푸는 셈입니다.
9. 실무 의사결정 — 어떤 파이프라인을 고를까
| 질문 | 조건 | 선택 |
|---|---|---|
| Q1. 시청자가 양방향으로 대화/상호작용 하는가? | YES | WebRTC(SFU). 1초 미만 지연 필요 |
| NO | → Q2 | |
| Q2. 허용 지연 예산이 얼마인가? | ~30초 OK | 표준 HLS (CDN 무한 확장, 최저 비용) |
| 2~5초 필요 | LL-HLS (부분 세그먼트 + 청킹) | |
| 1초 미만 | WebRTC | |
| Q3. 라이브 트랜스코딩을 직접 운영? 클라우드? | 클라우드(라이브) | AWS MediaLive 등 |
| 클라우드(VOD) | AWS MediaConvert 등 | |
| 자체 운영 | ffmpeg + nginx-rtmp / 전용 미디어 서버 |
| 시나리오 | 권장 | 이유 |
|---|---|---|
| 스포츠/콘서트 대규모 중계 | HLS + CDN | 시청자 폭주에도 무한 확장, 30초 지연 허용 |
| 라이브 커머스(채팅 정도) | LL-HLS | 2~5초면 구매 반응 충분, CDN 확장 유지 |
| 양방향 라이브 방송(호스트↔시청자 통화) | WebRTC(SFU) | 대화에 30초 지연은 불가능 |
| 방송 종료 후 다시보기 | VOD 패키징(MediaConvert류) | 파일 기반 배치 처리 |
10. 한 장 요약 — 암기 카드
| 단계 | 입력 → 출력 | 한 줄 |
|---|---|---|
| 1. Ingest | OBS → 서버 | RTMP push, H.264/AAC 1줄기 (이미 인코딩됨) |
| 2. Transcode | 1줄기 → N줄기 | 디코드 1번 → 멀티 인코딩(ABR Ladder) |
| 3. Package | N줄기 → 파일더미 | 세그먼트(.ts/.m4s) + m3u8(append로 실시간 갱신) |
| 4. Deliver | 파일 → CDN → 플레이어 | HTTP pull, 플레이어가 m3u8 주기 폴링 + 버퍼 3개 |
| 개념 | 한 줄 요약 |
|---|---|
| m3u8 갱신 | 새 세그먼트를 #EXTINF로 append, MEDIA-SEQUENCE 단조 증가 |
| ABR | 서버가 여러 화질 미리 생성, 플레이어가 대역폭 보고 선택 |
| 6~10초 | 세그먼트 길이일 뿐. 총 지연은 보통 18~30초 |
| 지연 지배 변수 | 세그먼트 완성 대기 + 버퍼(3개) — 둘 다 세그먼트 길이 함수 |
| seek(되감기) | 과거 .ts는 이미 파일 → 과거 파일 재요청일 뿐 |
| MediaLive vs MediaConvert | 라이브(실시간) vs 파일(VOD). 역할 완전 분리 |
| HLS vs WebRTC | 파일+CDN(느림·무한확장) vs 패킷+SFU(빠름·자원비례) |
11. 한 줄 결론
라이브 스트리밍 서버는 RTMP 1줄기를 받아(Ingest), 디코드 후 여러 화질로 다시 인코딩하고(Transcode), 6초짜리 파일로 잘라 m3u8 목차에 계속 추가하며(Package), CDN으로 뿌린다(Deliver). 6
10초는 세그먼트 "길이"일 뿐이고 실제 지연은 보통 1830초인데, 이건 느린 코드가 아니라 "파일로 잘라 HTTP로 주기 폴링" 하는 구조가 만든 비용이다. 그 대가로 CDN 무한 확장·VOD 호환·방화벽 통과를 얻는다. 지연이 치명적인 양방향 대화에는 같은 "라이브"라도 WebRTC(SFU)라는 정반대 설계가 답이다. 그리고 AWS에서는 라이브=MediaLive, VOD=MediaConvert로 도구 자체가 갈린다.
관련 글
- #40 라이브 스트리밍 프로토콜 선택 — HLS / LL-HLS / WebRTC 의사결정 — 지연 예산으로 프로토콜 고르는 프레임
- #37 GOP-세그먼트 정렬 산수 — 세그먼트가 키프레임으로 시작해야 하는 이유
- #38 ABR Ladder와 키프레임 동기화 — 렌디션 전환이 매끄러우려면
- #43 Agora 자체 코덱 vs Web SDK, SD-RTN, FEC — 실시간 RTC 쪽 네트워크 레이어
- #28 H.264 Profile·인코더 옵션·비트레이트의 현실 — 멀티 인코딩의 코덱 레이어
참고 자료
- RFC 8216 — HTTP Live Streaming — m3u8/EXTINF/MEDIA-SEQUENCE 표준
- Apple — Enabling Low-Latency HLS — LL-HLS 공식 가이드
- AWS Elemental MediaLive Features — 라이브 실시간 인코딩
- AWS Elemental MediaConvert — 파일 기반 VOD 트랜스코딩
- AWS — MediaConvert vs MediaLive 비교 — 두 서비스 역할 구분
- Mux — Adaptive Bitrate Streaming Guide — ABR 동작 원리
- Fastpix — Reducing Latency in HLS Streaming — HLS 지연 분해와 저감
- GetStream — SFU Cascading Architecture — WebRTC 확장 구조
- webrtcHacks — SFU Cascading — 서버 간 캐스케이딩 지연이 지역 거리(RTT)에 좌우되는 이유