[멀티 프로세스][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 기반 멀티 프로세스 대비)
    1. 효율적인 메모리 사용 (Copy-On-Write, COW)
      • fork()를 통해 워커 프로세스를 생성하여 COW 덕분에 메모리 절약 가능
      • 수정이 발생하지 않는 메모리는 워커 프로세스에서 부모 프로세스의 메모리를 읽기 전용으로 공유
    2. 초기 로딩 시간 절감
      • 부모 프로세스에서 어플리케이션을 preload하고 자식 프로세스에서 fork()하는 구조
      • 앱 초기화를 진행하는 spawn() 방식 대비 부모 프로세스 상태를 복사(가상 메모리 테이블 복사하며 물리적 메모리 페이지 공유, 수정이 발생하면 메모리 페이지 복제)하는 fork() 방식의 프로세스 생성 시간이 적음
    3. 프로세스 장애 관리
      • 타임아웃 설정 가능: 워커 프로세스가 일정 시간 답변 없을 경우, 마스터 프로세스가 워커 프로세스 교체
      • 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 서버 모두 멀티 프로세스 구현

멀티1image

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

멀티2

단일 프로세스 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 버킷과의 연결 풀 사이즈가 충분하지 않아서 기다린다거나 하는 부분 있을 수 있음.