[Dev] Pod CPU Limit과 FFmpeg Thread 최적 조정 - 3. 배경지식: 영상 처리와 FFmpeg

6 분 소요


이전 글까지 CPU 시간의 성격, CFS 스케줄러의 throttling 메커니즘, cgroup과 K8s의 리소스 관리를 살펴봤다. 이제 마지막 퍼즐 조각인 ffmpeg을 볼 차례다. ffmpeg이 영상을 어떻게 처리하고, 내부적으로 스레드를 어떻게 사용하는지 알아야, CPU limit과의 상호작용을 이해할 수 있다.

ffmpeg의 영상 처리 파이프라인과 멀티스레드 아키텍처도 파고 들면 깊은 주제다. 여기서는 CPU limit 환경에서 왜 문제가 되는지를 설명할 수 있을 정도로만 다룬다.


TL;DR

  • ffmpeg은 decode → process → encode 파이프라인으로 영상을 처리한다
  • 기본적으로 멀티스레드로 동작하며, -threads 미지정 시 시스템 CPU 코어 수 기반으로 스레드 수를 결정한다
  • ffmpeg의 speed처리한 영상 시간 / 경과한 벽시계 시간으로, 처리 효율을 나타낸다
  • ffmpeg 처리는 대부분 CPU-bound 작업이며, time 명령으로 워크로드 특성을 파악할 수 있다
  • CPU limit 환경에서 ffmpeg이 과도한 스레드를 생성하면, quota를 빠르게 소진해 throttling이 심화된다


FFmpeg이란

ffmpeg은 영상과 오디오를 변환, 인코딩, 디코딩, 필터링하는 범용 멀티미디어 프레임워크다. 이 문제에서는 영상에서 프레임을 추출하고(JPEG 이미지로 저장), 썸네일을 생성하는 데 사용하고 있다.


영상 처리 파이프라인

ffmpeg의 영상 처리는 세 단계 파이프라인으로 구성된다.

[Decode] → [Process] → [Encode]
  입력        처리        출력
단계 역할 CPU 부하
Decode 압축된 영상 데이터를 원시 프레임으로 변환 높음 (코덱에 따라 다름)
Process 스케일링, 필터 적용 등 프레임 가공 중~높음 (필터에 따라 다름)
Encode 가공된 프레임을 출력 형식으로 인코딩 높음 (코덱에 따라 다름)

이전 글에서 본 프레임 추출 명령을 이 파이프라인에 대입하면:

ffmpeg -i video.mp4 -filter_complex [0]scale=1280:720[s0] -map [s0] -q:v 2 -vsync vfr frames/frame_%06d.jpg -y
  1. Decode: H.264로 인코딩된 mp4 파일을 읽어 원시 프레임(YUV)으로 디코딩
  2. Process: scale 필터로 1280x720 해상도로 변환
  3. Encode: 각 프레임을 JPEG(품질 2)으로 인코딩하여 디스크에 저장

세 단계 모두 CPU를 많이 사용하는 작업이다.


멀티스레드 동작

순차 처리 vs. 병렬 처리

스레드가 1개라면 모든 프레임을 순차적으로 처리한다.

Thread 1: [Decode F1][Process F1][Encode F1][Decode F2][Process F2][Encode F2]...

스레드가 여러 개면 파이프라인 단계를 중첩(overlap)하여 처리할 수 있다.

Thread 1: [Decode F1] [Decode F2] [Decode F3] ...
Thread 2:            [Process F1] [Process F2] [Process F3] ...
Thread 3:                        [Encode F1] [Encode F2] ...
Thread 4:                                   [Write F1] ...

충분한 스레드가 있으면, Thread A가 Frame 10을 Decode하는 동안 Thread B가 Frame 9를 Process하고, Thread C가 Frame 8을 Encode하는 완전 중첩(full overlap)이 가능하다. 이 상태에서 모든 파이프라인 단계가 동시에 진행되어 대기 시간이 최소화된다.


-threads 옵션

ffmpeg의 병렬 처리 스레드 수는 -threads 옵션으로 제어한다.

ffmpeg -threads N -i input.mp4 ...
동작
0 (기본값) 시스템 CPU 코어 수 기반으로 자동 결정
1 싱글 스레드
N 명시적으로 N개 스레드 사용

