추론 시간 부하테스트 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 테스트 목적

a. 가정

  • 현재 모든 주요 작업(embedding, categories, quality, duplicates, score)은 세마포어 없이 실행되고 있음
  • 별도의 세마포어 없이 병렬 요청이 들어오면 시스템 자원이 포화될 가능성이 있으므로, 특정 vus 이상부터는 리소스 병목이 발생하여 응답 시간과 실패율이 증가할 것

b. 예상 결과

  • VUs가 낮을 경우에는 응답 시간과 CPU 부하가 안정적으로 유지될 것
  • VUs가 증가하면 CPU 경합과 ThreadPool 포화로 인해 응답 시간이 급격히 증가
    • CPU 처리량 한계가 가까워질수록 total_duration, embedding_duration, http_req_duration의 p95, max 값이 급격히 증가할 것
  • 세마포어를 도입하지 않으면 과도한 동시 요청에서 처리 지연 및 서버 과부하 발생
    • 이로 인해 total_duration의 급증과 http_req_failed 비율 증가

2. 테스트 설계

a. 도구

  • k6 (부하 테스트 및 성능 측정 CLI 도구)
  • GCP Monitoring (CPU 사용률 확인)

b. 방식

  • 테스트 대상 API 흐름:
    1. POST /api/albums/embedding (10장의 이미지 목록 포함)
    2. 201 응답 시, 병렬로:
      • POST /api/albums/categories
      • POST /api/albums/quality
      • POST /api/albums/duplicates
    3. /categories 응답에서 images 추출 → POST /api/albums/score
  • Test 조건:
    • --vus N (1분동안 N명의 사용자가 동시에 요청)
    • vus 값을 10, 12, 14, 16, 18, 20, 25, 30, 50까지 증가시키며 병목 시점 탐색
  • 측정 항목:
    • 평균 응답 시간 (http_req_duration)
    • 단계별 요청 응답 시간 (embedding_duration, score_duration 등)
    • 전체 처리 시간 (total_duration)
    • 대기 시간 (http_req_blocked, http_req_waiting)
    • 시스템 CPU 부하 (GCP Monitoring 기준)
    • 실패율 및 dropped iterations

3. 테스트 결과

  • 결과 요약 표
    • 주요 지표

      VUs embedding(avg / p95 / max) categories (avg / p95 / max) duplicates (avg / p95 / max) quality (avg / p95 / max) score (avg / p95 / max) total (avg / p95 / max) http_req_failed iteration_duration (avg)
      10 3.71s / 5.06s / 5.95s 336ms / 1.15s / 2.61s 342ms / 1.51s / 2.68s 362ms / 1.51s / 2.64s 163ms / 472ms / 523ms 5.15s / 6.97s / 10.75s 1.33% 8.27s
      12 4.72s / 5.86s / 6.95s 631ms / 1.79s / 2.02s 629ms / 1.77s / 2.06s 627ms / 1.76s / 2.02s 218ms / 511ms / 573ms 6.82s / 9.60s / 10.29s 0% 9.52s
      16 6.16s / 9.11s / 9.21s 2.05s / 4.56s / 5.07s 2.03s / 4.51s / 5.09s 2.04s / 4.53s / 5.05s 233ms / 613ms / 753ms 12.59s / 18.24s / 19.33s 0.24% 12.57s
      18 6.56s / 9.27s / 10.45s 2.19s / 4.86s / 5.80s 2.17s / 4.86s / 5.84s 2.18s / 4.85s / 5.79s 157ms / 245ms / 249ms 13.33s / 19.28s / 21.46s 0.22% 12.98s
      20 7.30s / 9.86s / 11.41s 2.80s / 5.80s / 6.61s 2.79s / 5.75s / 6.57s 2.79s / 5.76s / 6.57s 343ms / 623ms / 699ms 16.09s / 22.48s / 24.48s 0.19% 14.52s
      25 9.18s / 13.65s / 14.80s 4.24s / 8.35s / 8.69s 4.15s / 8.32s / 8.69s 4.17s / 8.34s / 8.73s 293ms / 475ms / 514ms 22.04s / 30.89s / 31.52s 0% 17.71s
      30 10.39s / 16.54s / 17.14s 5.78s / 11.06s / 11.39s 5.72s / 11.03s / 11.38s 5.74s / 11.04s / 11.42s 299ms / 445ms / 497ms 28.17s / 39.47s / 40.23s 0.44% 20.40s
      50 16.43s / 26.98s / 28.68s 10.44s / 20.41s / 20.87s 10.40s / 20.37s / 20.83s 10.42s / 20.45s / 20.82s 527ms / 864ms / 952ms 48.70s / 68.78s / 69.88s 0.59% 31.14s
    • 자원 사용률

      항목 관측값
      최대 CPU 사용률 약 76%
      CPU 사용률 경향 VUs 수와 거의 무관, 항상 70% 이상 유지됨
      최대 메모리 사용률 약 80%
      메모리 사용률 경향 VUs 수가 증가할수록 비례 증가 (이미지 임베딩 결과 캐싱의 영향으로 추정)

a. 자원 소모 & 병목 구간

  • CPU 사용률은 이미 10 VUs에서 70% 이상 고정되어 있음 → 스레드풀 과점유의 여지 없음
  • 메모리 사용률은 점진적으로 증가하며, 캐시 적중률이 높아질수록 전체 메모리 요구량 증가
  • VU 20~25 이후부터 응답 시간이 급격히 증가하고 요청 실패가 소수 발생하기 시작함

