부하 테스트 · 시험이 거짓말하는 자리 · 실습
초당 3천 건이 나왔는데 전부 오류 페이지였다
목표
상태 코드는 늘 200 인데 본문은 오류인 대상을 부하 시험해 보고, 요약만 보는 시험이 무엇을 놓치는지 직접 세어 확인합니다. 연결 재사용·응답 크기·캐시 열쇠를 바꿔 가며 시험 조건이 운영을 대표하는지 판단하고, 마지막에 이 결과를 믿어도 되는지 판정하는 점검표 스크립트를 만들어 종료 코드로 게이트를 세웁니다.
왜 중요한가
부하 시험 보고서에서 가장 위험한 문장은 '오류율 0%' 다. 부하 발생기가 아는 것은 상태 코드뿐이고, 200 안에 담긴 오류는 그 눈에 보이지 않는다. 게다가 오류 경로는 일을 하지 않으므로 빨리 끝나서, 시험이 망가질수록 지연 숫자가 좋아진다. 그래서 검증이 없는 부하 시험은 틀린 답이 아니라 아무 답도 아니다. 검증을 붙인 뒤에도 질문이 하나 더 남는다 — 이 시험 조건이 운영을 대표하는가. 연결을 매번 새로 열었다면 TCP 핸드셰이크를 잰 것이고, 응답이 운영의 64분의 1이었다면 직렬화와 대역폭을 한 번도 건드리지 않은 것이고, 같은 URL 만 때렸다면 캐시가 시험을 대신 통과한 것이다. 이 판단을 사람의 눈에 맡기지 않고 스크립트의 종료 코드로 못박는 것이 이 실습의 마지막 단계다.
단계
1. /opt/lab/lt/lt-test-validity/app.py 을 DELAY=0.02 ERR_EVERY=3 SIZE=1024 로 127.0.0.1:8080 에 띄우세요. 접근 기록은 /root/lt-test-validity/01-access.log 로 받습니다. 띄운 뒤 /reset 을 한 번 부르고 hey -n 300 -c 10 'http://127.0.0.1:8080/api/order?id=1' 을 돌려 원본 출력을 /root/lt-test-validity/01-summary.txt 에 저장하세요. 그리고 /root/lt-test-validity/01-claim.txt 에 네 줄 total= · status_200= · non_2xx= · rps= 를 그 요약에서 읽어 적습니다. rps 는 요약의 Requests/sec 값을 그대로 적으세요.
2. /root/lt-test-validity/01-access.log 의 여섯째 칸(kind)을 세어 /root/lt-test-validity/02-truth.tsv 를 만드세요. 두 줄이고 각 줄은 탭으로 나눈 두 칸 ok <수> 와 error <수> 입니다(ok 가 먼저). 그리고 /root/lt-test-validity/02-note.txt 에 세 줄을 적으세요 — error_ratio= 는 오류 본문의 비율을 백분율로 소수 첫째 자리까지, hey_non2xx= 는 1단계 요약의 비-2xx 응답 수, why= 는 두 숫자가 왜 이렇게 다른지를 40자 이상으로 적습니다.
3. /root/lt-test-validity/01-access.log 의 여덟째 칸(dur_ms)으로 정상 응답과 오류 응답의 중앙값을 각각 구해 /root/lt-test-validity/03-durations.tsv 에 두 줄로 적으세요. 각 줄은 탭으로 나눈 두 칸 ok <중앙값> · error <중앙값> 이고 밀리초를 소수 첫째 자리까지 적습니다. 중앙값은 오름차순으로 정렬한 n개 중 int(n/2)+1 번째(1부터 세어) 값으로 정합니다. 그리고 /root/lt-test-validity/03-note.txt 에 faster=<ok 또는 error> 와 effect=<이 성질이 시험 결과를 어느 쪽으로 밀어내는가, 40자 이상> 두 줄을 적으세요.
4. 오류 주입을 끈 대상(ERR_EVERY=0 DELAY=0.02 SIZE=1024)으로 같은 부하를 두 번 재세요. 한 번은 기본값으로(/root/lt-test-validity/04-on.txt, 접근 기록 /root/lt-test-validity/04-on.log), 한 번은 -disable-keepalive 를 붙여서(/root/lt-test-validity/04-off.txt, 접근 기록 /root/lt-test-validity/04-off.log) 이고 두 번 다 hey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1' 입니다. /root/lt-test-validity/04-keepalive.tsv 에 두 줄을 적으세요 — on <연결 수> <rps> 와 off <연결 수> <rps> 이고 rps 는 소수 둘째 자리까지입니다. 연결 수는 접근 기록의 둘째 칸에 나오는 서로 다른 값의 개수입니다. 그리고 /root/lt-test-validity/04-note.txt 에 use_run=<on 또는 off> · conn_ratio=<off 연결 수 ÷ on 연결 수, 소수 첫째 자리> · why=<50자 이상> 세 줄을 적습니다. 운영의 클라이언트는 연결 풀을 쓴다고 가정하세요.
5. 같은 부하(hey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1', ERR_EVERY=0)를 응답 크기만 바꿔 두 번 재세요 — SIZE=1024 는 /root/lt-test-validity/05-1k.txt, SIZE=65536 은 /root/lt-test-validity/05-64k.txt 입니다. /root/lt-test-validity/05-size.tsv 에 두 줄을 적습니다. 각 줄은 탭으로 나눈 네 칸 <이름> <요청당 바이트> <rps> <초당 바이트> 이고 이름은 1k 와 64k, rps 는 소수 둘째 자리까지, 초당 바이트는 요청당 바이트 × rps 를 반올림한 정수입니다. 요청당 바이트와 rps 는 요약의 Size/request 와 Requests/sec 에서 읽습니다. 그리고 /root/lt-test-validity/05-note.txt 에 bound_by=<latency 또는 bandwidth> · bytes_ratio=<64k 초당 바이트 ÷ 1k 초당 바이트, 소수 첫째 자리> · why=<50자 이상> 세 줄을 적으세요.
6. 캐시를 켠 대상(CACHE=1 ERR_EVERY=0 DELAY=0.02 SIZE=1024)으로 두 번 재세요. 한 번은 열쇠가 고정된 'http://127.0.0.1:8080/api/order?id=1'(/root/lt-test-validity/06-fixed.txt, 기록 /root/lt-test-validity/06-fixed.log), 한 번은 열쇠가 도는 http://127.0.0.1:8080/api/order/rotate(/root/lt-test-validity/06-rotate.txt, 기록 /root/lt-test-validity/06-rotate.log) 이고 두 번 다 hey -n 200 -c 10 입니다. 두 번째는 ROTATE_KEYS=500 으로 띄우세요. /root/lt-test-validity/06-cache.tsv 에 두 줄을 적습니다. 각 줄은 탭으로 나눈 다섯 칸 <이름> <적중> <빗나감> <적중률> <rps> 이고 이름은 fixed 와 rotate, 적중률은 백분율로 소수 첫째 자리까지, rps 는 소수 둘째 자리까지입니다. 그리고 /root/lt-test-validity/06-note.txt 에 representative=<fixed 또는 rotate> · inflation=<fixed rps ÷ rotate rps, 소수 첫째 자리> · why=<50자 이상> 세 줄을 적으세요.
7. /root/lt-test-validity/validate.sh 를 만드세요. bash validate.sh <hey 요약 파일> <접근 기록 파일> 로 부르면 세 줄을 출력합니다 — status <OK|FAIL> <값> · body <OK|FAIL> <값> · volume <OK|FAIL> <값> 이고 이름이 줄 맨 앞에 옵니다. status 는 요약의 비-2xx 응답이 0 일 때, body 는 접근 기록의 error 본문 비율이 1% 미만일 때, volume 은 접근 기록 줄 수가 요약의 응답 총수와 같을 때 OK 입니다. 하나라도 FAIL 이면 종료 코드 1, 전부 OK 면 0 으로 끝나야 합니다. 채점기는 자기가 만든 네 벌의 입력으로 이 스크립트를 돌려 통과와 실패를 모두 확인합니다.
8. 만든 점검표로 1단계의 결과를 판정하세요. bash validate.sh 01-summary.txt 01-access.log 의 출력을 /root/lt-test-validity/08-gate-out.txt 에 저장하고, /root/lt-test-validity/08-verdict.txt 에 네 줄을 적습니다 — gate_exit=<종료 코드> · failed_check=<FAIL 이 난 검사 이름> · trustworthy=<yes 또는 no> · fix=<이 시험을 다시 하려면 무엇을 고쳐야 하는가, 60자 이상> 입니다.
참고
- 작업 디렉터리는
/root/lt-test-validity입니다. 없으면 먼저 만드세요. - 부하 대상은
/opt/lab/lt/lt-test-validity/app.py입니다. 파일 첫머리의 주석에 환경 변수와 경로, 접근 기록의 여덟 칸이 적혀 있습니다. - 이 파드에서는 컨테이너를 띄울 수 없습니다(seccomp). 대상은 파이썬 표준 라이브러리 서버를
127.0.0.1에 직접 띄워 씁니다. 다시 띄우기 전에pkill -f 'lt-test-validity/app.py'로 앞 판을 죽이세요. nproc은 파드가 아니라 노드의 코어 수를 말합니다.hey가 안내하는 기본-cpus값도 마찬가지입니다 — 그래서 이 실습의 대상은 CPU 가 아니라time.sleep()으로 시간을 씁니다.- 흔한 실수: 두 번째 실행 전에 대상을 다시 띄우지 않아 접근 기록이 앞 실행과 섞이는 것.
- 흔한 실수:
-disable-compression과-disable-keepalive를 섞어 쓰는 것. 앞은 압축을, 뒤는 연결 재사용을 끕니다. - [hey (rakyll/hey)](https://github.com/rakyll/hey) · [k6 checks](https://grafana.com/docs/k6/latest/using-k6/checks/) · [k6 thresholds](https://grafana.com/docs/k6/latest/using-k6/thresholds/) · [http.server](https://docs.python.org/3/library/http.server.html) · [vegeta](https://github.com/tsenart/vegeta)
단계 8개
- 요약만 보면 이 시험은 완벽하다
- 서버가 실제로 무엇을 돌려줬는지 센다
- 오류가 더 빨라서 숫자가 좋아 보인다
- 연결을 재사용하느냐가 시험의 절반을 바꾼다
- 응답을 64배로 키우면 무엇이 바뀌나
- 같은 URL 만 때리면 캐시가 시험을 대신 통과한다
- 믿어도 되는 결과인지 판정하는 점검표를 만든다
- 1단계의 그 훌륭한 결과를 게이트에 통과시켜 본다