SCTE-35 OUT 신호와 START-DATE 사이 8초 지연의 원인
SRT로 들어온 광고 신호와 매니페스트에 찍힌 광고 시작 시각이 최대 8초까지 벌어졌습니다. 범인은 한 명이 아니라 두 명이었고, 그중 한 명은 항상 같은 만큼만 훔쳐 갔습니다.
같은 광고 브레이크, 두 개의 시각
국내 OTT 사업자의 실 운영 채널에서 SSAI 광고 삽입을 검증하던 중이었습니다. SRT로 올라오는 원본 스트림에는 SCTE-35 OUT 큐가 실려 있고, 운영 중인 SSAI 파이프라인은 이 큐를 받아 HLS 매니페스트의 EXT-X-DATERANGE에 옮겨 적습니다. SCTE-35는 방송 신호 안에 광고 전환 지점을 표시하는 큐 포맷이고, EXT-X-DATERANGE는 그 지점을 HLS 플레이어가 읽을 수 있는 시각 정보로 바꿔 광고 구간의 시작과 길이를 알려주는 태그입니다. 이 둘이 정확히 같은 순간을 가리켜야 광고가 원본 방송이 끝나는 지점에 딱 맞춰 붙습니다. 그런데 두 시각이 맞지 않았습니다.
인제스트 쪽에서 신호를 잡은 순간의 로그입니다.
[ingest] srt://origin.example.com:9000?streamid=<stream-id>
2026-03-11T04:21:07.318Z scte35 detected cue=OUT
splice_pts=8143092391
planned_duration=508.000
2026-03-11T04:21:07.318Z estimated_utc=2026-03-11T04:21:04.902Z같은 브레이크가 매니페스트에는 이렇게 나왔습니다.
#EXT-X-PROGRAM-DATE-TIME:2026-03-11T04:21:12.900Z
#EXT-X-DATERANGE:ID="<ad-break-id>",START-DATE="2026-03-11T04:21:12.900Z",PLANNED-DURATION=508.000,SCTE35-OUT=0xFC302F00...
#EXTINF:4.000,
segment_<channel-id>_10482.ts산수를 해보면 이렇습니다.
START-DATE 2026-03-11T04:21:12.900Z
estimated_utc 2026-03-11T04:21:04.902Z
--------------------------------------------
Delta +7.998s8초입니다. 508초짜리 브레이크에서 8초면 비율로는 작아 보이지만, 광고 소재 하나가 15초인 환경에서 8초는 절반이 넘습니다. 브레이크 앞부분에 원본 방송 화면이 8초쯤 남아서 새어 나가는 셈입니다.
측정 기준: 무엇을 무엇과 비교했는가
이런 종류의 이슈는 "무엇을 기준으로 쟀는가"를 흐리게 두면 논쟁만 길어집니다. 그래서 측정에 쓰는 값을 세 개로 못박았습니다.
| 값 | 정의 | 성격 |
|---|---|---|
detection_time | SRT 스트림에서 SCTE-35 패킷을 최초로 감지한 wall clock | 관측 시각이라 경로 지연이 섞여 있습니다 |
estimated_utc | video PTS를 wall clock으로 매핑해서 추정한 splice 시점 | 실제 splice 시점에 가장 가까운 값입니다 (오차 약 0.3초) |
START-DATE | 패키저가 EXT-X-DATERANGE에 써넣은 광고 시작 시각 | 플레이어가 실제로 믿게 되는 값입니다 |
추적한 지표는 하나입니다.
SRT Delta = START-DATE - estimated_utcdetection_time을 기준으로 쓰지 않은 이유가 있습니다. detection_time은 "패킷이 관측 지점에 도착한 시각"이라서 네트워크와 처리 경로가 그대로 섞여 들어갑니다. 반면 estimated_utc는 PTS 기반이라 미디어 타임라인 위에 붙어 있습니다. 지연을 재려면 미디어 타임라인 위의 진짜 splice 지점과 매니페스트에 적힌 값을 비교해야 원인 구간이 드러납니다.
참고: 지연 측정에서 "도착 시각"과 "발생 시각"을 섞으면, 나중에 어느 구간을 고쳐야 하는지 끝까지 나오지 않습니다. PTS 기반 추정값을 하나 만들어 두는 것이 디버깅 비용을 크게 줄여줍니다.
여러 브레이크를 모아보니 범위는 3초에서 8초
한 건만 보면 8초인데, 브레이크를 여러 개 모으면 값이 흔들렸습니다. 대표적인 관측 샘플입니다.
| 브레이크 | estimated_utc | START-DATE | SRT Delta |
|---|---|---|---|
| A | 04:21:04.902Z | 04:21:12.900Z | +7.998s |
| B | 05:02:33.140Z | 05:02:36.280Z | +3.140s |
| C | 06:14:51.005Z | 06:14:56.500Z | +5.495s |
흥미로운 건 하한선이 존재한다는 점이었습니다. 아무리 좋은 조건에서도 3초 아래로는 내려가지 않았습니다. 위로는 8초 근처에서 멈췄습니다. "0초에서 8초 사이에서 무작위로 흔들린다"가 아니라 "3초는 무조건 붙고, 거기에 최대 5초가 더 얹힌다"는 모양입니다.
이 모양이 힌트였습니다. 항상 붙는 고정값과 조건에 따라 붙는 가변값은 원인이 다를 수밖에 없습니다.
원인 1: 신호 경로가 두 개라서 생기는 고정 3초
첫 번째는 지연이라기보다 측정 구조의 문제였습니다.
파이프라인 안에서 SCTE-35 태그는 두 경로를 지납니다. 하나는 ingest 계층이 원본 태그를 파싱해 패키저로 넘기고, 패키저가 이 값을 그대로 START-DATE로 써넣는 실제 경로입니다. 다른 하나는 검증을 위해 SRT 모니터링 스트림에서 따로 태그를 감지하는 경로입니다.
estimated_utc를 뽑은 곳은 아래쪽, 즉 검증용 SRT 모니터링 스트림입니다. 정작 START-DATE를 결정하는 것은 위쪽 ingest 계층입니다. 두 경로는 버퍼링과 처리 단계가 다르고, 그 차이가 약 3초였습니다.
이 3초는 파이프라인이 광고를 늦게 넣어서 생긴 값이 아니라, 관측 지점이 서로 달라서 생긴 상수 오프셋입니다. 그래서 실제 지연을 논할 때는 이 값을 먼저 차감해야 합니다.
실제 삽입 지연 = SRT Delta - 3.0s (경로 오프셋)
브레이크 A: 7.998 - 3.0 = 약 5.0s
브레이크 B: 3.140 - 3.0 = 약 0.14s
브레이크 C: 5.495 - 3.0 = 약 2.5s주의: 이 오프셋을 모른 채로 "지연 8초"라고 보고하면, 실제로는 5초짜리 문제를 8초짜리로 부풀려서 엉뚱한 곳을 파게 됩니다. 측정계의 고정 오차를 먼저 분리하는 것이 순서입니다.
원인 2: CTS가 크면 광고 시작점을 segment 경계까지 밀어버린다
3초를 빼고 남은 0초에서 5초, 이게 진짜 문제입니다. 이 값이 흔들리는 이유는 원본 스트림의 CTS 였습니다.
CTS는 디코딩 순서와 표시 순서의 차이를 메우는 값입니다. B-frame이 들어간 스트림에서는 먼저 디코딩되지만 나중에 표시되는 프레임이 생기고, 그 간격이 CTS로 표현됩니다.
패키저는 SCTE-35의 splice 지점을 받으면 이 지점이 어느 segment 경계에 해당하는지 판단해야 합니다. 광고는 segment 단위로만 갈아끼울 수 있으니, splice 지점이 segment 중간에 걸리면 어느 한쪽 경계로 붙여야 합니다.
문제는 CTS 값이 클 때 이 판단이 흐려진다는 점입니다. splice 지점을 정확히 짚지 못하면 안전한 쪽을 택해 다음 segment 경계까지 미룹니다. segment 길이가 4초 전후인 환경에서 경계를 한 번 미루면 4초, 판단이 한 번 더 어긋나면 그 이상이 붙습니다. 가변 지연의 상한이 5초 근처에서 형성된 이유입니다.
이 구조를 이해하면 지연이 왜 항상 같은 값이 아닌지도 설명됩니다. splice 지점이 segment 경계에 우연히 가까우면 밀리는 양이 작고(브레이크 B의 0.14초), 경계 직후에 걸리면 거의 한 segment를 통째로 기다립니다(브레이크 A의 5초).
참고: 광고 삽입 지연이 segment 길이의 배수에 가깝게 튀어나오면, 원인은 거의 항상 경계 정렬입니다. 네트워크 지연이라면 이렇게 계단식으로 나오지 않습니다.
시계는 범인이 아니었다
이런 종류의 시간차를 보면 제일 먼저 의심받는 것이 서버 시계입니다. 그래서 먼저 걷어냈습니다.
- 모니터링 서버와 패키저는 같은 리전에 배치되어 있습니다
- 양쪽 모두 NTP 동기화 상태입니다
- 실측 시계 차이는 100ms 미만입니다
100ms는 지금 다루는 3초에서 8초 스케일에 비하면 무의미한 값입니다. 시계 동기화가 측정값에 영향을 주지 않는다는 것을 먼저 확정해 두면, 이후 논의에서 "혹시 시계가 틀린 것 아니냐"는 되돌이표를 없앨 수 있습니다.
이 지연이 뒤에 낳는 문제들
여기서 확인한 OUT 태그 지연은 그 자체로도 문제지만, 이후 다룰 현상들의 뿌리이기도 합니다.
| 이 지연이 만드는 결과 | 비고 |
|---|---|
IN 마커가 END-DATE보다 먼저 나타납니다 | 다른 글에서 다룹니다 |
| 광고 구간의 PDT 간격과 EXTINF 합이 맞지 않습니다 | 다른 글에서 다룹니다 |
| 이미 발행된 매니페스트가 나중에 바뀝니다 | 다른 글에서 다룹니다 |
OUT의 위치가 밀리면 광고 구간 전체의 좌표계가 함께 밀립니다. 뒤쪽 현상들을 각각 독립된 버그로 취급하면 근본 원인에 닿지 못합니다.
지금까지 정리한 것과 남은 것
지연을 두 조각으로 쪼개고 나면 대응이 갈립니다.
| 조각 | 크기 | 성격 | 대응 |
|---|---|---|---|
| 신호 경로 오프셋 | 약 3초 고정 | 관측 구조에서 오는 상수 | 측정할 때 차감하면 끝입니다 |
| segment 경계 밀림 | 0초에서 5초 가변 | CTS 기반 splice 판단 정확도 문제 | 실제로 고쳐야 하는 부분입니다 |
고쳐야 할 것은 두 번째입니다. splice 지점을 CTS가 큰 스트림에서도 정확히 짚어내면, 다음 경계로 통째로 미루는 회피 동작이 사라지고 가변 지연이 줄어듭니다.
정리
- SRT로 들어온 SCTE-35 OUT의 splice 시점과 매니페스트의
START-DATE사이에 3초에서 8초의 차이가 관측됐습니다. - 이 차이는 두 성분의 합입니다. 관측 경로가 달라서 생기는 고정 3초와, splice 지점을 segment 경계로 밀면서 생기는 0초에서 5초의 가변분입니다.
- 지연을 잴 때는
detection_time(도착 시각)이 아니라 PTS 기반estimated_utc(발생 시각)를 기준으로 삼아야 원인 구간이 드러납니다. - 가변분의 원인은 CTS가 큰 스트림에서 splice 지점 판단이 흐려져 다음 segment 경계까지 미루는 동작입니다. 지연이 segment 길이의 배수처럼 튀는 것이 그 증거입니다.
- 시계 동기화는 원인이 아닙니다(차이 100ms 미만). 이를 먼저 배제해 두면 조사 범위가 크게 줄어듭니다.