P95/P99 레이턴시 이해하기
성능 측정에서 가장 많이 오해하는 지표가 **평균 응답시간(Average)**입니다. 평균은 극단적인 이상값에 의해 쉽게 왜곡되기 때문에 실제 사용자 경험을 반영하지 못합니다.
백분위(Percentile) 레이턴시란?
Section titled “백분위(Percentile) 레이턴시란?”전체 요청을 응답시간 순으로 정렬했을 때의 위치값입니다.
| 지표 | 의미 |
|---|---|
| P50 | 50%의 요청이 이 시간 안에 응답 (중앙값) |
| P95 | 95%의 요청이 이 시간 안에 응답 |
| P99 | 99%의 요청이 이 시간 안에 응답 |
| P99.9 | 99.9%의 요청이 이 시간 안에 응답 |
예시로 이해하기
Section titled “예시로 이해하기”10,000건의 요청 중 응답시간이 다음과 같다고 가정합니다.
9,500건 → 50ms 이하 400건 → 50~200ms 99건 → 200~800ms 1건 → 5,000ms (타임아웃)- 평균: 약 55ms — “빠르다”는 인상
- P95: 200ms — 실제로 사용자 5%가 200ms 이상 경험
- P99: 800ms — 1%는 0.8초 이상 기다림
레이턴시가 높아지는 주요 원인
Section titled “레이턴시가 높아지는 주요 원인”1. DB 슬로우 쿼리
Section titled “1. DB 슬로우 쿼리”-- 인덱스 없는 풀 스캔 — 데이터 증가에 따라 선형으로 느려짐SELECT * FROM orders WHERE status = 'pending';
-- 개선: 인덱스 추가CREATE INDEX idx_orders_status ON orders(status);2. N+1 쿼리
Section titled “2. N+1 쿼리”사용자 목록 조회 → 1 query각 사용자의 주문 조회 → N queries (N = 사용자 수)총 N+1 queries → 100명이면 101번 DB 호출해결: JOIN 또는 ORM의 eager loading 사용
3. 외부 API 동기 호출
Section titled “3. 외부 API 동기 호출”# 나쁜 예: 외부 API 3개를 순차 호출 → 합산 지연result_a = call_api_a() # 100msresult_b = call_api_b() # 150msresult_c = call_api_c() # 80ms# 총 330ms
# 좋은 예: 병렬 호출results = await asyncio.gather(call_api_a(), call_api_b(), call_api_c())# 총 ~150ms (가장 느린 것 기준)Grafana에서 P95 확인하기
Section titled “Grafana에서 P95 확인하기”Prometheus + Grafana를 사용 중이라면 아래 PromQL로 확인할 수 있습니다.
# HTTP 요청의 P95 레이턴시 (5분 윈도우)histogram_quantile( 0.95, rate(http_request_duration_seconds_bucket[5m]))TestForge에서 P95 확인하기
Section titled “TestForge에서 P95 확인하기”TestForge 부하테스트 결과의 핵심 지표 카드에서 p95/p99를 바로 확인할 수 있습니다. 스캔 후 자동 추출된 엔드포인트를 대상으로 실측값을 제공하므로 별도 설정 없이 사용 가능합니다.
이 가이드를 내 서비스에 직접 적용해 보세요.
TestForge 무료 스캔 시작 →