기본값이 0(auto)이라는 점이 중요하다. ffmpeg은 별도로 옵션을 지정하지 않으면, 시스템의 CPU 코어 수를 감지하여 그에 맞는 스레드를 생성한다.

여기서 문제가 발생할 수 있다. 컨테이너 내부에서 ffmpeg이 코어 수를 감지할 때, cgroup의 CPU limit이 아닌 호스트의 물리 코어 수를 참조할 수 있다. 호스트에 20코어가 있고 Pod CPU limit이 1코어라면, ffmpeg이 20개 가까운 스레드를 만들 수 있다. 이전 글에서 본 것처럼, 1코어 quota에 많은 스레드가 동시에 돌면 quota를 순식간에 소진하고 throttling이 심화된다.

이전 글에서 작성한 코드에는 -threads 옵션을 지정하지 않았다. ffmpeg이 실제로 몇 개의 스레드를 만들어 동작하는지는 이후 실험에서 확인한다.

이 부분을 알게 되었을 때, “아차” 싶었다. 코드에서 -threads 옵션 하나 빠진 것이 이렇게 큰 성능 차이로 이어질 줄은 몰랐다. 컨테이너가 호스트의 코어 수를 “보는” 것과 “쓸 수 있는” 것이 다르다는 사실을, 뼈저리게 배웠다.


영상 처리 용어

ffmpeg의 처리 성능을 해석하려면 몇 가지 용어를 알아야 한다.

speed

speed는 ffmpeg이 영상을 얼마나 빠르게 처리하는지 나타내는 지표다.

speed = 처리한 영상 시간 / 경과한 벽시계 시간

여기서 “영상 시간”이란 영상 내부의 시간 축이다. 각 프레임에는 PTS(Presentation Time Stamp)라는 타임스탬프가 있어, 영상 속에서 언제 표시되어야 하는지를 나타낸다.

ffmpeg은 처리 중 주기적으로 현재 speed를 계산하여 표시한다.

현재 speed = 현재까지 처리한 마지막 프레임의 PTS / 현재까지 경과한 벽시계 시간

예를 들어, 60초 영상을 처리할 때 진행에 따라 speed가 다음과 같이 변한다.

처리 진행 처리한 영상 시간 (PTS) 벽시계 경과 시간 speed
20% 완료 12초 1초 12x
50% 완료 30초 3초 10x
100% 완료 60초 6초 10x

최종적으로 로그에 표시되는 speed는 마지막 프레임 PTS / 총 처리 시간이다. 즉 100% 완료 시점의 값이 최종 speed다.

처리한 영상 시간 실제 걸린 시간 speed 의미
60초 영상 6초 10x 10배속 처리
60초 영상 60초 1x 실시간 처리
60초 영상 120초 0.5x 절반 속도

파일 배치 처리 목적이라면, speed는 단순히 처리 효율이다. 높을수록 빠르게 끝난다. 실시간 스트리밍 목적이라면 speed가 최소 1x 이상이어야 하는데, 지금 다루는 문제는 배치 처리이므로 단순히 “빠를수록 좋다”고 보면 된다.


fps와 기타 용어

용어 의미 비고
fps 벽시계 1초당 처리하는 프레임 수 throughput 지표. 높을수록 빠름
codec 영상 압축/해제 알고리즘 H.264, H.265, VP9 등. 복잡할수록 디코딩 부하 증가
해상도 프레임의 가로 x 세로 픽셀 수 1280x720, 1920x1080 등. 높을수록 연산량 증가
GOP Group of Pictures. 키프레임 간격 seek 성능에 영향
FPS (원본) 영상 자체의 초당 프레임 수 30fps 영상이면 1초당 30개 프레임 존재

이 문제에서는 코덱, 해상도, GOP 등 영상 자체의 특성을 최적화하는 단계까지는 다루지 않는다. 핵심은 ffmpeg이 CPU를 얼마나 사용하는지, 그리고 그 사용 패턴을 어떻게 제어할 수 있는지다.


ffmpeg stat 해석

ffmpeg 실행 중 출력되는 진행 상태(stat)를 읽을 수 있어야 한다.

