Skip to content

타임시프트 delay 파라미터와 캐시 키 폭증 문제 ​

오리진 RPS가 한도 100에서 294로 올라가고, 그 결과가 404로 둔갑해 다운스트림 CDN까지 퍼진 사건입니다.

그날 그래프부터 ​

대형 스포츠 생중계가 걸린 피크 트래픽 이벤트였습니다. 라이브 본 채널은 아주 멀쩡했습니다. CDN 엣지 기준 최고 대역폭은 약 2.2 Gbps였고, 인코더와 패키저 모두 리소스 여유가 있었으며 라이브 도메인의 재생 지표에도 이상이 없었습니다.

문제는 타임시프트 재생 도메인 하나에서만 터졌습니다. 이벤트 시작 후 40분쯤 지난 시점부터 패키저 오리진 내부에서 404가 다량으로 발생하기 시작했고, 타임시프트 도메인 재생이 아예 불가능해졌습니다. 다운스트림 CDN 쪽에도 4xx와 5xx가 그대로 반환됐습니다.

로그 모양은 대충 이랬습니다. 식별자는 전부 치환해 두었습니다.

# 엣지 액세스 로그 (타임시프트 도메인)
GET /live/<channel-id>/index.m3u8?delay=1803  200  1.2ms  HIT
GET /live/<channel-id>/index.m3u8?delay=1804  404  812ms  MISS
GET /live/<channel-id>/index.m3u8?delay=1811  404  907ms  MISS
GET /live/<channel-id>/index.m3u8?delay=1826  404  1104ms MISS
GET /live/<channel-id>/index.m3u8?delay=1839  404  1233ms MISS

# 오리진(패키저) 로그
[origin.example.com] queue_depth=1187 upstream_timeout=1000ms -> 404

여기서 이미 절반은 답이 나옵니다. delay 값만 다른 요청들이 전부 MISS로 오리진까지 내려가고 있고, 응답 시간이 계단식으로 늘어나다가 404가 됩니다.

숫자를 붙여보면 ​

항목값
이벤트 최고 대역폭 (CDN 엣지, origin server mode)약 2.2 Gbps
사고 시점 패키저 오리진의 동시 요청 처리 한도초당 약 100회
실제 유입된 요청량초당 294회 초과
초과 배수약 2.94배
조치 후 상향한 처리 한도초당 10,000회
복구 후 정상 상태의 실측 요청량초당 약 300회

참고: 마지막 두 줄이 이 사건의 성격을 말해줍니다. 복구 후에도 실제 필요량은 초당 300회 근처였습니다. 트래픽이 비정상적으로 폭주한 게 아니라, 원래 필요했던 양이 처음부터 한도의 3배였다는 뜻입니다. 용량 산정이 요청 패턴을 따라가지 못한 경우입니다.

왜 CDN이 막아주지 못했나: cache key 폭발 ​

타임시프트 재생 URL은 보통 이런 모양입니다.

https://edge.example.com/live/<channel-id>/index.m3u8?delay=1800
                                                      ^^^^^^^^^^
                                                      지연 시간(초)

CDN 엣지는 이 쿼리 스트링을 캐시 키에 포함해서 동작합니다. delay=1800과 delay=1801은 CDN 입장에서 완전히 다른 객체입니다. 바이트가 99.9% 겹쳐도 상관없습니다. 키가 다르면 다른 물건입니다.

        요청 URL                                cache key                 결과
  ...index.m3u8?delay=1800   ->   /live/<channel-id>/index.m3u8|delay=1800   MISS -> origin
  ...index.m3u8?delay=1801   ->   /live/<channel-id>/index.m3u8|delay=1801   MISS -> origin
  ...index.m3u8?delay=1802   ->   /live/<channel-id>/index.m3u8|delay=1802   MISS -> origin
                                            (중략)
  ...index.m3u8?delay=7200   ->   /live/<channel-id>/index.m3u8|delay=7200   MISS -> origin

