2분의 내역
「영상 분석이 2분 넘게 걸린다, 워커 문제인 줄 알았는데 아니었다」에서 시작했습니다. 워커가 아닌 것이 맞았습니다 — 분석 한 편 자체가 100초 이상입니다.
단계별 실측 (RTX 3050 · 1280×726 클립)
같은 클립에서 프레임 수만 바꿔 두 번 쟀습니다.
| 단계 | 100프레임 | 300프레임 | 성질 |
|---|---|---|---|
| 프로세스 시동 + import | 3.1 | 1.8 | 고정 |
| 디코딩 | 0.5 | 0.6 | |
| 비전 모델 적재 (RT-DETR + ViTPose) | 10.7 | 10.4 | 고정 — 작업마다 다시 |
| 검출 패스 | 8.0 | 20.7 | 프레임 비례 |
| 포즈 추출 전체 | 16.2 | 35.0 | 적재(고정) + 비례 |
| 지표 추출 | 0.01 | 0.02 | |
| 판정 모델 적재 (로컬 transformers) | 7.4 | 8.8 | EC2는 vLLM 상주라 0 |
| 판정 6항목 순차 | 20.8 | 24.1 | 프레임 무관 |
| 미리보기 | 8.8 | 23.3 | 프레임 비례 |
| 합 | 72초 | 125초 |
검출은 GPU가 이미 포화라(배치 8로 묶어도 17.5초) 호출 수를 줄이지 않으면 안 줄어듭니다. 판정은 첫 항목에 워밍업이 얹혀 10.8초, 나머지는 항목당 2~3초입니다.
🔴 「원래 1분」은 회귀가 아니었습니다
출처를 찾았습니다 — 2026-09-03 EC2 한 바퀴의 총 1분 6초입니다. 그 클립이 92프레임(3.7초)이었습니다. 지금 도는 것은 창 상한을 채운 300프레임(10초)이고, 프레임에 비례하는 단계 셋이 3.3배가 됩니다.
코드 쪽 회귀도 같이 봤고 없었습니다.
DEFAULT_TARGET_FPS15→30(f2dacdc)은 그 1분 6초 측정보다 먼저입니다- 분석 창 10초(
55cbe66)는 그 뒤로 안 바뀌었고, 업로드 상한 결정은 오히려 줄이는 쪽이었습니다 - 대상 지정 2패스 재구성(
56e0ada)은 프레임당 forward 횟수가 그대로입니다 — 이전도 검출 1회 + 포즈 1회
고친 것 — 미리보기 인코딩
300프레임 미리보기 20초 중 그리기는 0.42초, 인코딩이 19.2초였습니다. OpenCV VideoWriter가 libvpx를 화질 우선 기본값으로 부르기 때문입니다. 같은 VP8/WebM을 ffmpeg에 -deadline realtime -cpu-used 8로 넘겼습니다.
| 전 | 후 | |
|---|---|---|
render_tracked_clip | 19.54초 | 2.36초 |
| 파일 | 18.45MB | 1.31MB |
| 코덱·해상도·프레임 수 | vp8 640×640 300 | 그대로 |
화질은 PSNR y 31.8dB만큼 떨어집니다 — 관절·번호·임팩트 테두리는 그대로고 배경이 조금 부드러워집니다. 업로드 바이트가 14분의 1이라 EC2 쪽 시간도 함께 줍니다.
분석 결과에는 닿지 않습니다. 미리보기는 판정이 전부 끝난 뒤에 만들고, 추가 추론 없이 이미 얻은 키포인트로 그리기만 합니다. 같은 영상을 전후로 돌려 확인했습니다 — features·skeleton·subject·keypoint_quality· frame_metrics_seconds 해시 동일, 70점 B 동일, 항목별 등급 동일, 근거 문장까지 동일. 달라진 것은 timing뿐입니다. features를 안 건드리므로 B-6 재실행도 부르지 않습니다.
검사 셋을 붙였습니다: 코덱이 VP8/WebM인지(EBML 서명을 직접 봅니다 — 확장자가 아니라), ffmpeg 없는 기계에서도 그림이 나오는지(OpenCV 경로로 떨어집니다), 인코딩 실패가 예외로 올라오는지(삼키면 리포트에 재생 안 되는 URI가 실립니다). 440 통과.
이어서 고친 둘 — 판정 동시 호출과 폴링
⑴ 판정 6항목을 동시에 던집니다 (원격/vLLM 일 때만). 항목당 2~3초가 순차로 쌓여 EC2 판정이 17초였습니다. 서버는 이미 배치를 관리하고 우리는 기다리기만 하므로, 묶어 던지면 가장 느린 항목 하나로 줄어듭니다.
🔴 점수는 이것으로 못 움직입니다. 등급은 프롬프트를 만들기 전에 코드가 정하고(grade_for), 모델이 뭐라 답하든 _validate 가 "grade": int(grade) 로 덮어씁니다. 동시 실행이 바꿀 수 있는 것은 evidence 문장뿐이고, 그건 지금도 실행마다 흔들리는 값입니다. 로컬(transformers)은 순차로 둡니다 — GPU에 모델이 하나라 얻을 것이 없고, outlines·transformers 가 여러 스레드에서 안전하다는 보장이 없습니다.
⑵ 워커 폴링 45초 → 5초. 분석이 1분대인데 그 앞에 주기의 절반이 그대로 붙어 있었습니다(평균 22.5초 → 2.5초). 빈 claim 은 로그를 남기지 않아 저널이 안 늘고, 자동 종료는 이 주기가 아니라 분석 자식 프로세스를 봅니다. claim 요청이 9배가 되는 것은 정어진 님께 미결 ho 48번으로 알렸습니다.
검사 다섯을 붙였습니다(445 통과): 동시에 도는가 · 등급이 안 움직이는가 · 항목 순서가 유지되는가 · 로컬은 순차인가 · 폴링 기본값이 대기 시간을 지배하지 않는가.
로컬 경로는 구조상 안 바뀌지만 같은 클립으로 한 번 더 확인했습니다 — features·skeleton·subject·keypoint_quality·result 전부 앞 회차와 동일.
EC2(T4 + 상주 vLLM)에서 쟀습니다 — 300프레임 한 편
인스턴스를 띄워 실측했습니다. 배포된 트리는 건드리지 않고 /tmp에 새 코드를 얹어 돌렸습니다(끝나고 지웠습니다).
| 단계 | 전 | 후 |
|---|---|---|
| 측정 (포즈 + 지표) | 38.0초 | 38.0초 (그대로) |
| 판정 6항목 | 12.64초 | 3.95초 |
| 미리보기 | 28.11초 · 18.46MB | 3.52초 · 1.32MB |
| 합 (S3 왕복 제외) | 약 79초 | 약 45초 |
| 큐 대기 (폴링) | 평균 22.5초 | 평균 2.5초 |
| 사용자 시계 | 약 100초 | 약 48초 |
판정은 순차 12.64초(중앙값, 3회)가 동시 3.95초로 3.2배입니다. 항목별로는 1.7~3.8초씩이던 것이 가장 느린 하나로 접혔습니다.
🔴 문장이 6/6 동일했습니다. 동시에 던지면 vLLM 배치 모양이 달라져 표현이 흔들릴 수 있다고 적었는데, 그리디 디코딩이라 그렇지 않았습니다 — 등급도 점수(70점 B)도 물론 같습니다. 바뀔 수 있다고 말한 것이 실제로는 안 바뀌었다는 뜻이고, 보장으로 쓰지는 않겠습니다(배치 구성은 부하에 따라 달라집니다).
남은 벽은 그대로 측정 38초입니다.
끝에서 끝까지 — S3 왕복까지 넣어 한 번 (조립값이 아닙니다)
위 표는 단계를 따로 잰 것이라, 서비스가 실제로 도는 경로(analyze_s3.py, S3에서 받아 S3에 올리는 것)로 한 번 더 돌렸습니다. 입력은 실사용자 업로드 (1080p 세로 25fps, 2.8MB)입니다.
[입력] … → 2.8MB, 0.8초
[측정] 214프레임 (실효 25.00fps), 30.3초
[판정] 백엔드 vllm, 준비 0.0초
{"fetch_s": 0.8, "measure_s": 30.26, "judge_s": 2.57, "preview_s": 2.89}
[전체 벽시계] 38.10초
38.1초입니다 — 프로세스 시동·import·업로드까지 포함한 벽시계입니다. 2026-09-03 의 1분 6초는 92프레임이었고 이번은 214프레임이니, 두 배 넘게 재면서 절반 가까이로 줄었습니다.
창을 꽉 채운 300프레임이면 측정이 약 39초라 벽시계 약 48초, 큐 대기(평균 2.5초)를 얹어 사용자 시계로 약 50초가 지금의 최악값입니다.
🔴 이 실행이 S3 reports/perf-check/… 에 리포트 한 벌을 남겼습니다(성능 측정용 접두사라 서비스 경로와 겹치지 않습니다). EC2 역할에는 지우는 권한이 없어 남겨 뒀습니다.
안 고친 것과 그 이유
- 검출 20초 — 포즈는 30fps가 필요하지만 사람 박스는 아닙니다. 호출 수를 줄이면 선택되는 박스가 달라져
features가 흔들립니다 — 사전 등록이 먼저입니다. 같이 잰 값: fp16 은 검출 19.1→14.0초 · 포즈 6.1→4.9초인데 박스가 300프레임 중 3프레임에서 갈리고(최소 IoU 0.986) 관절이 최대 0.24px 움직입니다. stride 2 는 검출이 9.6초(stride 3 이면 6.4초)로 더 크게 줄지만 박스 선택 규칙 자체가 바뀝니다. 셋 다 빨라지는 것이지 같은 답이 아닙니다 - 모델 적재 8~10초 × 매 작업 — 워커가 작업마다 별도 프로세스를 띄우는 구조의 대가입니다. 자식 격리·타임아웃이 거기 붙어 있어 지금 손댈 자리가 아닙니다
폴링 비용을 따져 봤습니다 — 45초와 5초의 차이는 월 $0.16
「요청이 9배면 AWS 요금이 많이 나오는 것 아니냐」에 숫자로 답했습니다. EC2에서 실제 바이트를 쟀습니다 — 🔴 claim 은 부르지 않았습니다. 조회가 아니라 소비라서, 같은 호스트의 무해한 경로로 전송량만 쟀습니다.
- 1회당 송신 약 2,664 B(TLS 핸드셰이크 포함). 수신은 AWS가 과금하지 않습니다
- 5초 → 1.40 GB/월 · 45초 → 0.16 GB/월 · 차이 $0.16/월(24시간 가동 가정)
- 아웃바운드 무료 한도가 월 100GB라 청구서에는 묻힐 값입니다
폴링이 건드리는 AWS 항목은 아웃바운드 전송 하나뿐입니다 — 백엔드가 우리 k3s라 요청당 과금이 없고, NAT도 안 씁니다(퍼블릭 서브넷 + IGW). 이 차이는 g4dn.xlarge를 월 15분 더 켜 두는 값이라, 대기 20초와 맞바꾸기로 하고 5초를 유지했습니다(미결 ho 48번에 근거와 다시 볼 조건을 적었습니다 — 워커를 여러 대로 늘리거나 백엔드가 요청당 과금되는 앞단으로 옮겨갈 때).
바이트가 문제가 되면 주기를 늦추는 것보다 나은 수단이 있습니다: worker.py 가 매번 새 TLS 연결을 열어서 송신의 대부분이 핸드셰이크입니다. 연결을 재사용하면 5초를 유지한 채 바이트만 5~10분의 1이 됩니다.
배포했습니다 — 그리고 설정 하나에 걸릴 뻔했습니다
EC2에서 288be71 → 5e35ae4 로 pull 하고 워커를 재시작했습니다. 배포된 venv로 검사 445 통과, 워커·vLLM 둘 다 active, 재시작 뒤 30초 관찰에 오류 없음 (빈 큐라 로그가 없는 것이 정상입니다).
🔴 /etc/supersub/worker.env 가 SUPERSUB_POLL_SECONDS=45 를 박아 두고 있었습니다. 코드 기본값만 바꿨으면 배포에서는 그대로 45초였습니다 — 재시작 로그의 폴링 5초 를 눈으로 확인하고서야 반영된 것을 알았습니다. 기본값을 고치는 변경은 배포된 설정이 그 값을 덮고 있는지까지 봐야 합니다.
일부러 하지 않은 것 둘:
uv sync를 돌리지 않았습니다. 새 코드가 쓰는 것은shutil·subprocess·concurrent.futures뿐이라 설치할 것이 없고,--extra aws없이 sync가 돌면 boto3가 빠져 워커의 S3 경로가 죽습니다. import 확인만 했습니다- 재시작 전에 워커 cgroup을 봤습니다. 분석 자식이 돌고 있었으면 그 작업이
failed로 보고되고 끝납니다 — 없는 것을 확인하고 눌렀습니다
다음에 여기서 이어갈 사람에게
남은 벽은 측정 38초 하나이고, 줄이는 수단과 대가는 위 「안 고친 것」에 숫자로 있습니다. 조사는 로컬 GPU만으로 됩니다 — 원본 39클립이 /mnt/d/supersub-phaseA/clips/ 에, 키포인트 캐시가 저장소 eval/phaseA/cache_target30/ 에 있고, 39클립 × fp32/fp16 재추출이 30~40분입니다.
🔴 미결 47번(B-6 기준선 재현 불가)에 걸리지 않습니다 — 저장된 09-08 산출과 대조하는 것이 아니라 오늘 코드끼리 두 번 돌려 비교하는 A/B라서, 47번이 풀리기를 기다릴 필요가 없습니다. 재는 것은 셋입니다: 시간 · features 12개 값의 차이 · 등급이 뒤집히는 클립 수(features만 있으면 grade_for 로 CPU에서 재판정).
이번에는 여기까지 하고 멈췄습니다 — 사전 등록까지 갈 만큼 급한 일이 아니라고 판단했습니다. 필요해지면 위 숫자를 다시 재지 말고 그대로 쓰면 됩니다.