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

부하 테스트

초당 3천 건이 나왔는데 전부 오류 페이지였다

TT Lab 에서 이어서 보기

목표

상태 코드는 늘 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자 이상> 입니다.

참고

요약만 보면 이 시험은 완벽하다

/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 값을 그대로 적으세요.

요약의 Status code distribution 아래 줄은 [<코드>]<탭><수> responses 모양입니다. awk 로 대괄호를 지우면 첫 칸이 코드, 둘째 칸이 수가 됩니다. 서버가 뜰 때까지 기다리려면 /stats 에 curl 을 반복해 보내세요. 이 단계에서 나오는 숫자는 나중에 '틀렸다' 고 밝혀질 숫자입니다.

서버가 실제로 무엇을 돌려줬는지 센다

/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자 이상으로 적습니다.

awk -F'\t' '$6=="ok"' 01-access.log | wc -l 로 셉니다. 접근 기록의 줄 수는 hey 가 센 응답 수와 같아야 합니다 — 다르면 중간에서 끊긴 요청이 있다는 뜻입니다. 상태 코드는 계약의 한 조각일 뿐이고, 본문이 나머지입니다.

오류가 더 빨라서 숫자가 좋아 보인다

/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자 이상> 두 줄을 적으세요.

awk -F'\t' '$6=="ok"{print $8}' 01-access.log | sort -n 으로 정렬한 뒤 줄 번호로 고릅니다. 오류 경로는 데이터베이스도 큐도 타지 않으니 빨리 끝납니다. 그래서 오류가 섞일수록 평균과 백분위가 좋아지고, 시험이 망가졌다는 신호가 '느려졌다' 가 아니라 '빨라졌다' 로 옵니다.

연결을 재사용하느냐가 시험의 절반을 바꾼다

오류 주입을 끈 대상(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자 이상> 세 줄을 적습니다. 운영의 클라이언트는 연결 풀을 쓴다고 가정하세요.

접근 기록의 둘째 칸은 그 요청을 나른 TCP 연결의 번호입니다. cut -f2 04-on.log | sort -u | wc -l 로 셉니다. 두 번째 실행 전에 대상을 다시 띄워야 접근 기록이 섞이지 않습니다. hey 의 옵션 이름은 -disable-compression 이 아니라 -disable-keepalive 입니다 — 둘은 다른 것을 끕니다.

응답을 64배로 키우면 무엇이 바뀌나

같은 부하(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자 이상> 세 줄을 적으세요.

초당 요청 수가 거의 그대로인데 초당 바이트만 수십 배로 뛴다면, 이 대상은 대역폭이 아니라 기다리는 시간에 묶여 있다는 뜻입니다. 처리량을 초당 요청 수로만 보면 이 차이가 보이지 않습니다. 운영 응답이 64KB 인데 1KB 로 시험했다면 그 시험은 직렬화와 대역폭을 한 번도 건드리지 않은 것입니다.

같은 URL 만 때리면 캐시가 시험을 대신 통과한다

캐시를 켠 대상(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자 이상> 세 줄을 적으세요.

접근 기록의 일곱째 칸이 hit 또는 miss 입니다. /api/order/rotate 는 요청마다 다른 열쇠를 쓰므로 열쇠 가짓수가 요청 수보다 많으면 적중이 하나도 없습니다. 운영의 적중률을 모른 채 고정 열쇠로 시험하면, 캐시 뒤의 데이터베이스는 한 번도 부하를 받지 않은 채 배포됩니다.

믿어도 되는 결과인지 판정하는 점검표를 만든다

/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 으로 끝나야 합니다. 채점기는 자기가 만든 네 벌의 입력으로 이 스크립트를 돌려 통과와 실패를 모두 확인합니다.

세 검사를 각각 돌린 뒤 마지막에 한 번만 종료 코드를 내야 합니다 — 중간에 exit 하면 나머지 검사 줄이 출력되지 않습니다. 소수 비교는 awk 'BEGIN{exit !(r < 1.0)}' 처럼 awk 의 종료 코드로 합니다. 이름을 줄 맨 앞에 두는 이유는 사람이 아니라 파이프라인이 읽기 때문입니다.

1단계의 그 훌륭한 결과를 게이트에 통과시켜 본다

만든 점검표로 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자 이상> 입니다.

1단계의 요약만 보면 비-2xx 는 0 이고 응답 수도 맞습니다. 그런데도 게이트가 막는다면 어느 검사가 막았는지가 이 실습의 결론입니다. fix 에는 '무엇을 검증하도록 시험을 고칠 것인가' 를 적으세요 — 도구를 바꾸는 것도, 응답을 검사하는 층을 붙이는 것도 답이 될 수 있습니다.