태스크별 리소스 제한 방안 - 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
  • cpulimit vs cgroups 비교

    항목 cpulimit cgroups
    지원 리소스 CPU 사용률(%)만 제한 CPU, 메모리, IO, 프로세스 수 등
    제한 방식 실행 중인 PID 또는 실행할 명령어에 CPU % 제한 특정 cgroup에 속한 프로세스 전체에 리소스 제한
    제어 대상 단일 프로세스 (PID or 명령어) 그룹화된 여러 프로세스 (cgroup path)
    정확도 낮음 (샘플링 기반, 루프에 delay 넣는 방식) 높음 (커널 레벨에서 스케줄링 제어)
    OS 지원 리눅스 리눅스 (컨테이너, Kubernetes도 내부적으로 사용)
    장점 매우 간단, 한 줄로 실행 가능 강력하고 정밀함, 여러 리소스 제어 가능
    단점 메모리/IO 제한 불가능, 정확도 낮음 설정 복잡, 초기 진입 장벽 있음
    실행 방식 cpulimit -l 30 -- command cgexec -g cpu,memory:group_name command
  • cpulimit 방식의 정확도가 낮은 이유

    cpulimit프로세스의 CPU 사용량을 모니터링하면서 주기적으로 SIGSTOP / SIGCONT를 보내서 일시정지시키는 방식

    • CPU 사용률이 지정된 %를 초과하면 해당 프로세스를 잠깐 멈춤 (SIGSTOP)
    • 일정 시간 후 다시 실행 (SIGCONT)
    • 이걸 반복하면서 사용률을 **"비슷하게 유지"**시킴

    정확도가 낮은 이유

    이유 설명
    주기 샘플링 방식 짧은 주기마다 체크하면서 stop/continue를 반복하는 식이라서, 완벽하게 원하는 %로 맞추진 못함
    시스템 상태에 민감 다른 프로세스 부하, 커널 스케줄러에 따라 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. 전체 흐름

  1. FastAPI 부팅 시 launch_worker() 통해 워커 subprocess 실행 (cgexec + 리소스 제한)
  2. 각 워커는 socket path로 listen
  3. 요청이 들어오면 FastAPI가 send_request()로 해당 워커에 전달
  4. 워커는 작업 수행 후 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 정책 재활용 가능
  • 운영 안정성 확보: 워커 프로세스를 안정적으로 격리 → 전체 서비스 품질 유지