부하 테스트 · 회귀 판정 · 실습
성능 회귀라고 되돌린 변경이 사실은 잡음이었다
목표
같은 대상을 다섯 번 반복해 잡음의 크기를 먼저 재고, 지연을 조금 늘린 판과 많이 늘린 판을 각각 다섯 번씩 재서 어느 쪽이 잡음을 넘는지 규칙으로 판정합니다. 그 규칙을 기준선 디렉터리와 새 측정 디렉터리를 받아 종료 코드를 내는 CI 게이트 스크립트로 만들고, 한 번짜리 비교가 같은 코드도 회귀로 만든다는 것을 숫자로 확인합니다.
왜 중요한가
성능 회귀 판정은 통계 문제가 아니라 운영 문제다. 문턱을 잘못 잡으면 둘 중 하나가 일어난다 — 거짓 경보가 잦아 사람들이 게이트를 끄거나, 문턱이 느슨해 진짜 회귀가 지나간다. 두 경우 다 게이트가 없는 것과 같아진다. 그래서 문턱은 회의실에서 정한 백분율이 아니라 그때 그 기계에서 실제로 잰 잡음이어야 한다. 잡음을 재려면 아무것도 바꾸지 않은 채로 같은 시험을 여러 번 돌려 봐야 하고, 그러면 자연스럽게 '몇 번 돌릴 것인가' 가 시간과 민감도의 맞바꿈이라는 것이 드러난다. 한 번만 돌리면 문턱이 0 이 되어 무엇이든 회귀가 되고, 많이 돌릴수록 더 작은 회귀를 자신 있게 잡을 수 있다. 이 판단을 사람의 눈에 맡기지 않고 스크립트의 종료 코드로 남기는 것이 이 실습의 마지막 단계다.
단계
1. /opt/lab/lt/lt-regression/svc.py 를 VERSION=base DELAY_MS=20 JITTER_MS=20 SEED=1234 로 127.0.0.1:8080 에 띄우세요. hey -n 200 -c 5 -o csv http://127.0.0.1:8080/work 를 다섯 번 돌려 출력을 /root/lt-regression/base/run1.csv 부터 run5.csv 까지 저장합니다. 실행마다 p50 을 구해 /root/lt-regression/01-noise.tsv 에 다섯 줄로 적으세요 — 각 줄은 탭으로 나눈 두 칸 run<번호> <p50 밀리초> 이고 밀리초는 소수 셋째 자리까지입니다. p50 은 csv 첫 칸(response-time, 초 단위)을 밀리초로 바꿔 오름차순 정렬한 뒤 int(n/2)+1 번째(1부터 세어) 값으로 정합니다. 그리고 /root/lt-regression/01-spread.txt 에 네 줄 min= · max= · spread_ms=<max - min> · spread_pct=<(max - min) ÷ min × 100, 소수 첫째 자리> 를 적습니다.
2. 1단계의 다섯 실행 중 p50 이 가장 작은 실행과 가장 큰 실행을 고르세요. /root/lt-regression/02-illusion.txt 에 여섯 줄을 적습니다 — fastest_run=<번호> · slowest_run=<번호> · fastest_p50=<밀리초> · slowest_p50=<밀리초> · apparent_change_pct=<(느린 값 - 빠른 값) ÷ 빠른 값 × 100, 소수 첫째 자리> · same_code=yes 입니다. 밀리초는 소수 셋째 자리까지 적습니다. 이 둘은 같은 판을 같은 조건으로 잰 것입니다.
3. 같은 /opt/lab/lt/lt-regression/svc.py 를 VERSION=small DELAY_MS=21 JITTER_MS=20 SEED=1234 로 127.0.0.1:8081 에 추가로 띄우세요(8080 은 그대로 둡니다). 1단계와 똑같은 부하를 다섯 번 걸어 /root/lt-regression/small/run1.csv 부터 run5.csv 까지 저장하고, /root/lt-regression/03-small.tsv 에 1단계와 같은 모양으로 다섯 줄을 적으세요. 그리고 /root/lt-regression/03-median.txt 에 median_of_medians=<다섯 p50 의 중앙값, 소수 셋째 자리> 한 줄을 적습니다. 중앙값의 정의는 1단계와 같습니다.
4. /opt/lab/lt/lt-regression/svc.py 를 VERSION=big DELAY_MS=30 JITTER_MS=20 SEED=1234 로 127.0.0.1:8082 에 추가로 띄우고, 같은 부하를 다섯 번 걸어 /root/lt-regression/big/run1.csv 부터 run5.csv 까지 저장하세요. /root/lt-regression/04-big.tsv 에 다섯 줄, /root/lt-regression/04-median.txt 에 median_of_medians= 한 줄을 3단계와 같은 모양으로 적습니다.
5. /root/lt-regression/05-rule.txt 에 네 줄을 적으세요 — metric=p50 · stat=median_of_5 · noise_ms=<1단계 다섯 실행의 최대 - 최소, 소수 셋째 자리> · rule=<이 규칙을 한 문장으로, 40자 이상> 입니다. 그리고 /root/lt-regression/05-verdict.tsv 에 세 줄을 적습니다. 각 줄은 탭으로 나눈 세 칸 <이름> <delta_ms> <judgment> 이고 이름은 base · small · big 순서입니다. delta 는 그 판의 다섯 실행 중앙값에서 base 의 다섯 실행 중앙값을 뺀 값(소수 셋째 자리)이고, judgment 는 delta 가 noise_ms 보다 클 때 regression, 아니면 noise 입니다.
6. /root/lt-regression/gate.sh 를 만드세요. bash gate.sh <기준선 디렉터리> <새 측정 디렉터리> 로 부르면 두 디렉터리의 *.csv 를 각각 읽어 실행마다 p50 을 구하고, 각 묶음의 중앙값을 냅니다. 출력은 한 줄이고 delta=<밀리초> noise=<밀리초> REGRESSION 또는 delta=<밀리초> noise=<밀리초> OK 입니다. delta 는 새 측정의 중앙값에서 기준선의 중앙값을 뺀 값, noise 는 기준선 실행들의 p50 최대에서 최소를 뺀 값이고 둘 다 소수 셋째 자리까지입니다. delta 가 noise 보다 크면 종료 코드 1, 아니면 0 으로 끝납니다. 채점기는 자기가 만든 세 벌의 입력으로 이 스크립트를 돌려 통과와 실패를 모두 확인합니다.
7. 1단계의 다섯 실행 중 p50 이 가장 작은 것을 /root/lt-regression/one-base/run.csv 로, 가장 큰 것을 /root/lt-regression/one-new/run.csv 로 복사하세요(두 파일 다 base 판의 측정입니다). 그 두 디렉터리로 게이트를 돌려 출력을 /root/lt-regression/07-gate-out.txt 에 저장하고, /root/lt-regression/07-onerun.txt 에 다섯 줄을 적습니다 — noise_ms=<게이트가 낸 값> · delta_ms=<게이트가 낸 값> · gate_exit=<종료 코드> · false_positive=<yes 또는 no> · min_runs=<기준선을 최소 몇 번 돌려야 한다고 볼 것인가, 2 이상의 정수> 입니다.
8. 게이트를 두 번 더 돌리세요. bash gate.sh base big 의 출력은 /root/lt-regression/08-gate-big.txt 에, bash gate.sh base base 의 출력은 /root/lt-regression/08-gate-same.txt 에 저장합니다. 그리고 /root/lt-regression/08-decision.txt 에 여섯 줄을 적으세요 — big_exit= · same_exit= · rollback=<yes 또는 no, big 판을 되돌릴 것인가> · min_runs=<5 이상의 정수> · threshold_rule=<문턱을 어떻게 정할 것인가, 40자 이상> · why=<60자 이상> 입니다.
참고
- 작업 디렉터리는
/root/lt-regression입니다. 없으면 먼저 만드세요. - 부하 대상은
/opt/lab/lt/lt-regression/svc.py입니다. 파일 첫머리의 주석에 환경 변수와 세 판을 띄우는 법이 적혀 있습니다. - 이 파드에서는 컨테이너를 띄울 수 없습니다(seccomp). 대상은 파이썬 표준 라이브러리 서버를
127.0.0.1에 직접 띄워 씁니다. 판을 다시 띄우려면pkill -f 'lt-regression/svc.py'로 앞 판을 죽이세요. hey -o csv는 요약 대신 요청마다 한 줄을 냅니다. 첫 칸이 응답 시간(초)이고 첫 줄은 머리글입니다.- 흔한 실수: 판마다
-n이나-c를 다르게 주는 것. 부하 조건이 다르면 두 측정은 견줄 짝이 아닙니다. - 흔한 실수: 문턱을 '5%' 처럼 고정하는 것. 이 실습의 대상에서도 잡음이 그보다 클 수 있습니다.
- [hey (rakyll/hey)](https://github.com/rakyll/hey) · [k6 automated performance testing](https://grafana.com/docs/k6/latest/testing-guides/automated-performance-testing/) · [k6 thresholds](https://grafana.com/docs/k6/latest/using-k6/thresholds/) · [Median absolute deviation](https://en.wikipedia.org/wiki/Median_absolute_deviation) · [wrk2](https://github.com/giltene/wrk2)
단계 8개
- 아무것도 바꾸지 않고 다섯 번 재서 잡음을 구한다
- 같은 코드끼리도 이만큼 벌어진다
- 5% 느려진 판을 다섯 번 잰다
- 50% 느려진 판도 다섯 번 잰다
- 판정 규칙을 글로 적고 세 판에 적용한다
- 규칙을 CI 게이트로 못박는다
- 한 번짜리 비교는 같은 코드도 회귀로 만든다
- 되돌릴 것인가 — 게이트의 답과 사람의 답