태스크별 리소스 제한 방안 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 리소스(CPU, MEM) 사용량 제한 고려하게 된 배경
a. 테스크별 CPU, MEM 사용량 측정
가. 리소스 사용량 측정 환경
-
맥북 pro M2
하드웨어 개요: 모델명: MacBook Pro 모델 식별자: Mac14,9 모델 번호: MPHE3KH/A 칩: Apple M2 Pro 총 코어 개수: 10(6 성능 및 4 효율) 메모리: 16 GB 시스템 펌웨어 버전: 11881.41.5 OS 로더 버전: 11881.41.5 일련 번호(시스템): DM4YW6FW59 하드웨어 UUID: ADC5E727-17C6-5934-9CD2-448B6A143102 UDID 권한 설정: 00006020-000C286E36E8C01E 활성화 잠금 상태: 활성화됨
나. 리소스 사용량 측정 결과(이미지 100장 기준)
| 테스크 | avg cpu(%) | peak cpu(%) | avg mem(MB) | peak mem(MB) | 소요 시간 | 비고 |
|---|---|---|---|---|---|---|
| 인물 분류 | 403.77% | 583.64% | 1456.73 MB | 2541.73 MB | 14.83s | |
| CLIP 임베딩 | 594.92%(118.60%) | 653.28%(137.16%) | 1510.26 MB(1454.32 MB) | 1621.06(1508.28) | 1.86s(2.25s) | ()는 스레드 개수 제한 전 |
| 중복 판별 | 101.26% | 101.26% | 254.98 MB | 254.98 MB | 0.01s | 너무 짧은 시간이라 avg, peak의 의미가 없음 |
| 품질 측정 | 886.56% | |||||
| (100.70%) | 886.56% | |||||
| (100.70%) | 238.15 MB | |||||
| (233.95 MB) | 238.15 MB | |||||
| (233.95 MB) | 0.01s | ()는 스레드 개수 제한 전 | ||||
| 태깅(카테고리 분류) | 868.60% | |||||
| (100.18%) | 868.60% | |||||
| (100.18%) | 249.66 MB(246.85 MB) | 249.66 MB(246.85 MB) | 0.01s | ()는 스레드 개수 제한 전 | ||
| 하이라이트 점수 | 102.44% | 102.44% | 239.94 MB | 239.94 MB | 0.01s |
2. 리소스 사용량 제한 환경 1 : GCP VM
a. OS 레벨 제어 방식(cpulimit, cgroups)
- 운영체제(Linux)에서 CPU 자원 할당 제한. 프로세스 단위로 실행 시 리눅스에서 해당 PID에 대해 제한 적용 가능.
가. cpulimit
- 특정 프로세스를 실행할 때
CPU 사용률 (%)을 제한하는 툴
cpulimit -l 30 -- python tag_task.py
나. cgroups
- 리눅스의 리소스 제어 그룹. CPU 외에도 메모리, IO 등 여러 자원에 정밀한 제한 가능
cgcreate -g cpu:/tagging
cgset -r cpu.cfs_quota_us=50000 tagging
cgexec -g cpu:tagging python tag_task.py
-
cpulimitvscgroups비교항목 cpulimitcgroups지원 리소스 CPU 사용률(%)만 제한 CPU, 메모리, IO, 프로세스 수 등 제한 방식 실행 중인 PID 또는 실행할 명령어에 CPU % 제한 특정 cgroup에 속한 프로세스 전체에 리소스 제한 제어 대상 단일 프로세스 ( PIDor 명령어)그룹화된 여러 프로세스 (cgroup path) 정확도 낮음 (샘플링 기반, 루프에 delay 넣는 방식) 높음 (커널 레벨에서 스케줄링 제어) OS 지원 리눅스 리눅스 (컨테이너, Kubernetes도 내부적으로 사용) 장점 매우 간단, 한 줄로 실행 가능 강력하고 정밀함, 여러 리소스 제어 가능 단점 메모리/IO 제한 불가능, 정확도 낮음 설정 복잡, 초기 진입 장벽 있음 실행 방식 cpulimit -l 30 -- commandcgexec -g cpu,memory:group_name command -
cpulimit방식의 정확도가 낮은 이유cpulimit는 프로세스의 CPU 사용량을 모니터링하면서 주기적으로SIGSTOP/SIGCONT를 보내서 일시정지시키는 방식- CPU 사용률이 지정된 %를 초과하면 해당 프로세스를 잠깐 멈춤 (
SIGSTOP) - 일정 시간 후 다시 실행 (
SIGCONT) - 이걸 반복하면서 사용률을 **"비슷하게 유지"**시킴
정확도가 낮은 이유
이유 설명 주기 샘플링 방식 짧은 주기마다 체크하면서 stop/continue를 반복하는 식이라서, 완벽하게 원하는 %로 맞추진 못함 시스템 상태에 민감 다른 프로세스 부하, 커널 스케줄러에 따라 CPU 점유율 제어가 오락가락할 수 있음 정밀 제어 불가 특정 코어 제한, 멀티코어 환경에서 세부 스케줄 제어 불가 - CPU 사용률이 지정된 %를 초과하면 해당 프로세스를 잠깐 멈춤 (
-
cgroups방식의 정확도가 높은 이유- Linux 커널이 직접 스케줄링 단계에서 CPU/메모리/I/O를 제한
- 컨트롤 그룹(cgroup)에 속한 모든 프로세스의 자원 사용 총합을 감시하고, 할당량 내에서만 실행하게 함
이유 설명 커널 수준에서 제어 유저 공간에서 stop/start 시키는 게 아니라, 커널이 직접 스케줄링 단에서 자원 할당량을 제어함 정해진 주기(cfs_period) + 할당량(cfs_quota) 기반 예: 100ms 단위로 20ms만 실행 허용 (정확한 제한 가능) 다양한 리소스 제어 가능 CPU core, mem, IO, process 수 등 다양한 제어 가능 -
파이썬 내부 병렬 처리(multiprocess, subprocess)는 리소스 사용량 제한이 불가
- ” 파이썬의 ‘multiprocessing’모듈은 프로세스당 CPU 코어 수를 직접 지정하는 기능을 제공하지 않는다.
- CPU 코어의 할당과 스케쥴링은 운영 체제의 작업이며, ‘multiprocessing’라이브러리는 이러한 낮은 수준의 자원 관리에 직접 개입하지 않는다. ” (동시에 실행 가능한 자식 프로세스 수 제한만 가능)
3. 리소스 사용량 제한 환경 2 : Docker/Kubernetes
a. Docker / Kubernetes 는 내부적으로 cgroups 사용
-
Docker 컨테이너 실행 시
-cpus,-memory옵션 → 자동으로 해당 컨테이너 전용 cgroup 생성 -
Kubernetes에서
resources.limits.cpu지정 → kubelet이 cgroups에 설정-
명령어:
docker run --cpus="0.5" --memory="512m" my-app -
내부 동작:
cgcreate -g cpu,memory:/docker/container-id cgset -r cpu.cfs_quota_us=50000 ...
-
-
컨테이너나 쿠버네티스 환경에서의 리소스 제한은 사실상 cgroups를 사용임.
4. 결론 : cgroups 기반 리소스 제한
a. 상황 (프로세스 단위로 자원 제어 필요)
- 현재 FastAPI 서버는 GCP VM에서 실행되며, 동시에 다수의 리소스 집약적 태스크가 요청될 수 있는 상황
- 각 태스크는 고유의 CPU/MEM 사용 특성이 있으며, 제한 없이 병렬 실행 시 GCP VM 전체의 성능 저하 또는 OOM(Out Of Memory) 위험
b. cpulimit 방식은 부적절
- CPU만 제한 가능하고,
- 제한 방식이 비정밀하며 (SIGSTOP/SIGCONT 기반),
- 메모리 제한이 불가능하므로
운영 안정성을 보장하기 어렵다.
c. cgroups는 커널 레벨에서 자원을 정밀하게 제한할 수 있는 유일한 수단
- CPU, 메모리, IO 등 다양한 자원을 조합하여 제한 가능
- 태스크를 별도 cgroup에 묶고, 해당 그룹 단위로 자원을 제어할 수 있어 유연함
- 추후 Docker/Kubernetes 환경으로 전환하더라도 동일한
cgroups기반으로 작동 → 이식성/확장성 뛰어남
5. 테스크별 리소스 제한 설계 (1차안)
| 테스크 그룹 | CPU 제한 (core 수) | 메모리 제한 (MB) | 설명 |
|---|---|---|---|
| 공통 워커 (분류/중복/품질/태깅 등) | 1 core |
1024MB |
메시지 큐 기반 직렬 처리 워커 1개 |
| CLIP 임베딩 | 2 cores |
2048MB |
이미지 배치 임베딩 고부하 태스크 |
a. cgroups 적용 예시
가. 태스크별 그룹 생성 예시
sudo cgcreate -g cpu,memory:/clip_embed
sudo cgcreate -g cpu,memory:/face_recog
...
나. 리소스 제한 설정 예시
# CLIP 임베딩: 2코어, 1.8GB
sudo cgset -r cpu.cfs_quota_us=200000 clip_embed
sudo cgset -r memory.limit_in_bytes=$((1800*1024*1024)) clip_embed
# 인물 분류: 1코어, 512MB
sudo cgset -r cpu.cfs_quota_us=100000 face_recog
sudo cgset -r memory.limit_in_bytes=$((512*1024*1024)) face_recog
다. 실행
cgexec -g cpu,memory:clip_embed python tasks/clip_embed.py
cgexec -g cpu,memory:face_recog python tasks/face_recog.py
- 또는 Python
subprocess로 실행 시:
subprocess.run([
"cgexec", "-g", "cpu,memory:clip_embed",
"python", "tasks/clip_embed.py"
])
b. 전체 구조 요약
[GCP VM]
│
├── FastAPI 서버 (메인 프로세스)
│ └─ /clip 요청 → CLIP 워커에 요청 전달
│ └─ /face 요청 → FACE 워커에 요청 전달
│
├── CLIP 워커 프로세스 (cg: clip_worker, CPU 2, MEM 1.5GB)
├── FACE 워커 프로세스 (cg: face_worker, CPU 1.5, MEM 1.0GB)
├── ...
- VM에서 FastAPI만 포그라운드로 실행
- 나머지 태스크 워커는 백그라운드 subprocess + cgroups로 리소스 제한
- 요청은 소켓(UNIX Domain Socket) 으로 전달 (빠르고 경량)
c. 프로젝트 구조
app/
├── main.py # FastAPI 엔트리
├── worker/
│ ├── manager.py # 워커 프로세스 관리
│ ├── ipc.py # 소켓 기반 요청/응답
│ └── workers/
│ ├── clip_worker.py # CLIP 태스크
│ └── face_worker.py # FACE 태스크
d. 전체 흐름
- FastAPI 부팅 시
launch_worker()통해 워커 subprocess 실행 (cgexec + 리소스 제한) - 각 워커는 socket path로 listen
- 요청이 들어오면 FastAPI가
send_request()로 해당 워커에 전달 - 워커는 작업 수행 후 JSON 응답 반환
6. 향후 Docker 전환 시 확장 전략
| 항목 | 현재 (GCP VM) | 전환 후 (Docker) |
|---|---|---|
| 실행 방식 | cgexec + python script |
docker run --cpus=... --memory=... |
| 리소스 제어 | cgroups 수동 구성 |
Docker가 내부적으로 cgroups 사용 |
| 관리 대상 | 프로세스 (PID) | 컨테이너 단위 (논리적 서비스 단위로 묶기 쉬움) |
| 이점 | 초기 구성 단순 | 운영 자동화, 이식성, 배포 최적화 |
a. 기대 성능 지표 (Expected Performance Metrics)
-
최적화된 리소스 제한 정책 적용(cgroups 기반)을 통해 다음과 같은 성능 개선 및 시스템 안정성을 기대
지표 항목 기준값 (최적화 전) 기대값 (최적화 후) 설명 CPU 사용률 (Peak) 최대 718.7% (인물 분류 기준) 최대 200~300% 내로 제한 고성능 코어 2~3개만 사용하여 과도한 부하 방지 메모리 사용량 (Peak) 최대 1.7GB (CLIP 임베딩 기준) 개별 태스크당 한도 제한 메모리 누수 및 OOM 방지 전체 시스템 Load Average Load spike > 8.0 발생 평균 2.0~3.0 이내 유지 리소스 경합 최소화, 병렬 요청 수용 능력 확보 OOM 에러 발생률 비정상 종료 위험 존재 0%로 감소 메모리 제한 적용으로 OOM 차단 요청 처리 실패율 높음 감소 제한된 리소스 내에서 안정적인 처리 보장 응답 시간 표준편차 요청 수에 따라 지연 편차 큼 변동성 최소화, 평균 ±10% 내 유지 워커 간 부하 균형 최적화 워크로드 동시 처리량 2~3개 테스크 동시 처리 시 한계 5개 이상 병렬 처리 가능 워커별 리소스 격리로 확장성 향상
b. 기대 효과 요약
- 서버 다운 방지: 리소스 초과 사용으로 인한 전체 서버 장애 예방
- 성능 예측 가능성 증가: 태스크 실행 시간, 시스템 부하의 표준화
- 스케일 전략 유연화: 추후 Docker 기반 환경 전환 시에도 동일한 cgroups 정책 재활용 가능
- 운영 안정성 확보: 워커 프로세스를 안정적으로 격리 → 전체 서비스 품질 유지