← 실험실 목록
입문 읽는 데 10분

p50과 p99

평균 응답시간은 멀쩡한데 왜 사용자는 느리다고 할까?

#p50#p99#Latency#백분위#Percentile#SLO#관측

엘리베이터 평균은 괜찮은데

아파트에 엘리베이터가 있다고 해 보세요. 관리 사무소는 “평균 대기 시간은 1분”이라고 자랑합니다.

그런데 주민 불만이 끊기지 않습니다. 대부분은 40초 만에 타는데, 100명 중 1명은 가끔 20분을 기다립니다. 평균을 내면 여전히 1분 근처입니다. 불평하는 사람은 그 1명입니다.

서버 응답시간도 같습니다. 대시보드의 평균은 빠른 요청에 가려지기 쉽습니다. 사용자가 “느리다”고 느끼는 순간은 느린 쪽 꼬리에 몰려 있습니다.

엘리베이터서버
평균 대기 1분평균 응답시간
절반의 사람이 기다리는 시간p50(느린 쪽 50%의 경계)
가장 불운한 1%가 기다리는 시간p99(느린 쪽 1%의 응답시간)

이 랩에서는 같은 평균을 가진 두 분포를 나란히 놓고, 왜 한쪽만 사용자 불만을 만드는지 눈으로 확인합니다.

직접 돌려보세요

왼쪽은 거의 모든 요청이 비슷한 분포입니다. 오른쪽은 평균을 똑같이 맞춘 채 느린 꼬리만 키운 분포입니다.

프리셋 같은 평균·긴 꼬리를 눌러 보세요. 평균 칸은 둘 다 100ms 근처인데, p99만 크게 벌어집니다.

같은 평균, 다른 꼬리
두 분포의 평균 차이는 0.0ms뿐입니다. 그런데 p99 차이는 2.0초입니다. 평균만 보면 둘 다 “괜찮아요”.

왼쪽 · 고른 분포

거의 모든 요청이 100ms

평균
100ms
p50
100ms
p99
100ms
표본
4000

구성: 빠름 100ms / 느림 100ms (0.0%)

오른쪽 · 꼬리 있는 분포

빠른 59ms · 느린 2.1초 (2%)

평균
100ms
p50
59ms
p99
2.1초
표본
4000

구성: 빠름 59ms / 느림 2.1초 (2.0%)

2%
비율을 바꿔도 평균은 100ms로 맞춥니다
2.1초
높을수록 p99만 먼저 나빠집니다

왜 이렇게 되는지, 산수로

공식은 곱셈과 덧셈입니다.

평균 ≈ (빠른 요청 × 그 비율) + (느린 요청 × 그 비율)

예를 들어 요청 100개 중 99개가 50ms, 1개가 5050ms이면:

구성계산결과
평균50×0.99 + 5050×0.01100ms
p50(가운데)대부분 50ms 구간50ms
p99(느린 쪽 1% 경계)그 1개 느린 요청5050ms

평균은 “괜찮음”입니다. p99는 “장애”에 가깝습니다. 1%만 망가져도 대시보드 평균은 멀쩡해 보일 수 있습니다.

흔한 오해

“평균이 목표 안이면 SLO 달성 아닌가요?”

평균 SLO만 보면 위 예시처럼 통과합니다. 그런데 그 1% 사용자는 장바구니·결제·로그인에서 이탈할 수 있습니다. 체감은 평균이 아니라 느린 꼬리에 가깝습니다.

“p99를 보면 너무 예민한 거 아닌가요?”

트래픽이 초당 100요청이면, p99는 초당 1요청입니다. “극소수”처럼 들려도 하루로 치면 수만 번일 수 있습니다. 서비스 규모에 따라 p95나 p99.9를 쓰기도 하지만, 평균만 보는 것보다는 항상 낫습니다.

“그럼 p50은 왜 보나요?”

p50은 “보통의 사용자”입니다. p50이 나빠지면 전체가 느린 겁니다. p99만 나빠지면 꼬리 문제(GC, 잠금, 느린 쿼리, 재시도, 원격 호출)를 의심합니다. 둘을 같이 보면 원인이 갈립니다.

챌린지

평균 한도는 지키되, 극단적인 1% 꼬리로 숫자를 속이는 해법은 통과하지 못하게 막아 두었습니다. 꼬리 높이를 직접 자르세요.

🎯 챌린지 — 평균은 지켜도 꼬리는 자르라

대시보드 평균은 100ms 이하로 유지해야 합니다. 동시에 사용자 체감인 p99는 400ms 미만이어야 합니다. 느린 요청 비율은 5%까지만 허용됩니다. 평균만 맞추려고 1%를 극단적으로 늘리는 해법은 일부러 실패하게 만들어 두었습니다.

평균 100ms 이하100ms
p99 400ms 미만5.0초
느린 요청 5% 이하1%
50ms
5.0초
1%
이론 평균
100ms
표본 p50
50ms
표본 p99
5.0초

확인 문제

Q1. 요청 99개는 50ms, 1개는 5050ms일 때 평균은?
Q2. 위와 같은 분포에서 사용자가 “느리다”고 느낄 가능성이 큰 지표는?
Q3. p50은 괜찮은데 p99만 갑자기 튀었다면 어디에 가까운 신호인가요?
Q4. 평균만 100ms로 맞추려고 1%를 극단적으로 늘리는 설계의 문제는?

⏱ 30초 리마인드

평균 ≈ 빠른×비율 + 느린×비율 · 체감은 p99
  1. 평균은 빠른 요청에 가려질 수 있다.
  2. p50은 보통의 사용자, p99는 불운한 1%의 경계다.
  3. 같은 평균이라도 꼬리 모양이 다르면 체감이 다르다.
  4. p50은 괜찮은데 p99만 나쁘면 꼬리 원인을 의심한다.
  5. SLO·알람에는 평균만 두지 말고 백분위를 함께 둔다.

현실에서는

Generic pattern은 단순합니다. 지연시간 목표를 평균이 아니라 백분위로 정하고, 꼬리를 만드는 작업(동기 잠금, N+1, 거대 페이로드, 무분별한 재시도)을 줄입니다.

예: 읽기 API 목표
  p50 < 80ms
  p99 < 300ms
평균만 100ms 미만  ← 이것만으로는 부족

구현 위치만 달라집니다.

  • TypeScript / Node.js: APM·OpenTelemetry histogram, p50/p99 패널. Promise 타임아웃과 동시성 제한이 꼬리를 만드는지 확인합니다.
  • Go: prometheus histogram quantile, 또는 hdrhistogram. p99 알람을 평균 알람보다 먼저 둡니다.
  • JVM: Micrometer/Timer percentile, GC 일시정지가 p99에 찍히는지 봅니다.
  • Kubernetes: Pod 평균 CPU는 멀쩡한데 특정 Pod만 느릴 수 있습니다. 백분위는 요청 단위로 모으는 편이 안전합니다.
  • AWS: ALB/API Gateway/CloudWatch latency percentile, Lambda duration percentile. 콜드 스타트는 평균보다 p99에 먼저 보입니다.

이 수치는 browser에서 만든 학습용 혼합 분포입니다. 실제 서비스의 p99는 측정 창, 샘플링, 히스토그램 버킷에 따라 달라지니 raw measurement로 다시 검증하세요.

다음 랩

평균에 가려진 꼬리를 보게 되었습니다. 다음은 그 꼬리가 주기적으로 한꺼번에 터지는 장면 — 캐시가 동시에 만료되며 DB로 쏟아지는 캐시 스탬피드입니다.