부하 테스트 · 병목 찾기 · 실습
병목 재현과 제거
이 실습은 진짜 VM 에서 돕니다
이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이
따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라
진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가
되고 docker exec 도 docker logs 도 그대로 동작합니다.
예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라
컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어
보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.
알아 둘 것이 둘 있습니다.
- 처음 뜨는 데 1분 남짓 걸립니다. VM 이 부팅하고 도커를 설치하기
- 브라우저 미리보기가 없습니다. VM 으로 들어오는 연결은 채점 포트
때문입니다. 파드 실습(보통 40초)보다 느립니다.
하나만 열려 있습니다. 웹 서버를 띄웠다면 VM 안에서 curl 로 확인하세요.
목표
병목을 일부러 만들어 재현하고, 계층별 가설로 좁히고, 제거한 뒤 개선폭을 숫자로 확인하고, 목표 트래픽을 감당할 인스턴스 수까지 계산합니다. 산출물은 /root/lt3/ 아래에 둡니다.
왜 중요한가
병목을 찾는 일은 감이 아니라 사다리를 순서대로 훑는 일입니다. 소켓 버퍼 → 스레드 풀 큐 → 커넥션 풀 대기 → DB 락 대기 → 처리. 이 실습이 만드는 병목은 그중 가장 앞쪽, 즉 '한 번에 하나만 처리한다'는 동시성 제약입니다. 이때 나타나는 신호가 중요합니다. 동시성을 10배로 올려도 처리량은 거의 그대로이고 지연만 10배로 늘어납니다. 처리량이 막히면 대기 시간이 대신 늘어난다는 이 관계를 눈으로 보고 나면, 프로덕션에서 Connection is not available, request timed out after 30000ms 같은 로그를 만났을 때 무엇을 먼저 볼지 알게 됩니다. 그리고 마지막 단계에서 배우는 것은 측정값을 그대로 쓰지 않는다는 원칙입니다. 측정된 최대 처리량의 70% 만 쓰고 나머지는 헤드룸으로 남깁니다.
준비
mkdir -p /root/lt3 을 해 두세요. 사용할 수 있는 이미지는 미리 받아 둔 python:3.12-alpine, nginx:1.27-alpine, alpine:3.20, busybox:1.36 뿐입니다.
단계
1. /root/lt3/slow.py 를 만듭니다. 조건: 표준 라이브러리 http.server 의 HTTPServer 를 쓸 것(한 번에 하나씩 처리), 각 요청에서 time.sleep(0.05) 로 50ms 지연을 줄 것, 응답 본문에 slow-app 문자열이 들어갈 것. 이 스크립트를 python:3.12-alpine 이미지로 실행해 컨테이너 이름 lt-slow, 호스트 포트 127.0.0.1:8086 으로 띄웁니다. curl http://127.0.0.1:8086/ 응답에 slow-app 이 보여야 합니다.
2. 동시성 1로 측정해 /root/lt3/slow-c1.txt 에 저장합니다. 정상이라면 p95 는 0.04초 이상, 처리량은 40 RPS 이하로 나옵니다(50ms 지연을 직렬 처리하므로 이론상 약 20 RPS).
3. 동시성 10으로 측정해 /root/lt3/slow-c10.txt 에 저장합니다. 확인할 것: 처리량이 동시성 1 대비 2배 미만(거의 그대로), p95 는 2배 이상 증가. 처리량이 막히면 대기 시간이 대신 늘어납니다.
4. /root/lt3/hypothesis.md 에 병목 후보 3개 이상을 - 또는 1. 로 시작하는 항목으로 씁니다. 후보는 서로 다른 계층이어야 합니다(동시성/스레드/워커, CPU, 커넥션 풀, 락 경합, IO·네트워크 중 최소 세 계층). 각 후보마다 어떻게 확인할지도 함께 적으세요.
5. /root/lt3/fast.py 를 만듭니다. 조건: ThreadingHTTPServer(또는 ThreadingMixIn)로 동시 처리가 가능할 것, 지연 50ms 는 그대로 유지할 것, 응답 본문에 fast-app 이 들어갈 것. 컨테이너 이름 lt-fast, 호스트 포트 127.0.0.1:8087 로 띄웁니다. 동시성 10으로 측정해 /root/lt3/fast-c10.txt 에 저장하며, 처리량이 slow-c10.txt 대비 3배 이상이어야 합니다.
6. fast 앱을 동시성 1로도 측정해 /root/lt3/fast-c1.txt 를 만든 뒤, /root/lt3/compare.csv 에 네 행을 정리합니다. 형식은 app,concurrency,rps,p95 이고 행은 slow,1, slow,10, fast,1, fast,10 입니다. 값은 각 결과 파일에서 읽은 것이어야 합니다.
7. /root/lt3/capacity.txt 에 네 줄을 씁니다.
target_rps=300(목표 피크 트래픽)measured_rps=—compare.csv의 fast/10 행의 rpssafe_rps=— 측정값의 70%, 소수점 버림(정수)instances=— ceil(300 ÷ safe_rps), 즉 올림한 정수
8. /root/lt3/report.md 에 리포트를 씁니다. 네 절이 필요합니다: 병목 원인, 증거(측정값), 조치, 용량 결론. 본문에 개선 전 처리량, 개선 후 처리량, 필요한 인스턴스 수 세 숫자가 그대로 들어가야 합니다.
참고
- 컨테이너 실행 예:
docker run -d --name lt-slow -p 127.0.0.1:8086:8080 -v /root/lt3:/app python:3.12-alpine python /app/slow.py(스크립트 안에서는 컨테이너 내부 8080 에 바인드). - 서버가 뜨지 않으면
docker logs lt-slow로 확인하세요. 파이썬 핸들러에서Content-Length헤더를 빼먹으면 클라이언트가 연결 종료를 기다려 측정이 왜곡됩니다. - 측정 예:
hey -n 100 -c 1 http://127.0.0.1:8086/ > /root/lt3/slow-c1.txt 2>&1,hey -n 200 -c 10 ..., fast 는 빠르므로-n 1000 -c 10정도가 좋습니다. - 올림 계산:
awk -v t=300 -v s="$SAFE" 'BEGIN{n=t/s; r=int(n); if(n>r) r=r+1; print r}'. - 흔한 실수 1: 개선을 '지연 줄이기'로 이해하는 것. 지연 50ms 는 그대로 두고 대기를 없애야 합니다. 지연은 그대로인데 처리량만 크게 늘어나는 것이 정답입니다.
- 흔한 실수 2:
safe_rps를 반올림하는 것. 버림이어야 하고, 반대로instances는 올림이어야 합니다. - 흔한 실수 3: 8단계 리포트에 "처리량이 크게 늘었다" 같은 문장만 쓰는 것. 근거는 숫자여야 합니다.
단계 8개
- 느린 애플리케이션 띄우기
- 동시성 1에서 기준 측정
- 동시성을 10배로 올려 보기
- 병목 가설 세우기
- 동시 처리로 바꾸기
- 개선 전후 비교표
- 필요한 인스턴스 수 계산
- 분석 리포트 작성