TT Lab
시작하기
배우기 러닝패스 코스

부하 테스트

구간별 p95 를 평균했더니 진짜 p95 보다 108밀리초 높았다

TT Lab 에서 이어서 보기

목표

백분위를 두 가지 방법으로 손수 계산해 값이 갈리는 것을 확인하고, 구간별 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자 이상으로 적어, 앞으로 여러 번의 부하 시험 결과를 합칠 때 팀이 지킬 규칙을 적으세요.

참고

구간마다 몇 건이고 p95 는 얼마인가

/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 가 가장 큰 구간 이름> 두 줄을 적으세요.

정렬은 sort -n, 줄 수는 wc -l 입니다. 가장 가까운 순위의 자리 번호는 ceil(0.95 x n) 이고 awk 에서는 int(0.95*n) + (0.95*n > int(0.95*n) ? 1 : 0) 으로 만듭니다. 세 구간의 표본 수가 서로 다르다는 점을 눈여겨보세요 — 뒤 단계에서 그 차이가 답을 바꿉니다.

같은 표본, 두 가지 계산법

/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> 한 줄을 소수 넷째 자리까지 적으세요.

정렬한 목록에서 nearest 는 색인 k-1, linear 는 values[lo] + (h-lo) x (values[hi]-values[lo]) 입니다. 20개짜리 표본은 두 방법의 차이가 눈에 보이도록 고른 것입니다 — 특히 맨 위 두 값이 멀리 떨어져 있습니다. 채점기는 자기가 만든 표본 파일로 이 도구를 직접 돌려 봅니다.

구간별 p95 를 평균하면 전체 p95 가 아니다

/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 를 적습니다. 다섯 값 모두 소수 첫째 자리까지, 음수면 앞에 빼기 기호를 붙입니다.

원본을 합치는 것은 cat /opt/lab/lt/lt-percentiles/shard-*.txt 한 번이면 됩니다. 2단계에서 만든 pct.py 를 그대로 쓰세요. 두 오차의 부호가 서로 다르다는 점이 이 단계의 핵심입니다 — 평균은 한쪽으로만 틀리지 않습니다.

합친 p95 가 놓일 수 있는 울타리

/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 입니다.

트래픽의 10%밖에 안 되는 느린 구간 하나를 빼면 전체 p95 가 얼마나 내려가는지 보세요. 그리고 울타리를 생각해 보면 — 모든 구간에서 95%가 어떤 값 이하라면, 섞어 놓아도 95%는 그 값 이하입니다. 그래서 '합쳤더니 모든 구간보다 크더라' 는 보고는 자료가 아니라 계산을 의심해야 합니다.

버킷은 더할 수 있다

/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 으로 봅니다).

버킷은 누적입니다. le=100 은 '100 이하인 것이 몇 건' 이지 '50 과 100 사이' 가 아닙니다. 추정식은 프로메테우스의 histogram_quantile 과 같습니다 — 목표 개수가 버킷 안 어디쯤인지를 비례로 봅니다. 채점기는 자기가 만든 표본과 경계로 이 도구를 직접 돌려 봅니다.

경계가 오차를 정한다

같은 표본을 촘촘한 경계 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 에는 촘촘한 경계가 공짜가 아닌 이유도 함께 적습니다.

버킷 추정의 오차는 '목표가 들어 있는 버킷의 폭' 이 정합니다. 250 부터 500 까지 한 칸인 표와 250 부터 300 까지 한 칸인 표에서 같은 목표를 찾아 보세요. 대신 경계를 늘리면 시계열도 그만큼 늘어납니다 — 프로메테우스에서는 경계 하나가 곧 시계열 하나입니다.

실제 두 실행을 합쳐 본다

/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 인 줄만 셉니다.

hey -o csv 의 첫 칸이 응답 시간(초), 일곱째 칸이 상태 코드입니다. 머리글 한 줄은 건너뛰세요. 2단계의 pct.py 를 그대로 쓰려면 cut -d, -f1 로 숫자만 뽑아 임시 파일에 모으면 됩니다. 두 실행의 건수가 다르다는 점에 주의하세요 — 합친 결과가 어느 쪽으로 기우는지가 보입니다.

여러 실행을 올바르게 합치는 도구

/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자 이상으로 적어, 앞으로 여러 번의 부하 시험 결과를 합칠 때 팀이 지킬 규칙을 적으세요.

머리글 한 줄을 건너뛰고, 칸이 일곱 개보다 적은 줄은 버리세요. 상태 코드를 거르지 않으면 실패한 요청의 시간까지 섞여 답이 달라집니다. 채점기는 자기가 만든 CSV 두 개로 이 도구를 두 가지 분위로 돌려 봅니다 — 실행마다 평균내는 구현이나 상태 코드를 안 거르는 구현은 그 자리에서 떨어집니다.