이코에코(Eco²)/Message Queue

이코에코(Eco²) Message Queue #13: Scan API 성능 측정 (SSE + Celery Chain + Gevent)

mango_fr 2025. 12. 25. 04:57

 

 
Celery Chain + Gevent Pool 기반 비동기 아키텍처 전환 후 실제 성능을 측정하고, HTTP 1.1 + gRPC + asyncio와 비교합니다.

항목 내용
테스트 일시 2025-12-25 02:16 ~ 04:35 (KST)
테스트 도구 k6 (JavaScript 기반 부하 테스트)
대상 엔드포인트 /api/v1/scan/classify/completion (SSE)
모니터링 Prometheus + Grafana (Scan SSE Pipeline 대시보드)

1. 테스트 환경

1.1 Queueing 아키텍처

1.2 Worker 설정

scan-worker gevent 100 1~5
character-match-worker gevent 50 1~4
character-worker gevent 50 1~2
my-worker gevent 50 1~2

1.3 k6 테스트 스크립트

export const options = {
  stages: [
    { duration: "60s", target: 34 },  // 0 → 34 VU ramp-up
    { duration: "90s", target: 34 },  // 34 VU 유지
    { duration: "30s", target: 0 },   // ramp-down
  ],
  thresholds: {
    http_req_failed: ["rate<0.3"],       // 실패율 30% 미만
    sse_total_duration: ["p(95)<30000"], // 95%가 30초 이내
    sse_ttfb: ["p(95)<8000"],            // TTFB 95%가 8초 이내
  },
};

2. 테스트 결과

2.1 테스트 케이스 요약

1 02:16:01 ~ 02:17:45 10 100% 100% 링크
2 02:59:01 ~ 03:00:12 34 82.8% 100% 링크
3 04:31:59 ~ 04:34:45 30 95.1% 100% 링크

k6 vs Grafana 불일치: k6에서 503 에러는 Istio/Envoy 레벨에서 no healthy upstream 발생.
Grafana는 Celery 레벨 성공률 측정 → Worker가 처리한 Task는 100% 성공.


2.2 테스트 #1: 저부하 (k6, VU 10, non-HA)

Grafana 메트릭

https://snapshots.raintank.io/dashboard/snapshot/QB6LdIZwjFLXiNVQq1xljAQ3vnVT77a3

Chain Avg Duration 11.3s
TTFB (p50) 1.74s
Success Rate 100%
RPS 0.10 req/s
Active Connections (max) ~10

스테이지별 평균 소요 시간:

  • vision: ~4.5s (OpenAI Vision API)
  • rule: ~0.3s (DB 조회)
  • answer: ~4.8s (OpenAI Chat API)
  • reward: ~1.7s (Character Match + DB)

2.3 테스트 #2: 고부하 (k6, VU 34)

 
Grafana 메트릭

https://snapshots.raintank.io/dashboard/snapshot/ULL5FNeft8dN9yU25MYyKyEoOrMxbagk

Chain Avg Duration 19.0s
TTFB (p50) 1.07s
Success Rate 90.7%
RPS 1.10 req/s
Active Connections (peak) 25

문제 분석:

  1. 503 no healthy upstream: scan-api Pod가 과부하로 liveness probe 실패 → 재시작
  2. HPA가 스케일아웃 되기 전에 Pod 불안정 발생
  3. Celery Worker는 정상 처리 (Grafana Success 100%)

2.4 테스트 #3: 안정화 (k6, VU 30)

Grafana 메트릭

https://snapshots.raintank.io/dashboard/snapshot/FqFEJTRNVC3G9SQbH0DWc2EluC7RlFzU
 

Chain Avg Duration 21.1s
TTFB (p50) 1.32s
Success Rate 100%
RPS 0.27 req/s

관찰 사항:

  1. VU 30으로 감소 → 95.1% 성공률 달성
  2. p99 응답 시간이 ~25s로 증가 (OpenAI rate limit 근접)
  3. Reward Null 73.2%: 캐릭터 매칭 로직 이슈 (별도 수정 필요)

3. 버전별 Scan API 성능표

3.1 Scan API v1.0.0 (asyncio.to_thread, only HTTP 1.1)

