[멀티 프로세스][both server] 멀티 프로세스 (병목 개선 및 트러블 슈팅) - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 개요
- 리소스 사용량 식별 및 처리 시간 모니터링 결과, gpu 서버의 멀티 스레드 환경이 경합을 심화시킨 것을 확인
- gpu 서버도 멀티 프로세스 구조로 변경하여 병목 개선하고자 함
2. 속도 측정 (gpu 서버 다중 프로세스로 수정)
a. 상황: gpu 서버 vm vCPU 2 → 8개 확장
- 임베딩 기능을 cpu → gpu 서버로 이전하여 cpu 서버의 cpu 여유가 생김
- 이에 반해 gpu 서버는 이미지 로딩 및 디코딩, 임베딩 도입으로 인해 cpu 리소스를 최대로 사용 중
- 이에 아래와 같이 cpu 사양 변화시키고자 함:
- cpu 서버 vCPU: 4→2
- gpu 서버 vCPU: 2→8 (4개로 설계했으나 클라우드팀에서 8개로 확장해줌)
- cpu 사양 변화 예상 효과
- gpu 서버를 멀티 프로세스로 변환 가능: 멀티 프로세스는 cpu 개수만큼 병렬 처리 가능.
(기존에는 vCPU 2개로 멀티 프로세스를 도입해도 프로세스 간 컨텍스트 스위칭으로 cpu 경합 발생)
- gpu 서버의 vCPU 8개로 요청들의 실질적인 병렬 처리가 가능할 것.
b. gpu 서버 멀티 프로세스 도입 과정 트러블 슈팅 및 gunicorn 미도입 결정
가. 배경: cpu 서버와 동일한 방식으로 gunicorn 기반 멀티 프로세스 도입 시도 (+이유)
- gunicorn 기반 멀티 프로세스의 장점 (uvicorn 기반 멀티 프로세스 대비)
- 효율적인 메모리 사용 (Copy-On-Write, COW)
- fork()를 통해 워커 프로세스를 생성하여 COW 덕분에 메모리 절약 가능
- 수정이 발생하지 않는 메모리는 워커 프로세스에서 부모 프로세스의 메모리를 읽기 전용으로 공유
- 초기 로딩 시간 절감
- 부모 프로세스에서 어플리케이션을 preload하고 자식 프로세스에서 fork()하는 구조
- 앱 초기화를 진행하는 spawn() 방식 대비 부모 프로세스 상태를 복사(가상 메모리 테이블 복사하며 물리적 메모리 페이지 공유, 수정이 발생하면 메모리 페이지 복제)하는 fork() 방식의 프로세스 생성 시간이 적음
- 프로세스 장애 관리
- 타임아웃 설정 가능: 워커 프로세스가 일정 시간 답변 없을 경우, 마스터 프로세스가 워커 프로세스 교체
- keep-alive(마스터 수준 연결 관리): uvicorn은 keepalive 관리가 워커 수준에서 되기 때문에 한 클라이언트로부터 후속 요청이 다른 워커로 가게 되면 재연결 필요 (keep alive 효과 매우 제한적)
- max-requests: 최대 처리 요청 수를 설정하고 그후 워커 프로세스 자동 교체 → 메모리 누수 관리
나. 트러블 슈팅: gunicorn 이용 시, fork() 과정에서 cuda reinitialize 에러 발생
[[트러블 슈팅] GPU 서버 멀티 프로세스 도입 (gunicorn cuda does not allow reinitialize)](%5B%E1%84%90%E1%85%B3%E1%84%85%E1%85%A5%E1%84%87%E1%85%B3%E1%86%AF%20%E1%84%89%E1%85%B2%E1%84%90%E1%85%B5%E1%86%BC%5D%20GPU%20%E1%84%89%E1%85%A5%E1%84%87%E1%85%A5%20%E1%84%86%E1%85%A5%E1%86%AF%E1%84%90%E1%85%B5%20%E1%84%91%E1%85%B3%E1%84%85%E1%85%A9%E1%84%89%E1%85%A6%E1%84%89%E1%85%B3%20%E1%84%83%E1%85%A9%E1%84%8B%E1%85%B5%E1%86%B8%20(gun%2020f54217ba3f808c93cae3dec98901e3.md)
- 부모 프로세스에서 cuda 관련 라이브러리 임포트 후, 자식 프로세스 생성 시, 메모리 페이지 공유됨
- 부모 프로세스에서 cuda context 생성 (gpu 드라이버 레벨)
- fork() 시, 메모리 공유 및 복제는 일어나나 gpu 드라이버 레벨 공유 및 복제는 일어나지 않음
- 자식 프로세스에서 cuda 초기화가 완료되었다 인식되었지만 실제 cuda 이용 시, cuda context가 없음
- cuda context reinitialize를 시도하지만 cuda에서는 원칙상 재초기화를 허용하지 않아 에러 발생
다. 멀티 프로세스 구축: uvicorn 기반 멀티 프로세스 이용
- uvicorn 기반 멀티 프로세스는 spawn() 시스템 콜을 통해 워커 프로세스 생성
- 프로세스를 완전히 새롭게 생성하기 때문에 cuda 관련 라이브러리 임포트 및 cuda context 생성이 자식 프로세스에서 진행됨
- 자식 프로세스에 cuda가 정상적으로 초기화된 상태로 문제 없이 이용 가능
c. 속도 측정: cpu - gpu 서버 모두 멀티 프로세스 구현

작업 |
퍼센트 |
gpu |
86.17% |
cpu |
88.48% |
gpu-mem |
26.02% |
cpu-mem |
74.18% |

|
단일 프로세스 |
cpu 멀티 프로세스 |
cpu, gpu 멀티 프로세스 |
처리 시간 |
18s |
37s |
17s |
- cpu 서버만 멀티 프로세스로 수정한 경우, 지연 시간이 급증하는 것 현상을 기존 처리 시간 수준으로 되돌려둠
- gpu 서버에서 cpu, gpu 리소스를 풀로 이용하고 있는 것을 확인함
- 의문점:
- 단일 프로세스 대비 속도 향상이 없음
- 그럼에도 불구하고 gpu, cpu 리소스는 풀로 사용하고 있음
- 가설(추측):
- 단일 프로세스 환경에서는 cpu 바운드 테스크 병목 (GIL의 영향)
- 멀티 프로세스 환경에서는 i/o 바운드 테스크 병목
- gpu, cpu 리소스를 풀로 이용함에 따라 cpu 바운드 병목은 개선되었으나 i/o 바운드 테스크가
새로운 병목 지점이 됨에 따라 전체 지연속도는 개선이 없었을 것으로 예상
- 추가 테스트할 내용:
- 단일 프로세스, 멀티 프로세스 cpu, gpu 리소스 사용량, cpu 바운드 테스크 & i/o 바운드 테스크 지연 시간 테스트
- 만약 i/o 바운드 테스크가 멀티 프로세스 환경에서 새로운 병목 지점이 되었다면 CDN 도입을 앞당겨서 개선
- 사이먼 의견: 로깅을 통해 태스크별 소요 시간 파악
- 만약 i/o 바운드 자체가 느리다면 이미지 로딩(장당) 시간은 일정해야 함.
- 그러나 장당 로딩 시간 증가함.
- 이는 사진 a 로딩동안 사진 b 로딩이 기다리고 있을 가능성 시사.
- 만약 비동기 처리가 잘되어 있다면 사진 a, b는 동시에 i/o 요청되고
비슷하게 완료되어야 함.
- gcs 버킷과의 연결 풀 사이즈가 충분하지 않아서 기다린다거나 하는 부분 있을 수 있음.