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

부하 테스트

부하 생성기가 백프레셔에 협조한다

TT Lab 에서 이어서 보기

한 줄 요약

부하 테스트 결과와 프로덕션 지표가 다르면 대개 부하 테스트 쪽을 의심해야 한다. 실제 사용자는 이전 요청이 느리다고 다음 요청을 미뤄 주지 않기 때문이다.

왜 이게 필요했나

부하 생성기를 초당 1,000건으로 설정했는데 응답 하나가 2초를 잡아먹었다고 하자. 동기식(closed) 생성기는 그 2초 동안 보냈어야 할 2,000건을 보내지 않는다. 시스템이 가장 느렸던 구간의 샘플이 통째로 사라지고, 부하 생성기는 자신이 만들어 낸 백프레셔에 '협조'해 버린다. Gil Tene 이 coordinated omission 이라고 부른 문제다.

증상은 셋이다. 부하를 올려도 p99 가 거의 움직이지 않는다. 보고된 처리량이 설정한 목표치보다 낮은데 지연 분포는 목표치에서 측정한 것처럼 생겼다. 그리고 프로덕션이 부하 테스트보다 꼬리가 훨씬 나쁘다.

모든 실행에 적용할 수용 검사는 한 줄이다. 보고된 실제 처리량이 설정한 목표 처리량과 일치하는가. 일치하지 않으면 그 실행의 지연 분포는 신뢰할 수 없다.

어떻게 동작하나

이것이 closed 모델과 open 모델의 차이다. closed 는 동시성을 고정한다(항상 N개가 처리 중). open 은 도착률을 고정한다(응답 여부와 무관하게 초당 λ건 도착). 실제 사용자는 open 에 가깝다.

리틀의 법칙 L = λ × W 는 두 모델을 잇는다. 시스템 안의 평균 요청 수는 도착률 곱하기 평균 체류 시간이다. RPS 1,000, 지연 100ms 면 동시에 100개가 처리 중이다. 반대로 동시성을 10으로 고정한 closed 실행에서 RPS × 평균 지연이 10 근처가 아니라면, 부하 생성기가 목표 부하를 실제로 내지 못했다는 뜻이다.

커넥션 풀 사이징에도 그대로 쓴다. 인스턴스 하나가 초당 500 요청, 평균 80ms 면 500 × 0.08 = 40 이고, 꼬리 지연을 감안해 1.5배 여유를 주면 권장 풀 크기는 60 이다. 여기서 멈추면 안 된다. 함대 단위로 다시 검산해야 한다. 인스턴스 40대 × 풀 60 = 최대 2,400 연결인데 Postgres max_connections 가 200 이면 배포 직후 접속 폭주로 장애가 난다.

현장에서 만나는 모습

나쁜 벤치마크에는 공통점이 있다. 워밍업이 없어 JIT 가 가열되지 않았고, CPU 주파수를 고정하지 않았고, OS 노이즈를 고려하지 않았고, 한 번만 측정했고, 이상치를 처리하지 않았다.

좋은 절차는 단순하다. 워밍업 10회, 본 측정 100회 이상, 2σ 밖 제거, p50·p95·p99 를 함께 보고, 환경 기록. 콜드 스타트 실측치도 알아 두면 워밍업 길이를 정하는 데 도움이 된다. Python/Node 150–500ms, Go/Rust 50–100ms, JVM 1000–3000ms, GraalVM Native 50–100ms.

테스트 종류도 목적에 따라 나눈다. 부하(예상 트래픽에서 정상 동작), 스트레스(한계점을 찾기 위해 점진 증가), 스파이크(급증 반응), 내구성 Soak(장시간으로 메모리 누수 탐지).

사다리를 어디까지 올릴 것인가

동시성을 올려 가며 재는 사다리에서 사람들이 가장 자주 하는 실수는 너무 일찍 멈추는 것과 너무 오래 끄는 것이다. 처리량이 아직 오르고 있는데 멈추면 한계를 못 찾고, 이미 꺾인 뒤에도 계속 올리면 시험 자체가 시스템을 망가뜨려 뒤의 측정이 전부 못 쓰게 된다.

멈출 지점을 정하는 기준은 셋이다.

  1. 처리량이 더 이상 오르지 않는다. 동시성을 두 배로 올렸는데 처리량이 10% 도 안 늘면 이미 포화다. 그 지점을 무릎이라 부른다.
  2. 오류가 나기 시작한다. 오류율이 올라가면 그 뒤의 지연 숫자는 의미가 없다. 실패한 요청은 대개 빨리 끝나므로 오히려 지연이 좋아 보이는 착시까지 생긴다.
  3. 되돌아오지 않는다. 부하를 낮췄는데 지연이 원래대로 안 돌아오면 무언가 망가진 것이다. 큐가 쌓였거나, 커넥션이 새거나, 메모리가 회수되지 않은 상태다. 이때는 시험을 멈추고 원인을 봐야지 사다리를 계속 올리면 안 된다.

세 번째가 실은 가장 값진 관찰이다. 정상적인 시스템은 부하를 내리면 지연도 따라 내려온다. 그렇지 않다면 그 시스템은 피크를 견디지 못했을 때 스스로 회복하지 못한다는 뜻이고, 그것은 처리량 숫자보다 훨씬 중요한 사실이다. 그래서 사다리를 올린 뒤에는 반드시 내려오면서도 한 번 재야 한다. 올라갈 때와 내려올 때의 같은 동시성에서 값이 다르면 그 차이가 곧 회복력의 결함이다.

한 가지 더. 각 단계는 충분히 길어야 한다. 30초짜리 단계로는 자동 확장도, 캐시 예열도, 큐가 쌓이는 것도 관찰되지 않는다. 최소 몇 분은 유지해야 정상 상태의 값이 나오고, 그 전의 값은 과도 구간이라 따로 표시해 두는 편이 좋다. 특히 자동 확장이 붙어 있는 시스템은 새 인스턴스가 뜨고 준비되기까지의 시간이 통째로 과도 구간이라, 그 시간보다 짧은 단계로는 확장이 실제로 도움이 되는지조차 알 수 없다.

다음 실습에서 할 것

동시성 1·2·5·10·20 사다리를 계획하고 스크립트로 자동 실행합니다. 결과를 표로 만들어 처리량이 더 이상 늘지 않는 무릎 지점을 규칙에 따라 찾고, 리틀의 법칙으로 실행 자체가 유효했는지 검증한 뒤, 도착률을 고정한 open 실행을 따로 돌려 목표 대비 실제 처리량을 대조합니다.