세마포어 대신 Task Queue 도입 테스트 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 테스트 목적

a. 가정

  • Embedding 작업에 세마포어를 설정했을 때, 전체적으로 시간 개선은 많이 생겼지만 평균-최대 소요 시간 간의 편차가 큰 편이었다.
    • VUs 30명 기준, 평균 소요시간 8.93s / 최대 소요시간 14.78s
  • 세마포어는 실행되는 스레드는 제한할 수 있지만, 순차 처리는 보장하지 못해서 그로 인해 대기 시간이 길어지는 Embedding 작업이 있었을 것으로 판단
    • 똑같이 스레드 수는 제한하되, 순차 처리를 보장할 수 있는 Task Queue를 도입하면 개선된 결과를 얻을 수 있을 것으로 예상

b. 예상 결과

  • 요청 처리 시간의 분산이 줄어들 것으로 예상한다.

2. 테스트 설계

a. 도구

  • 부하 테스트 도구: k6
  • 백엔드 서버: FastAPI
  • 실행 환경: CPU 서버, max_workers 고정(8)
  • 지표 수집: avg / p95 / max + http_req_failed 여부

b. 방식

  • 실험 1: max_workers=8, embedding에 세마포어 4 적용
  • 실험 2: max_workers=8, embedding에 worker 수가 4인 Task Queue 도입
  • 각 실험에 대해 VUs(동시 사용자)를 10, 15, 20, 25, 30까지 순차적으로 증가시키며 테스트 진행
  • 비교 지표:
    • 각 작업별 처리 시간 (embedding / categories / duplicates / quality / score)
    • 전체 요청 처리 시간 (total_duration)
    • 실패율 (http_req_failed)
    • iteration_duration을 통해 실질적 대기 시간 확인

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
    10 3.51s / 5.11s / 5.15s 157ms / 286ms / 290ms 130ms / 256ms / 258ms 138ms / 263ms / 268ms 85ms / 144ms / 148ms 4.02s / 5.31s / 5.35s 0.00%
    15 4.56s / 7.23s / 7.33s 172ms / 303ms / 332ms 154ms / 258ms / 259ms 174ms / 295ms / 330ms 108ms / 181ms / 184ms 5.17s / 7.43s / 7.52s 0.00%
    20 5.82s / 9.53s / 9.74s 158ms / 295ms / 318ms 152ms / 260ms / 320ms 157ms / 280ms / 300ms 111ms / 171ms / 199ms 6.40s / 9.74s / 9.93s 0.00%
    25 7.00s / 11.61s / 12.44s 194ms / 305ms / 330ms 190ms / 280ms / 296ms 200ms / 305ms / 331ms 121ms / 251ms / 260ms 7.70s / 11.82s / 12.65s 0.00%
    30 8.25s / 14.21s / 14.57s 193ms / 289ms / 316ms 179ms / 263ms / 285ms 187ms / 260ms / 273ms 124ms / 234ms / 264ms 8.93s / 14.44s / 14.78s 0.00%
  • 태스크큐 도입

    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)
    10 3.49s / 5.06s / 5.08s 149ms / 252ms / 274ms 137ms / 230ms / 235ms 137ms / 228ms / 233ms 94ms / 160ms / 162ms 4.00s / 5.31s / 5.34s
    15 4.62s / 7.34s / 7.41s 180ms / 289ms / 322ms 161ms / 267ms / 294ms 180ms / 273ms / 332ms 108ms / 193ms / 212ms 5.25s / 7.59s / 7.67s
    20 5.82s / 9.63s / 9.69s 194ms / 269ms / 277ms 157ms / 231ms / 267ms 180ms / 264ms / 310ms 133ms / 194ms / 235ms 6.49s / 9.89s / 10.00s
    25 6.99s / 11.71s / 12.26s 202ms / 354ms / 360ms 186ms / 330ms / 356ms 201ms / 347ms / 401ms 134ms / 278ms / 379ms 7.71s / 11.91s / 12.49s
    30 8.17s / 14.06s / 14.48s 170ms / 292ms / 348ms 163ms / 259ms / 344ms 164ms / 283ms / 348ms 146ms / 238ms / 463ms 8.81s / 14.31s / 14.78s

4. 결론: asyncio.Queue(worker=4)는 세마포어(4)와 비교해 성능상 유의미한 차이가 없다

a. Embedding 작업 시간 변화

  • avg/p95/max 기준으로 세마포어 제한 vs. asyncio.Queue 모두 VUs 증가에 따라 선형적으로 증가
  • 예를 들어, VU=30 기준:
    • 세마포어: 8.25s / 14.21s / 14.64s

    • Queue: 8.17s / 14.06s / 14.48s

      ➤ 약 1~2% 미만의 차이로, 사실상 동일한 처리 성능

b. 다른 작업 항목들 (Categories / Duplicates / Quality / Score)

  • 각 작업의 평균 처리 시간 및 분산도 거의 동일
  • 오히려 일부 VU에서 Queue 방식이 약간 더 높은 분산(p95~max)을 보이는 경우도 있음
  • Score나 Quality처럼 상대적으로 가벼운 작업에서도 크게 유리한 차이 없음

c. Total Duration

  • 전체 요청 처리 시간도 각 VU 수준에서 1~2% 이내의 미미한 차이

    → 실질적으로 사용자 경험이나 처리량에 영향을 줄 수준은 아님

d. 선택 가이드

  • 단순 제한을 원한다면 세마포어(4) 방식이 충분

    → 구현이 간단하고 효과도 안정적

  • 정교한 워커 관리(큐 기반 디버깅 등)가 필요한 경우에만 asyncio.Queue 도입 고려

    → 성능 향상보다는 구조적 제어 목적