경우의 수 계산 ​

여기서 산수를 해보면 규모가 실감이 납니다.

캐시 키 조합 수 = rendition 수 x delay 값의 가짓수 x (매니페스트 + 세그먼트)

rendition 5개 (1080p, 720p, 480p, 360p, audio)
delay 범위 2시간 = 7,200초, 플레이어가 초 단위로 값을 만들면 7,200가지

  5 x 7,200 = 36,000 개  (마스터/미디어 매니페스트만 세어도)

3만 6천 개 키를 채우려면 각 키마다 최소 1회씩 오리진 요청이 필요합니다. 그런데 타임시프트 매니페스트는 라이브이기 때문에 TTL이 세그먼트 길이 수준(수 초)입니다. 캐시가 채워지자마자 만료되는 셈입니다. 채워도 재사용되지 않는 캐시가 되는 것입니다.

역산도 해봅시다. 관측된 294 RPS를 세그먼트 길이 6초로 나눠보면 다음과 같습니다.

294 req/s x 6 s = 약 1,760

-> 서로 다른 delay 값을 들고 매니페스트를 6초마다 갱신하는
   세션이 약 1,700 ~ 1,800개 규모라는 뜻

동시 시청자 수십만 규모의 이벤트에서 타임시프트 사용자 1,700명은 아주 작은 비율입니다. 소수의 사용자가 오리진 용량을 다 먹어치우는 구조였던 셈입니다.

request collapse가 무력화되는 조건 ​

CDN 엣지에는 보통 request collapse(같은 키에 대한 동시 MISS를 하나로 묶어 오리진에 1회만 보내는 로직)가 있습니다. 이번에도 있었고 정상 동작했습니다. 문제는 collapse가 성립하는 전제 조건입니다.

request collapse의 이득 = (같은 키를 동시에 요청하는 클라이언트 수)

라이브 본 채널:    1 키 <- 200,000 클라이언트   => 이득 200,000배
타임시프트(delay): 1 키 <- 1 ~ 2 클라이언트     => 이득 사실상 1배

주의: collapse는 요청 수를 줄이는 마법이 아니라 키가 같을 때만 효과가 있는 최적화입니다. 키를 사용자마다 다르게 만들어 놓으면 collapse, tiered cache, shield 같은 오리진 보호 장치가 전부 이론상으로만 존재하게 됩니다.

대기열, 타임아웃, 그리고 404 전파 ​

용량 초과가 어떻게 404가 되는지가 이 사건에서 가장 헷갈리는 부분입니다. 체인으로 그리면 아래와 같습니다.

delay 파라미터가 404로 번지는 경로

참고: 여기서 얻을 교훈은 404가 "없다"는 뜻이 아니라 "지금 못 찾겠다"는 뜻일 수도 있다는 점입니다. 과부하 상황에서 세그먼트나 매니페스트를 제때 조립하지 못하면 오리진은 5xx가 아니라 404로 답하는 경우가 흔합니다. 404 급증을 무조건 경로 문제나 매니페스트 문제로만 보면 진단이 30분 넘게 새기 쉽습니다. 404 급증을 볼 때는 오리진의 큐 깊이와 응답 시간 분포를 같이 봐야 합니다.

그때 한 조치와, 구조적으로 고쳐야 하는 것 ​

먼저 급한 불은 용량으로 껐습니다. 패키저 오리진과 CDN 양쪽에 리소스를 확장하고 타임시프트 동시 처리 한도를 조정해서, 해당 엔드포인트의 초당 처리량을 10,000회까지 올렸습니다. 배포 후 타임시프트 도메인은 정상화됐고 404 비율도 원래 수준으로 돌아왔습니다. 실제 정상 상태 요청량은 초당 약 300회로 안정됐습니다.

다만 용량 증설은 시간을 사는 조치일 뿐입니다. 근본 원인은 캐시 키 설계에 있습니다.

