같은 영상 조각을 가상 사용자 50명이 요청하는데도 원본 서버가 매번 파일을 보내고 있었습니다. PokeClip의 CDN 개발 실험에서 시청자별 쿠키와 쿼리가 같은 URI의 캐시를 나누는 비교군을 만들었기 때문입니다. 공유 캐시로 바꾸려고 이 값을 제외하자 이번에는 원본의 init·segment 요청이 401로 실패했습니다. 캐시 재사용을 막던 값이 원본의 세션 확인에도 쓰이고 있었습니다.

해결하려면 캐시 객체를 구분하는 값과 원본에 전달할 값을 따로 정해야 했습니다. 수정한 구성에서 원본 미디어 요청은 1,254건 → 28건, 공유 캐시의 바이트 전송 회피율은 97.95%였습니다. 반면 미디어 HTTP p95는 약 1.4초로 비슷했습니다. 저는 비교 조건과 채택할 정책을 정했고, 실험 구성·계측·분석은 AI 협업으로 진행했습니다. 이 수치는 운영 장애 전후가 아니라 통제된 개발 실험의 결과입니다.

캐시 키를 줄이다 원본 인증까지 빼 버렸습니다

임시 AWS 실험에서는 원본 직결, CDN 시청자별 캐시, CDN 공유 캐시의 세 구성을 비교했습니다. 시청자별 캐시는 모든 쿠키·쿼리와 실험용 bench_viewer 값으로 캐시를 나눈 조건입니다. 기존 운영에서 발견한 설정 결함이 아니라 공유 여부의 차이를 보기 위한 비교군입니다.

미디어 조각의 캐시 키에서 쿠키·쿼리를 제외하면 동일 URI를 재사용할 수 있습니다. 그런데 원본 전달 정책에서도 세션 정보를 빼자 MediaMTX는 세션을 확인할 수 없어 401을 반환했습니다. 공유할 객체의 식별 조건을 줄이는 것과 원본이 요청을 받아들일 조건을 없애는 것은 다른 변경이었습니다.

그래서 AWS의 캐시 키 정책과 원본 요청 정책을 나눠 적용했습니다.

대상 수정한 정책 이유
미디어 CachePolicy 쿠키·쿼리를 키에서 제외 동일 URI의 동일 바이트 공유
OriginRequestPolicy 세션 쿠키·쿼리 전달 유지 Miss 때 MediaMTX가 세션을 확인
재생목록 TTL 0 새 조각을 알리는 내용이 계속 바뀜
미디어 조각 TTL 120초 내용이 고정된 조각 재사용
뷰어 접근 엣지의 CloudFront 서명 쿠키 검사 Hit에서도 권한 확인

공유의 전제는 동일 URI가 동일 콘텐츠를 가리킨다는 것입니다. 사용자별 쿼리가 실제 바이트를 바꾸는 서비스라면 이 값을 무조건 캐시 키에서 빼서는 안 됩니다. 또한 원본 검사는 Hit 요청에 실행되지 않을 수 있으므로 뷰어 권한을 엣지에서 확인해야 했습니다.

원본 요청은 줄었지만 p95는 거의 같았습니다

50 VU·25초·각 조건 1회에서 시청자별 캐시 원본 미디어 요청 1,254건과 공유 캐시 28건 비교

2026년 10월 5일 실험의 원본은 서울 리전 t3.small에서 실행한 MediaMTX 1.20.1 컨테이너이며, CPU 1개·384MiB로 제한했습니다. 입력은 720p30 H.264 약 1.5Mbps + AAC 128kbps, GOP 2초였고 실제 조각도 약 2초였습니다. CloudFront ICN53-P1 엣지를 거쳤으며 Origin Shield와 S3 되감기는 사용하지 않았습니다. 같은 Mac의 k6로 세 구성마다 1·10·25·50 VU를 25초·각 1회 순차 실행했습니다.

50 VU·25초 시청자별 캐시 공유 캐시
클라이언트 미디어 수신 253.00MB 263.243MB
원본 미디어 전송 253.00MB 5.387MB
원본 미디어 요청 1,254건 28건
요청 기준 캐시 적중률 0.00% 97.81%
미디어 HTTP p95 1,442.14ms 1,389.47ms

공유 캐시의 바이트 전송 회피율은 1 − 5.387 / 263.243 = 97.95%입니다. 공유 캐시 실행에서 시청자들이 받은 미디어 바이트 중 원본이 직접 보내지 않아도 된 비율입니다. 분모가 변경 전 원본 전송량 253.00MB는 아닙니다. 두 실행의 클라이언트 수신 총량이 다르므로 전후 원본 바이트 감소율과 구분했습니다. X-Cache로 센 요청 적중률 97.81%도 분모가 다른 지표입니다.

목록은 TTL 0이므로 계속 원본으로 갔습니다. 공유 캐시 50 VU에서 목록은 1,366요청·1.921MB, 미디어를 포함한 원본 응답은 7.308MB였습니다. 97.95%는 미디어 바이트 지표이며 전체 요청 감소율은 아닙니다.

12조건의 완료 HTTP는 16,214건이었고 실패와 첫 수신 뒤 관측한 순번 누락은 0이었습니다. 다만 종료 유예가 끝나 공유 캐시 실행의 마지막 반복 1개는 중단됐습니다. 초기 401 실패는 pilot 기록으로 분리하고 수정한 구성에서 정식 부하를 실행했습니다.

이 결과로 채택한 것은 원본 미디어를 반복 전송하지 않는 공유 정책입니다. 세 구성의 50 VU p95는 약 1.39~1.44초로 비슷해 지연 개선의 근거는 얻지 못했습니다. 뷰어→CDN은 HTTPS, CloudFront→EC2와 직결은 HTTP였으며 각 조건 1회라 반복 신뢰구간도 없습니다. 프로토콜·연결 차이를 캐시 효과와 분리하지 못하므로 CPU나 실제 재생 지연이 98% 좋아졌다고 해석할 수 없습니다.