테스트 결과 (2025-12-08):

5명 10s 15s 100%
10명 11s 16s 100%
100명 - 150s+ 0%

병목 분석:

  • GIL로 인한 CPU 처리 병목
  • 스레드풀 크기 제한 (2+4 = 6 concurrent)
  • GPT I/O가 70~80% 점유

3.2 /scan/classify (HTTP 1.1+gRPC, v1.0.7) vs /scan/classify/completion (SSE, v1.0.7)

동시 처리 60명 (스레드 2+4, HPA 1-4), Success 98+% 30 greenlets (HPA 1-4), 100%
평균 응답 시간 10~22초 11~21초
성공률 98.3% 95.1% (50명 기준 성공률 38.7%)
메모리 사용 4GB ~1GB (gevent)
스케일링 HPA 자동 (1-4) HPA 자동 (1-4)
병목 GIL + 스레드풀 SSE 연결 부하, Celery Task
Grafana Snapshot p99 10-19.4s, avg 6s, CPU 0.04, Memory 174-300MB p99 49s, avg 13s, CPU 0.25, Memory 512MB+

4. 핵심 지표 분석

4.1 스테이지별 소요 시간

vision 4.5s 6~10s 40% OpenAI Vision API
rule 0.3s 2~3s 5% PostgreSQL 규칙 조회
answer 4.8s 8~15s 45% OpenAI Chat API
reward 1.7s 3~5s 10% 캐릭터 매칭 + DB

소요 시간 비율:

       vision (40%)         rule(5%)      answer (45%)       reward(10%)
├────────────────────────────┼────┼─────────────────────────────┼───────┤
│█████████████████████████████│████│█████████████████████████████│███████│
└────────────────────────────────────────────────────────────────────────┘
0s                          5s      6s                         11s     13s

핵심 병목: OpenAI API 호출 (vision + answer)이 전체 소요 시간의 85% 차지.


4.2 TTFB 분석

저부하 (VU 10) 1.74s 5.7s 정상
고부하 (VU 34) 1.07s 3.4s 빠름 (OpenAI 캐시 히트 추정)
안정화 (VU 30) 1.32s 16.3s OpenAI 지연

TTFB = 첫 번째 SSE 이벤트 도착 시간 (vision task 시작 시점)


5. 개선 방향

OpenAI 동기 호출 OpenAI Batch API (50% 비용 절감)
Rate Limit 500 RPM Tier 업그레이드 or Multi-Provider
scan-api HPA CPU 70% startupProbe 조정, 초기 replica 증가
캐시 규칙 DB 조회 Redis 캐싱 (rule 단계 최적화)
Celery Chain 동기식 Task Chain, 큐 부하 LangChain/LangGraph로 경량화, run_id 단위 큐잉

6. 결론

6.1 성과

  • prefork 대비 안정성: 30명 초과 부하에서 0% → 95% 성공률
  • 메모리 효율: 4GB → 1GB (75% 절감)
  • 자동 스케일링: HPA 기반 탄력적 확장

6.2 한계

  • OpenAI Rate Limit: LLM IO 바운드가 상한, 보다 높은 Tier 혹은 멀티 프로바이더 재고
  • TTFB 증가: 부하 증가 시 p95 16s까지 상승
  • 큐잉 경량화 필요: LLM 체이닝을 Celery Chain으로 풀어 Task(동기) 및 큐잉 부하가 큼, LangChain/LangGraph로 경량화 필요
  • SSE 연결 부하 비용 높음: 기존 HTTP 1.1에 비해 스트리밍 방식인 SSE의 부하가 큶. 실시간 단계별 Event 전송으로 UX 상승 여력 존재.

6.3 다음 단계

  1. OpenAI Batch API 적용 검토
  2. LangChain/LangGraph 마이그레이션 검토, run_id 단위로 큐잉 고려
  3. Rate Limit 대응 (Multi-Key 또는 Tier 업그레이드)

관련 문서

대시보드 정의

  • /backend/workloads/monitoring/dashboards/scan-sse-pipeline.yaml
  • /backend/workloads/monitoring/dashboards/domain-scan-api.yaml