부하 테스트 · 백분위를 합치는 법 · 실습
구간별 p95 를 평균했더니 진짜 p95 보다 108밀리초 높았다
목표
백분위를 두 가지 방법으로 손수 계산해 값이 갈리는 것을 확인하고, 구간별 p95 의 단순 평균과 가중 평균이 전체 p95 와 어떻게 어긋나는지 재고, 히스토그램 버킷을 더해 합치는 방법과 그 오차를 잰 뒤, 실제 부하 실행 두 번의 결과를 올바르게 합치는 도구를 만들어 제출합니다.
왜 중요한가
백분위는 평균과 달리 부분에서 전체를 만들 수 없다. 평균은 합과 개수로 되어 있어 부분의 합을 더하면 전체가 되지만, 백분위는 정렬된 위치라서 부분의 위치를 더해도 전체의 위치가 되지 않는다. 그런데 대시보드와 보고서는 구간별 p95 를 아무렇지 않게 평균한다. 그 값은 참값보다 높을 수도 낮을 수도 있어서 '평균이면 안전한 쪽으로 틀리겠지' 라는 직관도 통하지 않는다. 여기에 계산법까지 둘이라, 도구를 바꾸면 아무 일도 없었는데 p95 가 나빠진 것처럼 보인다. 옳게 합치는 길은 원본을 다시 정렬하거나 히스토그램 버킷을 더하는 것뿐이고, 버킷으로 가면 경계가 오차를 정한다. 이 세 가지를 손으로 한 번 해 보면, 다음부터는 원본 출력 파일을 버리지 않게 된다.
단계
1. /opt/lab/lt/lt-percentiles/ 에 구간별 지연 표본이 세 개 있습니다(한 줄에 밀리초 하나). /root/lt-percentiles/shards.tsv 를 만드세요. 세 줄이고 각 줄은 탭으로 나눈 세 칸 <파일이름> <표본 수> <p95> 입니다. 이름은 shard-a · shard-b · shard-c 순서로 쓰고, p95 는 가장 가까운 순위(정렬한 뒤 ceil(0.95 x n) 번째 값)로 구해 소수 첫째 자리까지 적습니다. 그리고 /root/lt-percentiles/01-note.txt 에 total_n=<세 파일의 표본 수 합> 과 slowest=<p95 가 가장 큰 구간 이름> 두 줄을 적으세요.
2. /root/lt-percentiles/pct.py 를 만드세요. python3 pct.py <표본파일> <분위> <nearest|linear> 로 부르면 백분위 하나를 소수 넷째 자리까지 한 줄로 찍습니다. nearest 는 ceil(분위 x n) 번째로 작은 값을 그대로 쓰고, linear 는 h = (n - 1) x 분위 자리를 앞뒤 표본 사이에서 비례로 만듭니다. 그 도구로 /opt/lab/lt/lt-percentiles/tiny.txt(20줄)를 재서 /root/lt-percentiles/methods.tsv 에 세 줄을 적으세요. 각 줄은 탭으로 나눈 세 칸 <분위> <nearest> <linear> 이고 분위는 0.50 · 0.95 · 0.99 순서, 값은 소수 넷째 자리까지입니다. 그리고 /root/lt-percentiles/02-note.txt 에 gap_p95=<0.95 에서 linear 빼기 nearest> 한 줄을 소수 넷째 자리까지 적으세요.
3. /root/lt-percentiles/combine.txt 에 다섯 줄을 적으세요. mean_of_p95= 는 1단계에서 구한 세 p95 의 단순 평균, weighted_mean_of_p95= 는 표본 수를 가중치로 쓴 평균, true_p95= 는 세 파일의 원본을 모두 합쳐 가장 가까운 순위로 다시 구한 p95 입니다. 이어서 error_mean= 에 mean_of_p95 빼기 true_p95, error_weighted= 에 weighted_mean_of_p95 빼기 true_p95 를 적습니다. 다섯 값 모두 소수 첫째 자리까지, 음수면 앞에 빼기 기호를 붙입니다.
4. /root/lt-percentiles/bound.tsv 에 두 줄을 적으세요. 각 줄은 탭으로 나눈 세 칸 <이름> <표본 수> <p95> 이고, 첫 줄은 이름이 all(세 구간을 모두 합친 것), 둘째 줄은 no-c(shard-a 와 shard-b 만 합친 것)입니다. p95 는 가장 가까운 순위로 소수 첫째 자리까지. 그리고 /root/lt-percentiles/04-note.txt 에 세 줄 min_shard_p95= · max_shard_p95= · inside=<yes|no> 를 적으세요. inside 는 all 의 p95 가 구간별 p95 의 최솟값과 최댓값 사이에 있으면 yes 입니다.
5. /root/lt-percentiles/hist.py 를 만드세요. python3 hist.py <경계를 쉼표로> <표본파일...> 로 부르면 탭 두 칸짜리 표를 찍습니다. 각 줄은 <경계> <그 경계 이하인 표본의 누적 개수> 이고 마지막 줄은 +Inf <전체 개수> 입니다. 파일을 여럿 주면 파일마다 센 뒤 같은 경계끼리 더합니다. 이 도구로 경계 50,100,250,500,1000 와 세 구간 파일을 모두 주어 나온 표를 /root/lt-percentiles/hist-coarse.tsv 에 저장하세요. 그리고 /root/lt-percentiles/hist-p95.txt 에 세 줄 est_p95= · true_p95= · abs_error= 를 소수 첫째 자리까지 적습니다. est_p95 는 그 표에서 선형 보간으로 추정한 p95 입니다 — 누적 개수가 0.95 x 전체 이상이 되는 첫 경계를 찾아, 앞 경계와 그 경계 사이를 누적 개수에 비례해 나눕니다(앞 경계가 없으면 0 으로 봅니다).
6. 같은 표본을 촘촘한 경계 50,75,100,125,150,200,250,300,400,500,750,1000,1500 로 다시 세어 /root/lt-percentiles/hist-fine.tsv 에 저장하세요. 그리고 /root/lt-percentiles/error.tsv 에 두 줄을 적습니다. 각 줄은 탭으로 나눈 세 칸 <이름> <추정 p95> <절대 오차> 이고 이름은 coarse · fine 순서, 값은 소수 첫째 자리까지입니다. 오차는 3단계의 true_p95 와의 차이의 절댓값입니다. 마지막으로 /root/lt-percentiles/06-note.txt 에 better=<coarse|fine> 과 reason=<40자 이상> 두 줄을 적으세요. reason 에는 촘촘한 경계가 공짜가 아닌 이유도 함께 적습니다.
7. /opt/lab/lt/lt-percentiles/target.py 를 8080 포트에 띄우세요(/fast 는 30밀리초, /slow 는 250밀리초). hey 를 두 번 돌려 원본 출력을 남깁니다 — hey -n 300 -c 10 -o csv http://127.0.0.1:8080/fast 를 /root/lt-percentiles/run-fast.csv 로, hey -n 100 -c 10 -o csv http://127.0.0.1:8080/slow 를 /root/lt-percentiles/run-slow.csv 로. 그다음 /root/lt-percentiles/runs.txt 에 네 줄을 적으세요 — p95_fast= · p95_slow= · mean_of_p95=(두 p95 의 단순 평균) · merged_p95=(두 실행의 원본 응답 시간을 모두 합쳐 다시 구한 p95). 모두 초 단위 소수 넷째 자리까지이고, status-code 가 200 인 줄만 셉니다.
8. /root/lt-percentiles/merge_p95.py 를 만드세요. python3 merge_p95.py <분위> <hey CSV...> 로 부르면 CSV 들의 status-code 가 200 인 줄에서 첫 칸(응답 시간, 초)을 모두 한 통에 모아 가장 가까운 순위로 백분위를 구해 소수 넷째 자리까지 한 줄로 찍습니다. 실행마다 백분위를 구해 평균내면 안 됩니다. 그 도구를 7단계의 두 CSV 에 돌려 /root/lt-percentiles/merged.txt 에 네 줄을 적으세요 — q=0.95 · merged_p95= · mean_of_p95= · gap=(merged 빼기 mean). 마지막으로 /root/lt-percentiles/policy.txt 에 rule= 로 시작하는 한 줄을 60자 이상으로 적어, 앞으로 여러 번의 부하 시험 결과를 합칠 때 팀이 지킬 규칙을 적으세요.
참고
- 작업 디렉터리는
/root/lt-percentiles입니다. 없으면 먼저 만드세요. - 재료는
/opt/lab/lt/lt-percentiles/에 있습니다 —shard-a.txt(6000줄) ·shard-b.txt(3000줄) ·shard-c.txt(1000줄) 은 한 줄에 밀리초 하나이고,tiny.txt(20줄)는 계산법 차이를 보기 위한 작은 표본입니다.make_shards.py가 이 파일들을 만든 고정 시드 스크립트이고,target.py는 7단계에서 띄울 부하 대상입니다. - 부하 대상은 컨테이너가 아니라 파이썬 표준 라이브러리 서버입니다. 이 실습 환경에서는 컨테이너를 띄울 수 없습니다.
(nohup python3 <경로>/target.py 8080 >/dev/null 2>&1 &)로 띄우고curl로 한 번 확인한 뒤 부하를 거세요. - 흔한 실수:
hey출력에서 상태 코드를 거르지 않는 것. 실패한 요청의 시간이 섞이면 백분위가 달라집니다. - 흔한 실수: 표본이 적을 때 두 계산법의 차이를 '오차' 로 보는 것. 둘 다 맞는 정의이고, 무엇을 쓰는지 적어 두지 않은 것이 문제입니다.
- [Histograms and summaries (Prometheus)](https://prometheus.io/docs/practices/histograms/) · [Query functions — histogram_quantile](https://prometheus.io/docs/prometheus/latest/querying/functions/) · [k6 metrics reference](https://grafana.com/docs/k6/latest/using-k6/metrics/reference/) · [k6 thresholds](https://grafana.com/docs/k6/latest/using-k6/thresholds/) · [HdrHistogram](https://github.com/HdrHistogram/HdrHistogram) · [hey](https://github.com/rakyll/hey)
단계 8개
- 구간마다 몇 건이고 p95 는 얼마인가
- 같은 표본, 두 가지 계산법
- 구간별 p95 를 평균하면 전체 p95 가 아니다
- 합친 p95 가 놓일 수 있는 울타리
- 버킷은 더할 수 있다
- 경계가 오차를 정한다
- 실제 두 실행을 합쳐 본다
- 여러 실행을 올바르게 합치는 도구