이미 캐시된 파일을 권한 없이 받을 수 없어야 했습니다

캐시 적중률만 확인하면 공유 정책의 절반만 검증한 셈입니다. 원본 인증을 건너뛰는 Hit에서도 접근을 제한해야 하므로 CloudFront 서명 쿠키를 Trusted Key Group과 연결하고 동일 URI를 대조했습니다.

요청 응답 본문 확인
유효 서명, 최초 200 / Miss 373,502바이트
유효 서명, 반복 200 / Hit 길이와 SHA-256 동일
캐시 예열 뒤 서명 없음 403 접근 차단

유효한 요청은 같은 바이트를 재사용하고, 서명이 없는 요청은 캐시 예열 뒤에도 403을 받았습니다. 원본 요청을 줄이는 정책과 뷰어의 접근 제한이 함께 성립하는지 확인한 결과입니다. 사용한 임시 자원은 실험 뒤 정리했습니다.

앞선 로컬 실험은 성능 개선율이 아니라 비교의 기준선입니다

AWS 실험 전에 입력과 저장부터 분리해 확인했습니다. 네트워크 스파이크에서는 OBS의 SRT가 NLB를 지나 프라이빗 EC2에 도달하고 녹화 파일 생성 뒤 S3 Endpoint로 저장되는지 확인했습니다. 관리 접근은 SSM, dev 서버 주소는 EIP·Route 53으로 구분했습니다. 고정 커밋의 개발 환경 문서에 남긴 당시 경로이며, 자원 정리 이후의 상시 운영이나 가용성 결과는 아닙니다.

10월 5일에는 같은 Mac의 loopback에서 upstream MediaMTX 바이너리도 측정했습니다. FFmpeg가 720p·30fps H.264와 AAC를 RTMP 송출하고, k6가 영상·오디오 목록을 약 0.9초마다 읽어 새 완성 조각을 받았습니다. 부하별로 30초·1회 실행했습니다.

가상 사용자 받은 미디어 조각 미디어 HTTP p95
1 VU 32개 1.48ms
10 VU 320개 4.16ms
25 VU 800개 5.13ms
50 VU 1,600개 7.31ms

총 미디어는 2,752조각·약 565.9MB, 리다이렉트를 포함한 HTTP는 8,944건이었습니다. 실패와 첫 수신 이후 관측한 순번 누락은 0이었고, Prometheus의 10초 이동 구간 최대 송신율은 94.22Mbps였습니다. 예비 실행에서는 k6가 반복 사이 쿠키를 초기화해 인증이 끊겼으며, noCookiesReset을 켠 뒤 얻은 결과가 이 최종 기록입니다.

이는 같은 호스트에서 전달 기능이 동작하는 기준값입니다. 제 수정 전후의 개선율이나 EC2·NLB 수용량은 아닙니다. 영상 디코딩·브라우저 끊김·LL-HLS 부분 조각 지연은 측정하지 않았습니다. 스크립트와 원시 JSON, 서버 설정·바이너리 해시는 조건을 다시 대조할 수 있도록 보존했습니다.

S3 후속 실험에서는 두 번째 요청이 계속 Miss였습니다

10월 6일에는 저장 영상 경로도 따로 확인했습니다. 새 비공개 S3 객체와 OAC(always)를 사용했고, 뷰어→CloudFront와 CloudFront→S3 모두 HTTPS였습니다. 이 시험 객체에는 뷰어 인증을 적용하지 않았으므로 앞선 서명 쿠키 검증과 다른 실험입니다.

조건 관측
즉시 두 번째 전체 GET 20/20 Miss
동일 IP·1초 뒤 두 번째 GET 20/20 Miss
후속 세 번째 전체 GET 20/20 Hit, 동일 POP·본문 해시 일치
4MiB 객체의 첫 256KiB Range 10/10 HTTP 206, Content-Range·본문 일치

두 번째 요청이라는 순번만으로 Hit라고 가정할 수 없었습니다. 따라서 새 curl·HTTP/1.1, DNS를 고정한 동일 IP·POP 조건에서 최초 Miss와 실제 확인된 후속 3차 Hit 20쌍을 비교했습니다. 지표는 연결 준비 뒤 첫 바이트까지의 시간인 time_starttransfer − time_pretransfer입니다.

객체 크기 최초 Miss 중앙값 후속 3차 Hit 중앙값
256KiB 54.0ms 14.2ms
4MiB 52.9ms 15.0ms

총 110요청을 실행했고, 업로드·다운로드·추가 원본 요청을 보수적으로 예약한 총량은 1,990MiB로 2GiB 상한 안이었습니다. 첫 두 요청이 Miss인 내부 원인은 확정하지 못했습니다. 위 시간 차이는 S3 원본 전송 절감률이나 영상 재생 지연을 뜻하지도 않습니다.

이 일련의 실험에서 바뀐 것은 CDN을 바라보는 확인 순서입니다. 캐시 키를 줄인 뒤 원본 인증이 깨졌으므로 전달 정책을 분리했고, Hit를 늘린 뒤에는 무서명 요청을 차단하는지 확인했습니다. 결과에서도 요청·바이트·지연을 나눠 읽었습니다. 1,254건을 28건으로 줄인 선택을 지탱하는 근거는 적중률 하나가 아니라, 원본 로그와 바이트 대조, 캐시 뒤의 403까지였습니다.


전체 기술 기록으로 돌아가기 · 이 글의 포트폴리오 요약 PDF 20~23쪽