이코에코(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 |
문제 분석:
503 no healthy upstream: scan-api Pod가 과부하로 liveness probe 실패 → 재시작- HPA가 스케일아웃 되기 전에 Pod 불안정 발생
- 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 |
관찰 사항:
- VU 30으로 감소 → 95.1% 성공률 달성
- p99 응답 시간이 ~25s로 증가 (OpenAI rate limit 근접)
- 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 다음 단계
- OpenAI Batch API 적용 검토
- LangChain/LangGraph 마이그레이션 검토, run_id 단위로 큐잉 고려
- Rate Limit 대응 (Multi-Key 또는 Tier 업그레이드)
관련 문서
대시보드 정의
- /backend/workloads/monitoring/dashboards/scan-sse-pipeline.yaml
- /backend/workloads/monitoring/dashboards/domain-scan-api.yaml