부하 테스트 · 부하 발생기가 병목일 때 · 이론
발생기가 못 만든 부하는 서버의 한계가 아니다
한 줄 요약
부하 발생기가 만들어 내지 못한 부하는 서버의 한계가 아니다. 시험 결과를 읽기 전에 요청한 부하와 실제로 걸린 부하가 같았는지부터 확인해야 한다.
왜 이게 필요했나
주문 API 의 용량 검토 보고서에 이렇게 적혀 있었다. "초당 500건을 1분간 인가했고 p99 는 210밀리초, 오류는 0건이었습니다." 그런데 같은 시간 서버의 요청 수 지표는 초당 48건에서 평평했다. 500건은 걸린 적이 없었다.
원인은 단순했다. 발생기를 동시성 10으로 돌렸고 응답은 200밀리초였다. 닫힌 루프에서 이 조합이 만들 수 있는 최대 처리량은 10 ÷ 0.2 = 초당 50건이다. 500건은 명령줄에 적힌 희망이었을 뿐, 물리적으로 나올 수 없는 숫자였다. 그런데 도구는 아무 경고도 하지 않고 "p99 210밀리초" 를 예쁘게 출력했다.
이런 결과가 위험한 이유는 서버에 대한 잘못된 믿음을 만들기 때문이다. 실제 부하의 10분의 1만 겪은 서버는 당연히 건강해 보인다. 그 숫자를 근거로 용량을 잡으면 출시 당일에 정확히 열 배의 격차만큼 무너진다.
어떻게 동작하나
닫힌 루프 발생기의 처리량 상한은 리틀의 법칙에서 바로 나온다. 대기 중인 요청 수 L, 도착률 λ, 체류 시간 W 사이에 L = λ × W 가 성립하므로, 동시성을 C 로 고정하면 λ 는 C ÷ 응답시간 을 넘을 수 없다.
| 동시성 | 응답 200밀리초일 때 상한 |
| --- | --- |
| 1 | 초당 5건 |
| 5 | 초당 25건 |
| 10 | 초당 50건 |
여기서 중요한 것은 이 상한이 서버와 무관하다는 점이다. 서버가 아무리 빨라도 발생기가 기다리는 동안에는 요청이 나가지 않는다. 그래서 목표 비율을 채우고 싶으면 동시성을 목표율 × 응답시간 이상으로 잡아야 한다.
두 번째 함정은 비율 옵션의 의미다. hey 의 -q 는 문서에 "Rate limit, in queries per second (QPS) per worker" 라고 적혀 있다 — 워커마다 걸리는 상한이다. -c 5 -q 2 는 초당 2건이 아니라 초당 10건이다. 반대로 총 목표율을 적어 넣었다고 믿으면 실제 부하는 워커 수만큼 부풀어 있다. 도구마다 이 정의가 달라서, 옵션 하나를 잘못 읽으면 시험 전체가 다른 실험이 된다.
세 번째는 발생기 자신의 자원이다. 요청 하나에 소켓 하나가 필요하므로 동시성이 열린 파일 수 한도를 넘으면 그만큼이 조용히 실패한다. 프로세스 수, CPU, 그리고 발생기가 도는 호스트의 네트워크 대역도 같은 방식으로 상한이 된다. 발생기가 대상보다 작은 기계에서 돌면 그 시험은 발생기를 재는 시험이다.
그래서 시험을 설계할 때 순서가 있다. 먼저 걸고 싶은 목표 비율을 정하고, 그 비율에 응답 시간을 곱해 필요한 동시성을 구한다. 초당 500건에 응답 200밀리초라면 최소 100개의 워커가 필요하다. 그다음 그 동시성이 발생기 쪽 한도 안에 들어오는지 확인한다 — 열린 파일 수, 임시 포트 범위, 메모리 순이다. 이 두 계산을 먼저 해 두면 "왜 목표가 안 걸렸는지" 를 나중에 조사할 일이 줄어든다.
현장에서 만나는 모습
가장 흔한 신호는 "동시성을 두 배로 올렸는데 총 처리량도 정확히 두 배가 되고 응답 시간은 그대로" 인 결과다. 서버가 포화됐다면 처리량은 평평해지고 응답 시간이 올라간다. 둘 다 아니라면 서버는 아직 놀고 있고 한계는 발생기 쪽에 있다.
그래서 판정 절차는 두 숫자만 보면 된다. 서버 쪽 처리 시간이 그대로인데 총 비율만 목표에 못 미치면 발생기 한계다. 반대로 처리 시간이 늘면서 비율이 평평해지면 그것이 진짜 포화점이다. 이 구분을 하지 않으면 멀쩡한 서버에 인스턴스를 더 붙이는 일이 벌어진다.
오류 분포도 단서다. "connection refused" 나 "too many open files" 는 서버가 아니라 발생기가 낸 오류일 때가 많다. 특히 파일 디스크립터 한도는 동시성을 크게 올리는 순간 갑자기 나타나는데, 도구는 이것을 그냥 실패 요청으로 세어 오류율에 섞어 버린다. 그러면 "서버가 5% 실패했다" 는 결론이 나온다.
한 가지 더 흔한 모습은 발생기와 대상이 같은 기계에서 도는 시험이다. 이 실습의 파드가 정확히 그 상황이다. 두 쪽이 같은 CPU 를 나눠 쓰면 부하를 올릴수록 발생기 몫이 줄어들고, 그래서 대상이 느려지는 것인지 발생기가 느려지는 것인지 구분할 수 없게 된다. 실제 시험에서는 발생기를 다른 기계에 두어야 하고, 그럴 수 없다면 적어도 이 사실을 보고서에 적어 두어야 한다.
마지막으로, 이 모든 것을 막는 가장 싼 방법은 보고서 양식이다. 요청한 부하와 실제로 걸린 부하를 나란히 적는 칸을 만들어 두면, 둘이 다를 때 누구든 알아본다. 칸이 없으면 아무도 묻지 않는다.
다음 실습에서 할 것
응답이 200밀리초 걸리는 파이썬 서버를 띄우고, 동시성별 처리량 상한을 리틀의 법칙으로 먼저 예측한 뒤 hey 로 실제로 재서 맞춰 본다. -q 가 워커당 상한이라는 것을 실측으로 확인하고, 목표율에 크게 못 미치는 시험을 일부러 만들어 그것이 서버 포화인지 발생기 한계인지 가르는 절차를 세운다. 열린 파일 수 한도를 낮춰 발생기 자신이 한계가 되는 모습도 직접 만들어 보고, 마지막으로 요청한 부하와 실제로 걸린 부하가 함께 적혔는지 확인하는 검사 스크립트를 만들어 남긴다.