백분위수
백분위수는 값이 여럿일 때 「아래쪽 몇 퍼센트가 이 값 안에 든다」를 수 하나로 집어 줍니다. 값을 작은 것부터 줄 세우고 그 줄을 백 눈금으로 나눈 다음, 몇 번째 눈금에 선 값인지를 읽습니다. 요청 백 건 가운데 아흔아홉 건이 몇 초 안에 끝났는지를 이 수로 적습니다.
쉽고 빠른 이해
백분위수는 값 묶음에서 「아래쪽 몇 퍼센트가 이 값 안에 든다」는 경계를 집어 줍니다. 요청 백 건을 재고 나서 「아흔아홉 건은 0.4초 안에 끝났다」를 0.4초라는 수 하나로 말해 줍니다.
이게 없으면 오래 걸린 쪽을 가리킬 말이 평균밖에 없습니다. 평균은 오래 걸린 몇 건을 나머지에 섞어 버려서, 늘 고르게 응답하는 서비스와 가끔 크게 늦는 서비스가 같은 수로 나옵니다.
- 값을 작은 것부터 줄 세웁니다
- 줄을 백 눈금으로 나누고 몇 번째 눈금을 볼지 정합니다
- 그 눈금에 선 값을 읽습니다
대가는 둘입니다. 눈금을 읽으려면 값을 전부 들고 있어야 합니다. 그리고 잰 건수가 적으면 뒤쪽 눈금이 한두 건에 휘둘립니다.
상세
학생 백 명을 키 작은 사람부터 줄 세웠다고 해 봅시다. 앞에서 아흔 번째에 선 학생의 키를 읽으면 그 값이 아흔 번째 백분위수입니다. 여럿을 더하고 나눠 만든 수가 아니라 누군가에게서 실제로 잰 키 하나입니다.
백분위수는 값들을 작은 것부터 늘어놓고 백 등분한 눈금 가운데 몇 번째 눈금의 값인지를 말합니다. 요청 천 건의 응답 시간을 재서 아흔아홉 번째 백분위수가 0.4초로 나왔다고 하겠습니다. 구백구십 건은 0.4초 안에 끝났다는 뜻입니다. 열 건은 그보다 오래 걸렸습니다.
줄을 몇 토막으로 자르느냐에 따라 이름이 갈립니다. 둘로 자른 한가운데 값이 중앙값이고, 백으로 자른 것이 백분위수입니다. 이렇게 줄 세운 위치의 값을 집는 수를 통틀어 분위수라고 부릅니다.
눈금이 백 개면 줄의 뒤쪽 끝을 잘게 물을 수 있습니다. 그래서 오래 걸린 쪽을 들여다보는 이야기에 백분위수가 자주 쓰입니다.
p50 · p95 · p99 표기
백분위수는 p 로 시작하는 짧은 이름으로도 적습니다. p 뒤의 수가 몇 번째 눈금인지입니다. p99 는 아흔아홉 번째 백분위수입니다. 요청 백 건 가운데 아흔아홉 건이 그 값 안에 끝났다는 뜻입니다.
셋을 한 줄에 나란히 적는 것이 흔합니다. p50 은 보통 얼마나 걸리는지를 말합니다. p99 는 가장 오래 걸린 쪽이 얼마나 뒤처지는지를 말합니다.
| 표기 | 읽는 법 | 이 값 안에 끝난 요청 |
|---|---|---|
| p50 | 쉰 번째 백분위수 | 두 건 중 한 건 |
| p95 | 아흔다섯 번째 백분위수 | 스무 건 중 열아홉 건 |
| p99 | 아흔아홉 번째 백분위수 | 백 건 중 아흔아홉 건 |
p50 은 줄의 한가운데 눈금이라 중앙값과 같은 값입니다. 같은 값에 이름이 둘 붙은 것이지 다른 셈이 아닙니다.
값을 집는 두 단계
셈은 두 단계입니다. 먼저 값을 작은 것부터 줄 세웁니다. 그다음 보고 싶은 눈금의 위치를 세어 그 값을 읽습니다. 더하기도 나누기도 없어서 결과로 나오는 것은 줄 안에 있던 값 하나입니다.
줄을 세우는 첫 단계가 정렬입니다. 건수가 많으면 백분위수를 구하는 비용은 거의 이 단계에서 나옵니다.
응답 시간 열 건을 재 봤다고 하고 눈금 셋을 읽어 봅니다.
import math
xs = sorted([0.18, 0.12, 2.10, 0.13, 0.30,
0.10, 0.25, 0.40, 0.15, 0.20])
def p(k):
return xs[math.ceil(k / 100 * len(xs)) - 1]
p(50) # 0.18 다섯 번째 값
p(90) # 0.40 아홉 번째 값
p(100) # 2.10 열 번째 값
p 는 눈금 번호를 받아 그 위치의 값을 돌려줍니다. 열 건이면 쉰 번째 눈금이 다섯 번째 값입니다.
아흔 번째 눈금은 아홉 번째 값입니다.
순서만 보는 셈입니다. 제일 오래 걸린 2.10초가 몇 배로 늘어도 p50 과 p90 의 값은 움직이지 않습니다.
위치가 정수로 떨어지지 않을 때 무엇을 돌려줄지는 하나로 정해져 있지 않습니다. 같은 열 건에서 p95 를 읽으면 위치가 9.5 입니다. 아홉 번째 값 0.40 과 열 번째 값 2.10 사이에 눈금이 섭니다. 여기서 처리가 둘로 갈립니다.
flowchart TD
A["열 건에서 p95 · 눈금 위치 9.5"]
A --> B["올림 · 뒤쪽 값을 고른다"]
A --> C["비율로 갈라 중간 수를 낸다"]
B --> D["2.10 · 줄에 있던 값"]
C --> E["0.40 과 2.10 사이 · 줄에 없던 값"]
위 코드는 올림을 골랐습니다. 비율로 가르는 방식은 줄에 없던 값을 내놓습니다. 그래서 같은 데이터라도 어느 방식으로 구했는지에 따라 값이 조금씩 다르게 나옵니다.
평균이 가리는 것
요청 백 건을 잰 결과가 아래와 같다고 하겠습니다. 거의 다 0.2초 안에 끝났습니다. 한 건만 2.1초가 걸렸습니다.
flowchart TD
subgraph 줄["짧은 것부터 줄 세운 요청 백 건 · 초"]
A["1~50번째 · 0.1"]
B["51~95번째 · 0.2"]
C["96~99번째 · 0.4"]
D["100번째 · 2.1"]
A --> B --> C --> D
end
P50["p50 이 읽는 눈금"] --> A
P95["p95 가 읽는 눈금"] --> B
P99["p99 가 읽는 눈금"] --> C
백 건의 평균은 0.177초입니다. 그런데 절반은 0.1초에 끝났습니다. 한 건은 2.1초를 기다렸습니다.
평균 하나로는 두 사실 어느 쪽도 보이지 않습니다. 평균은 모든 값을 섞어 하나로 줄이기 때문입니다.
백분위수는 섞지 않고 위치를 읽습니다. 그래서 2.1초짜리 한 건이 p50 과 p95 를 끌어올리지 못합니다. 나머지에서 멀찍이 떨어진 이런 값을 이상치라고 부릅니다.
평균에 묻히는 이 오래 걸린 쪽을 꼬리 지연이라고 부릅니다. 뒤쪽 눈금을 읽는 까닭이 이 꼬리를 숫자 하나로 집어내는 것입니다.
성능 목표를 적는 꼴
운영 약속은 평균이 아니라 백분위수로 적습니다. 「응답 시간의 평균이 0.5초 이하」로 적으면 열 건 중 한 건이 5초씩 걸려도 약속이 지켜지기도 합니다. 「아흔아홉 번째 백분위수가 0.5초 이하」로 적으면 백 건 중 아흔아홉 건이 그 안에 들어와야 합니다.
이 꼴로 쓰는 문서가 서비스 수준 목표와 서비스 수준 계약입니다. 목표 값 옆에는 두 가지를 같이 적습니다. 얼마 동안 잰 값인지와 몇 건을 재서 나온 값인지입니다. 5분씩 끊어 구한 p99 와 한 달 치를 모아 구한 p99 는 다른 수입니다.
부하 테스트 도구가 내는 리포트도 같은 꼴입니다. 요약 표에 여러 눈금을 나란히 싣고, 실패한 요청을 셈에 넣을지 말지를 따로 정합니다. 실패한 요청은 일찍 끊기거나 타임아웃까지 기다려서 값을 한쪽으로 끕니다.
여러 대의 값을 합치는 법
서버 여러 대에서 잰 p99 를 평균 내면 전체 p99 가 안 나옵니다. 백분위수는 줄 세운 위치를 읽은 값입니다. 줄이 달라지면 같은 눈금도 다른 값을 가리킵니다.
서버 A 가 잰 다섯 건이 1, 2, 3, 4, 5 라고 해 봅시다. 서버 B 가 잰 세 건은 10, 20, 30 입니다. A 의 p50 은 3 입니다. B 의 p50 은 20 입니다.
flowchart TD
subgraph SA["서버 A · 다섯 건"]
A1["1 · 2 · 3 · 4 · 5"]
end
subgraph SB["서버 B · 세 건"]
B1["10 · 20 · 30"]
end
subgraph SC["여덟 건을 합쳐 다시 줄 세운다"]
C1["1 · 2 · 3 · 4 · 5 · 10 · 20 · 30"]
end
PA["A 의 p50 · 3"] --> A1
PB["B 의 p50 · 20"] --> B1
A1 --> C1
B1 --> C1
C1 --> PC["다시 읽은 p50 · 4"]
PA --> AVG["두 p50 의 평균 · 11.5"]
PB --> AVG
두 p50 을 평균 내면 11.5 가 나옵니다. 여덟 건을 합쳐 다시 줄 세우면 p50 은 4 입니다. 두 서버는 건수가 다릅니다. 값이 퍼진 모양도 다릅니다. 그래서 생긴 차이입니다.
합치려면 원래 값을 다시 모아 줄을 세워야 합니다. 값을 다 들고 있기 어려우면 구간마다 몇 건인지를 세어 둔 히스토그램을 씁니다. 구간별 건수는 여러 대의 것을 더할 수 있습니다. 더한 뒤에 눈금을 다시 읽으면 됩니다.
건수와 흔들림
뒤쪽 눈금일수록 적은 건수에 기댑니다. 백 건을 재서 p99 를 읽으면 그 값을 떠받치는 것은 한 건뿐입니다. 그 한 건이 어쩌다 오래 걸렸으면 p99 도 같이 튑니다.
그래서 뒤쪽 눈금을 볼 때는 몇 건을 재서 나온 값인지 같이 적습니다. 건수가 적으면 여러 번 재서 값이 얼마나 흔들리는지 함께 봅니다.
쓰는 때와 안 쓰는 때
한 건 한 건의 경험을 말할 때 씁니다. 사용자는 평균 지연을 겪지 않고 자기 요청 한 건이 걸린 시간을 겪습니다. 그래서 「백 건 중 아흔아홉 건이 0.3초 안에 끝난다」처럼 눈금과 시간을 짝지어 목표를 적습니다.
합계나 용량을 셀 때는 평균이 맞습니다. 평균에 건수를 곱하면 합계가 그대로 나옵니다. 백분위수에는 그런 성질이 없습니다. 하루에 오간 데이터 양을 잡으면서 p99 를 곱하면 엉뚱한 수가 나옵니다.
값들이 한 덩이로 몰려 있을 때도 눈금을 여러 개 볼 까닭이 없습니다. 퍼진 폭이 좁으면 p50 과 p99 가 거의 같은 값이 됩니다. 이럴 때는 평균 하나로도 묶음을 충분히 가립니다.
관련 항목
등분 수로 갈린 분위수의 이름
분위수 · 중앙값 · 사분위수 · 십분위수 · 높은 분위수 · 사분위 범위
백분위수와 나란히 분포를 요약하는 통계량
평균 · 분산 · 표준편차 · 최빈값 · 분포 · 요약 통계 · 누적 분포 함수
백분위수로 재는 성능 지표
지연 · 응답 시간 · 꼬리 지연 · 처리량 · 오류율 · 초당 요청 수
백분위수를 셈하거나 어림하는 방법
정렬 · 선택 알고리즘 · 히스토그램 · t-digest · HdrHistogram · 근사 분위수
백분위수로 약속을 적는 운영 문서
서비스 수준 계약 · 서비스 수준 목표 · 서비스 수준 지표 · 에러 예산 · 경보
백분위수 값을 흔드는 요인
이상치 · 긴 꼬리 · 조율된 누락 · 표본 크기 · 측정 창 · 치우침
백분위수를 리포트로 내는 도구
다른 이름: percentile · 백분위 · 퍼센타일