frame= 3723 fps= 32 q=2.0 time=00:02:04.25 speed=1.07x
필드 의미 이 문제에서의 해석
frame 지금까지 처리한 프레임 수 추출된 이미지 개수
fps 벽시계 1초당 처리 프레임 수 처리 throughput. throttling 시 급격히 떨어짐
q 품질 계수 q:v=2 설정값 반영 (1-31, 낮을수록 고품질)
time 처리한 영상 시간 (PTS 기준) 영상의 어느 지점까지 처리했는지
speed 영상 시간 / 벽시계 시간 처리 효율. 높을수록 빠름

throttling이 발생하면 fps가 떨어지고, speed가 낮아진다. 이 지표들의 변화를 관찰하면 CPU 제한이 ffmpeg 성능에 미치는 영향을 직접 확인할 수 있다.


ffmpeg 워크로드 특성

CPU-bound 작업

ffmpeg의 영상 처리는 대부분 CPU-bound다. 파이프라인의 각 단계를 CPU 시간 관점에서 보면:

단계 user time sys time 설명
영상 디코딩/인코딩 매우 높음 낮음 순수 CPU 연산 (DCT, 양자화 등)
파일 읽기/쓰기 낮음 높음 시스템 콜, 페이지 캐시
네트워크 처리 낮음 중간 소켓, TCP 등

디코딩과 인코딩이 전체 처리 시간의 대부분을 차지하며, 이 과정에서 user time이 급증한다.


time 명령으로 워크로드 분석

time 명령과 함께 실행하면 워크로드 특성을 파악할 수 있다.

time ffmpeg -i video.mp4 -filter_complex [0]scale=1280:720[s0] -map [s0] -q:v 2 -vsync vfr frames/frame_%06d.jpg -y

결과를 아래와 같이 해석해 볼 수 있다.

패턴 가능한 원인
user가 높다 ffmpeg 인코딩/디코딩 자체가 무거움 (코덱, 해상도, 필터 문제)
sys가 높다 디스크 I/O, overlayfs, VM 환경 문제
real만 높고 user/sys는 낮다 CPU throttling 또는 I/O wait

세 번째 패턴이 이 문제에서 핵심이 될 수 있다. throttling이 발생하면 프로세스가 강제 대기하므로, CPU가 실제로 일한 시간(user+sys)은 크지 않은데 벽시계 시간(real)만 길어진다.

이전 글에서 도출한 코어 수 추정 공식 (user + sys) / real도 여기에 적용할 수 있다. 이 비율이 Pod의 CPU limit보다 현저히 낮다면, throttling으로 인해 CPU 시간을 제대로 활용하지 못하고 있다는 뜻이다.


정리: 퍼즐 조각 맞추기

세 편의 배경지식을 종합하면, 이전 글의 37배 성능 차이에 대한 가설을 세울 수 있다.

  1. ffmpeg은 기본적으로 호스트 코어 수 기반으로 많은 스레드를 생성한다 (post 03)
  2. Pod CPU limit 1000m은 cgroup cpu.max로 변환되어 1코어 분량의 quota가 설정된다 (post 02)
  3. 다수의 스레드가 동시에 실행되면 quota를 순식간에 소진하고, 나머지 시간은 throttling으로 대기한다 (post 01)
  4. goroutine 3개가 ffmpeg 2개(프레임 추출 + 썸네일) + 업로드 1개를 병렬 실행하므로, CPU 경합이 더 심해진다 (post 00)
ffmpeg 스레드(호스트 코어 수 기반) × goroutine 2~3개
→ 1코어 quota를 순식간에 소진
→ 거의 매 period마다 throttling
→ 대부분의 시간을 대기하며 보냄
→ 4.5초 → 148초 (37배)

하지만 이것은 가설이다. I/O, 메모리, 네트워크 등 다른 요인의 영향이 없는지도 확인해야 한다. 다음 글에서는 이 가설을 실험으로 검증한다.

세 편에 걸쳐 배경지식을 정리하면서, 그동안 “대충 알고 있다”고 생각했던 것들이 실은 제대로 몰랐던 것임을 깨달았다. CPU의 시간적 성격, CFS의 bandwidth control, cgroup의 계층 구조, 그리고 ffmpeg의 스레드 모델. 하나하나는 어렵지 않은데, 이것들이 맞물려 돌아가는 모습을 이해하고 나니 비로소 문제의 전체 그림이 보였다.



hit count

댓글남기기