주요 병목 요인

  • embedding 연산이 가장 오래 걸리며 → 연산 병렬화에 한계가 있음
  • 후속 API(categories, quality, duplicates)들도 캐싱 이후 CPU 자원을 많이 소모함

b. 병목 구간 포착

항목 평균 duration (s) p95 duration (s) 분석
embedding VU 10: 3.7s → VU 30: 10.4s → VU 50: 16.4s 최대 26.9s 연산 자체가 가장 오래 걸리며, 자원 한계 도달 시 급격한 지연 발생
categories VU 10: 0.29s → VU 30: 5.8s → VU 50: 10.4s 최대 20.9s 캐시 기반 조회라도 VUs가 많아지면 병렬 처리 경쟁이 심화됨
duplicates VU 10: 0.29s → VU 30: 5.7s → VU 50: 10.4s 최대 20.8s 유사도 비교 연산이 많은 태스크라 병목 발생
quality VU 10: 0.3s → VU 30: 5.7s → VU 50: 10.4s 최대 20.8s CLIP 기반 품질 추론 + blur 검사로 인해 CPU 부하 발생
score VU 10: 0.16s → VU 30: 0.3s → VU 50: 0.52s 최대 0.95s 상대적으로 가벼움. score는 병목 요소 아님
total VU 10: 5.1s → VU 30: 28s → VU 50: 48s 최대 ~69s 전체 시스템 병목이 누적되어 발생하는 결과

c. http_req_failed 비율

  • 16~25 VUs까지는 0.2% 이하의 낮은 실패율.
  • vus=30부터 실패율이 0.44%, vus=50은 **0.59%**까지 상승 → 시스템이 오류 처리 임계점에 근접.

d. 작업별 부하

  • embedding은 확실한 병목 요소 (최대 17s 이상 걸림).
  • 후속 작업(categories, duplicates, quality)도 점점 지연 → 대부분 5~10s 이상 (avg 기준).
  • score_duration은 비교적 가볍지만 함께 밀림.

4. 결론

인사이트 근거
병목은 embedding 연산과 후속 태스크가 동시에 병렬 처리될 때 가중됨 embedding avg: 16.4s (VU 50), categories/quality 등 10s+
12 vus까지는 안정적으로 유지 embedding p95 기준 ~5s, total ~7s
⚠️ 16 VUs 이상에서는 p95가 10초 이상으로 상승하며 지연 시작 total_duration p95: 18.2s (VU 16), 22.4s (VU 20)
⚠️ 25 이상에서는 병목이 심해지고 max 69초 응답 발생 total_duration max: 31s (VU 25), 40s (VU 30), 69s (VU 50)

5. 병렬 처리 제한 설정 기준 제안

작업 종류 병목 유형 제안 세마포어 크기 (기준: 30 VUs)
embedding 가장 무거움 (CPU + I/O) 3~4
categories 중간 (CPU) 6~8
duplicates 중간 (CPU) 6~8
quality 중간 (CPU) 6~8
score 가벼움 10~12
  • 이 수치는 CPU core 수, torch 스레드 설정, 메모리 상태에 따라 조정 필요

6. 세마포어가 터지지 않아도 필요한 이유

a. 처리량 대비 응답 시간 안정성 보장

항목 세마포어 없음 세마포어 도입 시
VU 10 평균 응답 5~6초 수준 (안정적) 비슷
VU 16+ 평균 응답 12~48초 급증 → 불안정 병렬 제한으로 폭주 방지 → 일정한 응답 유지 가능
  • 즉, "터지진 않더라도" 응답시간이 기하급수적으로 증가 → 사용자 체감 성능 하락

    → 세마포어는 자원을 분산해서 응답 시간 분산을 평탄하게 유지

b. 우선순위가 다른 작업 간 공정성 확보

  • 현재 구조는 embedding / categories / duplicates / quality / score 등 모두 같은 스레드풀 사용
  • 특정 순간 embedding 작업이 몰리면, 후속 경량 작업이 밀리게 됨
  • 세마포어로 태스크 종류별 병렬 개수 조절 시:
    • heavy한 embedding은 제한
    • 경량 태스크들은 여유롭게 동시 처리 가능 → 실시간성 유지
  • 예: score_duration은 0.3초 작업인데도 embedding 대기 때문에 3~5초 걸릴 수 있음
    • 세마포어 없이는 짧은 작업이 긴 작업에 인질로 잡히는 구조

c. 서버 부하 분산 및 예측 가능한 운영 가능

  • GCP 서버 기준 CPU 76%, RAM 80% 사용
  • 이 수치는 *“위험선 직전”*이며, 백그라운드 프로세스나 기타 요청에 따라 폭주 가능
  • 세마포어는 "고삐" 역할:
    • 순간적인 요청 몰림을 자체적으로 제어
    • 스케일 아웃 없이도 안정성 확보

d. 오류율 관리 및 장애 예방

  • VU 30 이상부터 HTTP 422, 5xx 발생률 증가
  • 이들은 대부분 과도한 병렬 처리 → 캐시 미적용/DB 지연/락 경합에서 발생
  • 세마포어는 장애를 예방하고 평균 실패율을 낮추는 가장 간단한 컨트롤 수단

7. 결론 요약

항목 세마포어 없이도 가능? 세마포어 있으면 더 좋은 점
시스템이 터짐? ❌ 아니었음 ✅ 더 안정적
응답 시간 ❌ 급증함 (VU↑) ✅ 예측 가능하게 유지
경량 작업 보장 ❌ embedding에 밀림 ✅ 공정하게 처리됨
확장성 ❌ 제한적 ✅ 동시성 제어 가능
오류율 ⬆️ VU 30~50에서 증가 ✅ 줄일 수 있음