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

부하 테스트

성능 회귀라고 되돌린 변경이 사실은 잡음이었다

TT Lab 에서 이어서 보기

목표

같은 대상을 다섯 번 반복해 잡음의 크기를 먼저 재고, 지연을 조금 늘린 판과 많이 늘린 판을 각각 다섯 번씩 재서 어느 쪽이 잡음을 넘는지 규칙으로 판정합니다. 그 규칙을 기준선 디렉터리와 새 측정 디렉터리를 받아 종료 코드를 내는 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자 이상> 입니다.

참고

아무것도 바꾸지 않고 다섯 번 재서 잡음을 구한다

/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, 소수 첫째 자리> 를 적습니다.

csv 의 첫 줄은 머리글이라 건너뛰어야 합니다. gawk 의 asort 로 정렬하면 한 줄로 끝납니다. 이 대상은 CPU 를 태우지 않고 잠만 자므로, 여기서 보이는 흔들림은 기계 경합이 아니라 대상이 일부러 넣은 재현 가능한 흔들림입니다. 운영의 가비지 컬렉션이나 이웃 부하가 그 자리를 대신한다고 보면 됩니다.

같은 코드끼리도 이만큼 벌어진다

1단계의 다섯 실행 중 p50 이 가장 작은 실행과 가장 큰 실행을 고르세요. /root/lt-regression/02-illusion.txt 에 여섯 줄을 적습니다 — fastest_run=<번호> · slowest_run=<번호> · fastest_p50=<밀리초> · slowest_p50=<밀리초> · apparent_change_pct=<(느린 값 - 빠른 값) ÷ 빠른 값 × 100, 소수 첫째 자리> · same_code=yes 입니다. 밀리초는 소수 셋째 자리까지 적습니다. 이 둘은 같은 판을 같은 조건으로 잰 것입니다.

sort -k2 -n 01-noise.tsv 로 정렬하면 양 끝이 바로 나옵니다. 여기서 나오는 백분율이 곧 '아무것도 바꾸지 않았을 때 한 번의 측정으로 주장할 수 있는 변화량' 입니다. 고정 문턱을 그보다 작게 잡으면 게이트는 배포를 무작위로 막게 됩니다.

5% 느려진 판을 다섯 번 잰다

같은 /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단계와 같습니다.

부하 조건을 바꾸면 두 판을 견줄 수 없습니다 — -n 과 -c 를 1단계와 똑같이 두세요. 다섯 값의 중앙값은 정렬해서 세 번째 값입니다. 한 실행의 값이 아니라 다섯 실행의 중앙값을 쓰는 이유는, 운 나쁜 실행 하나가 판정을 뒤집지 못하게 하려는 것입니다.

50% 느려진 판도 다섯 번 잰다

/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단계와 같은 모양으로 적습니다.

세 판이 동시에 떠 있어도 서로 방해하지 않습니다 — 잠만 자는 대상이라 CPU 를 다투지 않기 때문입니다. 이 단계에서 나오는 차이는 눈으로도 보일 만큼 크지만, 그래도 '눈에 보인다' 를 판정 근거로 쓰지 않는 것이 이 실습의 요지입니다. 판정은 다음 단계의 규칙이 합니다.

판정 규칙을 글로 적고 세 판에 적용한다

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

base 줄의 delta 는 자기 자신과의 차이이므로 0.000 입니다. small 이 어느 쪽으로 판정되는지는 미리 정하지 마세요 — 그날 잰 잡음이 정합니다. 그것이 고정 백분율 문턱과 다른 점입니다. 부등호는 '크다' 이지 '크거나 같다' 가 아닙니다.

규칙을 CI 게이트로 못박는다

/root/lt-regression/gate.sh 를 만드세요. bash gate.sh <기준선 디렉터리> <새 측정 디렉터리> 로 부르면 두 디렉터리의 *.csv 를 각각 읽어 실행마다 p50 을 구하고, 각 묶음의 중앙값을 냅니다. 출력은 한 줄이고 delta=<밀리초> noise=<밀리초> REGRESSION 또는 delta=<밀리초> noise=<밀리초> OK 입니다. delta 는 새 측정의 중앙값에서 기준선의 중앙값을 뺀 값, noise 는 기준선 실행들의 p50 최대에서 최소를 뺀 값이고 둘 다 소수 셋째 자리까지입니다. delta 가 noise 보다 크면 종료 코드 1, 아니면 0 으로 끝납니다. 채점기는 자기가 만든 세 벌의 입력으로 이 스크립트를 돌려 통과와 실패를 모두 확인합니다.

디렉터리 안의 csv 개수를 미리 정해 두지 마세요 — 채점기는 다섯 개가 아닌 묶음으로도 돌립니다. csv 가 하나도 없으면 0 으로 끝내지 말고 다른 종료 코드로 알리세요. 새 측정이 더 빠른 경우(delta 가 음수)는 회귀가 아닙니다. 값을 숫자로 견줄 때는 awk 'BEGIN{exit !(d > n)}' 처럼 awk 의 종료 코드를 쓰세요.

한 번짜리 비교는 같은 코드도 회귀로 만든다

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 이상의 정수> 입니다.

기준선 실행이 하나뿐이면 최대와 최소가 같아 잡음이 0 이 됩니다. 문턱이 0 이면 0 보다 큰 차이는 무엇이든 회귀입니다. 이 단계에서 회귀로 보고된 두 측정은 같은 판을, 같은 기계에서, 같은 조건으로 잰 것입니다. min_runs 에는 자기 규칙에서 쓸 최소 반복 횟수를 적으세요.

되돌릴 것인가 — 게이트의 답과 사람의 답

게이트를 두 번 더 돌리세요. 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자 이상> 입니다.

같은 자료를 자기 자신과 견주면 차이가 0 이므로 어떤 규칙에서도 회귀가 아니어야 합니다 — 게이트가 그 경우에도 막는다면 규칙이 아니라 구현이 틀린 것입니다. threshold_rule 에는 고정 백분율 대신 무엇을 쓸지 적으세요. why 에는 되돌리기로 한(또는 하지 않기로 한) 근거를 앞 단계의 숫자와 이어서 적습니다.