부하 테스트 · 도착 과정과 생각 시간 · 실습
평균은 같은데 한쪽만 대기열이 터졌다
목표
같은 평균 요청률로 일정 간격 · 지수분포 · 묶음 세 가지 도착을 직접 만들어 같은 서버에 보내고, 대기열과 지연이 어떻게 갈리는지 잽니다. 이어서 닫힌 루프의 생각 시간이 실효 요청률을 정하는 법칙을 실측과 대조하고, 고른 도착을 가정한 용량 산정이 몇 배 빗나가는지 숫자로 보인 뒤 이 서비스의 도착 모델과 워커 수를 근거와 함께 결정합니다.
왜 중요한가
용량 산정은 거의 언제나 평균으로 시작한다 — 초당 몇 건, 한 건에 몇 밀리초, 그러니 워커 몇 개. 그 계산에는 '요청이 고르게 온다' 는 가정이 숨어 있는데, 실제 트래픽은 크론과 알림과 재시도 때문에 묶음으로 온다. 대기열은 평균이 아니라 순간에 반응하므로, 평균 이용률이 25% 여도 묶음이 오는 순간에는 워커가 모자라고 그 대기가 다시 타임아웃과 재시도를 만든다. 반대편에는 닫힌 루프의 함정이 있다. 가상 사용자 수로 시험하면 서버가 느려질 때 부하도 함께 줄어, 보고서의 '초당 몇 건까지 버텼다' 가 서버의 한계가 아니라 시험 설정의 한계가 된다. 이 두 가지를 한 번 손으로 재 보면, 다음부터는 부하를 적을 때 평균 옆에 도착 모양을 함께 적게 된다.
단계
1. /opt/lab/lt/lt-arrival-process/server.py 를 python3 /opt/lab/lt/lt-arrival-process/server.py 8080 40 4 로 띄우세요(포트 8080 · 서비스 40밀리초 · 워커 4). 한가할 때의 응답 시간을 curl -s -o /dev/null -w '%{time_total}\n' 로 여섯 번 재서 나온 초 단위 숫자를 /root/lt-arrival-process/01-probe.txt 에 한 줄에 하나씩 그대로 남기세요. 그리고 /root/lt-arrival-process/01-target.txt 에 다섯 줄을 적습니다 — service_ms=(여섯 번의 중앙값을 밀리초로 반올림) · workers=(워커 수) · max_rate=(워커 수 나누기 서비스 시간, 초당 건수) · chosen_rate=25 · utilization=(chosen_rate 를 max_rate 로 나눈 값, 소수 둘째 자리까지).
2. /root/lt-arrival-process/gen.py 를 만드세요. python3 gen.py <even|poisson|burst> <요청수> <초당요청> <시드> <출력CSV> [URL] 로 부릅니다. URL 을 주지 않으면 보내지 않고 계획만 CSV 에 적습니다 — 머리글 i,intended 에 이어 요청마다 번호와 예정 시각(시작 기준 초)을 적습니다. even 은 간격이 늘 1/요청률, poisson 은 간격이 평균 1/요청률 인 지수분포, burst 는 12건을 같은 시각에 몰아 보내고 12/요청률 만큼 쉽니다. 시드를 받아 난수를 고정하세요. 확인용으로 python3 gen.py even 300 25 7 /root/lt-arrival-process/sched-even.csv 를 한 번 돌려 두세요.
3. 발생기를 마른 실행으로 세 번 돌려 /root/lt-arrival-process/sched-even.csv · /root/lt-arrival-process/sched-poisson.csv · /root/lt-arrival-process/sched-burst.csv 를 만드세요(모두 300 25 7). 그리고 /root/lt-arrival-process/schedules.tsv 에 세 줄을 적습니다. 각 줄은 탭으로 나눈 네 칸 <모양> <요청 수> <평균 간격> <변동계수> 이고 모양은 even · poisson · burst 순서, 평균 간격은 소수 넷째 자리까지 초 단위, 변동계수는 간격의 표준편차를 평균으로 나눈 값으로 소수 셋째 자리까지 적습니다.
4. 세 도착 방식으로 같은 서버에 같은 수의 요청을 실제로 보내세요. 방식마다 curl -s http://127.0.0.1:8080/reset 으로 기록을 지우고, python3 gen.py <모양> 150 25 7 /root/lt-arrival-process/run-<모양>.csv http://127.0.0.1:8080/work 로 보낸 뒤, curl -s http://127.0.0.1:8080/dump > /root/lt-arrival-process/srv-<모양>.csv 로 서버 기록을 받습니다(모양은 even · poisson · burst). 그다음 /root/lt-arrival-process/latency.tsv 에 세 줄을 적으세요 — 탭으로 나눈 네 칸 <모양> <성공 건수> <p50> <p95> 이고 지연은 run-*.csv 의 received 빼기 sent 를 밀리초로 바꿔 가장 가까운 순위로 구해 소수 첫째 자리까지, code 가 200 인 줄만 셉니다.
5. 서버가 남긴 /root/lt-arrival-process/srv-*.csv 에는 요청마다 qlen(그 요청이 닿은 순간 시스템 안에 있던 요청 수)이 들어 있습니다. /root/lt-arrival-process/queue.tsv 에 세 줄을 적으세요 — 탭으로 나눈 네 칸 <모양> <기록 수> <최대 대기열> <평균 대기열> 이고 모양은 even · poisson · burst 순서, 평균은 소수 둘째 자리까지입니다. 그리고 /root/lt-arrival-process/05-note.txt 에 두 줄 worst=<최대 대기열이 가장 큰 모양> 과 ratio=<그 최대값을 even 의 최대값으로 나눈 값, 소수 첫째 자리까지> 를 적으세요.
6. /root/lt-arrival-process/closed.py 를 만드세요. python3 closed.py <URL> <사용자수> <생각시간초> <지속초> <출력CSV> 로 부르면 사용자마다 요청 하나를 보내고 생각 시간만큼 쉬는 일을 지속 시간 동안 되풀이합니다. CSV 는 머리글 user,sent,received,code 에 이어 시작 기준 초를 적습니다. python3 closed.py http://127.0.0.1:8080/work 8 0.2 10 /root/lt-arrival-process/closed.csv 로 한 번 돌리고, /root/lt-arrival-process/think.txt 에 여섯 줄을 적으세요 — users=8 · think_s=0.200 · mean_r_s=(평균 응답 시간) · predicted_rate=(사용자수 나누기 (mean_r_s 더하기 think_s)) · measured_rate=(성공 건수 나누기 처음 sent 부터 마지막 received 까지의 시간) · naive_rate=(응답 시간을 0 으로 보고 사용자수 나누기 think_s). 요청률은 소수 셋째 자리까지, 시간은 소수 넷째 자리까지입니다.
7. /root/lt-arrival-process/capacity.txt 에 다섯 줄을 적으세요. formula_workers=(고른 도착을 가정한 계산: chosen_rate x service_s 를 올림한 정수) · observed_even= · observed_burst=(각각 srv-even.csv 와 srv-burst.csv 에서 동시에 시스템 안에 있던 요청 수의 최댓값) · underestimate_factor=(observed_burst 를 formula_workers 로 나눈 값, 소수 첫째 자리까지) · verdict=<under|ok>(formula_workers 가 observed_burst 보다 작으면 under). 동시 요청 수는 arrive 에 1 을 더하고 end 에 1 을 빼며 시각 순으로 훑어 최댓값을 잡습니다.
8. /root/lt-arrival-process/arrival-model.txt 에 네 줄을 적으세요. model=<even|poisson|burst> 는 앞으로 이 서비스의 용량을 잡을 때 쓸 도착 모델, workers= 는 그 모델로 정한 워커 수(정수), evidence= 는 왜 그 모델인지를 앞 단계에서 만든 파일 이름과 숫자를 들어 60자 이상으로, risk= 는 그 모델이 틀렸을 때 무엇이 먼저 무너지는지 40자 이상으로 적습니다. 채점기는 workers 가 7단계에서 관측한 최대 동시 요청 수 이상인지, 그리고 고른 도착을 가정한 formula_workers 보다 큰지를 봅니다.
참고
- 작업 디렉터리는
/root/lt-arrival-process입니다. 없으면 먼저 만드세요. - 재료는
/opt/lab/lt/lt-arrival-process/server.py하나입니다./work가 일하는 길이고,/reset은 기록을 지우며,/dump는 요청마다seq,arrive,start,end,qlen를 CSV 로 돌려줍니다. 시간은 CPU 가 아니라time.sleep으로 씁니다. - 부하 대상은 컨테이너가 아니라 파이썬 표준 라이브러리 서버입니다. 이 실습 환경에서는 컨테이너를 띄울 수 없습니다.
(nohup python3 /opt/lab/lt/lt-arrival-process/server.py 8080 40 4 >/dev/null 2>&1 &)로 띄우고curl로 한 번 확인한 뒤 부하를 거세요. - 흔한 실수: 방식을 바꾸기 전에
/reset을 부르지 않는 것. 앞 실행의 기록이 남아 대기열 비교가 뒤섞입니다. - 흔한 실수: 묶음 도착의 지연을 '서버가 느려졌다' 로 읽는 것. 서비스 시간은 그대로이고 늘어난 것은 대기 시간입니다 —
srv-*.csv의start빼기arrive가 그 대기입니다. - [k6 open vs closed models](https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/open-vs-closed/) · [k6 executors](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/) · [k6 constant-arrival-rate](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/constant-arrival-rate/) · [k6 ramping-arrival-rate](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ramping-arrival-rate/) · [Service Level Objectives (SRE Book)](https://sre.google/sre-book/service-level-objectives/)
단계 8개
- 워커가 몇이고 한 건에 얼마나 걸리는가
- 도착 모양을 고를 수 있는 발생기
- 같은 평균, 다른 모양
- 세 방식으로 같은 서버를 때린다
- 서버가 센 대기열
- 생각 시간이 요청률을 정한다
- 고른 도착을 가정한 용량은 몇 배 빗나가나
- 이 서비스의 도착 과정을 무엇으로 잡을 것인가