방법하는 일효과주의점
delay 값 양자화delay를 10초 또는 30초 단위로 스냅키 가짓수가 7,200에서 240 ~ 720으로 감소사용자가 느끼는 seek 정밀도 하락
캐시 키에서 delay 제외 + 매니페스트 재작성delay를 오리진 내부 window 계산에만 쓰고, 세그먼트 URL은 절대 시간 기반 공용 경로로 발급세그먼트는 라이브와 100% 캐시 공유패키저 매니페스트 생성 로직 수정 필요
tiered cache / shield 계층 추가엣지 뒤에 중간 캐시를 두고 오리진 팬인을 줄임오리진 유입 대폭 감소키가 다양하면 여기서도 MISS, 양자화와 병행해야 의미 있음
매니페스트 TTL과 세그먼트 TTL 분리매니페스트만 짧게, 세그먼트는 길게트래픽 대부분(세그먼트)이 캐시 히트매니페스트 요청 자체는 그대로 남음
오리진 동시성 한도와 큐 정책 명시한도 초과 시 대기 대신 즉시 503과 Retry-After 반환404 오진단 제거, 원인이 즉시 드러남플레이어 재시도 정책과 합의 필요

가장 효과가 큰 조합은 delay 양자화와 세그먼트 경로 공용화입니다. 매니페스트 조합은 240개 수준으로 줄고, 실제 대역폭의 99%를 차지하는 세그먼트는 라이브 캐시와 같은 키를 쓰게 됩니다. 앞선 산수에 대입하면 오리진 유입은 초당 수 회 수준으로 떨어집니다.

정리해서 원칙으로 ​

이제 정의를 붙일 수 있습니다.

cache key는 CDN이 "이 두 요청이 같은 물건인가"를 판단하는 유일한 기준입니다. 그래서 캐시 키에 무엇을 넣느냐는 캐시 정책이 아니라 오리진 용량 설계입니다. 키에 사용자별로 달라지는 값(delay, timestamp, session, token, 재생 위치)을 넣는 순간, 캐시 적중률이 아니라 오리진의 초당 요청 수가 그 값의 가짓수만큼 곱해집니다.

라이브 스트리밍에서 오리진 RPS는 시청자 수에 비례하지 않습니다. 서로 다른 캐시 키의 수에 비례합니다. 시청자 20만 명이 같은 키를 보면 오리진 요청은 초당 몇 회에 그치지만, 시청자 1,700명이 각자 다른 키를 들고 오면 초당 300회가 됩니다. 이번 사건의 전부가 이 한 문장에 담겨 있습니다.

정리 ​

  • 타임시프트 URL의 delay 파라미터가 캐시 키에 포함되어 rendition과 delay 조합이 3만 개 규모로 늘었고, 요청이 거의 전부 MISS로 오리진에 도달했습니다.
  • 오리진 처리 한도는 초당 100회였는데 실제 유입은 초당 294회, 약 2.94배 초과였습니다. 대기열 적재와 업스트림 타임아웃이 404로 표출되어 다운스트림 CDN까지 4xx와 5xx가 퍼졌습니다.
  • request collapse는 키가 같을 때만 효과가 있습니다. 사용자별로 키가 갈리면 오리진 보호 장치 전체가 무력화됩니다.
  • 처리 한도를 초당 10,000회로 올려 복구했고, 정상 상태 실측치는 초당 약 300회였습니다. 용량 산정 자체가 요청 패턴을 반영하지 못한 상태였다는 뜻입니다.
  • 근본 해결은 delay 값 양자화와 세그먼트 경로 공용화입니다. 오리진 RPS는 시청자 수가 아니라 서로 다른 캐시 키의 수에 비례한다는 점을 용량 산정의 출발점으로 삼아야 합니다.

基于 VitePress 构建 · 部署于腾讯云 EdgeOne Pages