테스트를 돌리면 숫자와 그래프가 나온다. 하지만 이 숫자들이 "좋은 건지 나쁜 건지"를 판단하려면 패턴을 읽을 줄 알아야 한다. 이 편에서는 TPS와 MTT 그래프에서 나타나는 세 가지 패턴을 익히고, VUser를 단계별로 올려가며 포화 곡선을 그리는 방법을 다룬다.
결과 페이지 구성
테스트가 끝나면 Performance Test 상세 페이지에 결과가 표시된다. 핵심 요소는 이렇다.
- 요약 테이블 — TPS 평균, Peak TPS, MTT 평균, Error Rate가 숫자로 표시된다
- TPS 그래프 — X축이 시간, Y축이 초당 처리량. 테스트 Duration 동안 TPS가 어떻게 변했는지 보여준다
- MTT 그래프 — X축이 시간, Y축이 평균 응답 시간(ms). TPS 그래프와 나란히 본다
- CSV 다운로드 — 상세 데이터를 CSV로 내려받을 수 있다. 별도 분석이 필요할 때 사용한다
TPS 그래프의 세 가지 패턴
TPS 그래프는 서버의 상태를 가장 직관적으로 보여주는 지표다. VUser를 올려가면서 세 가지 패턴 중 하나가 나타난다.
정상 — 서버에 여유가 있다
TPS가 일정한 수평선을 그린다. 약간의 출렁임은 있지만 전체적으로 안정적이다.
식당 비유: 주방에 여유가 있어서 주문이 들어오는 대로 바로 처리한다. 대기열이 없다.
TPS
│ ┌─────────────────────┐
│ │ │ ← 안정적 수평선
│───────┘ └──
│
└────────────────────────────── 시간
↑ 워밍업
앞부분에 TPS가 올라가는 구간은 JVM 워밍업이다. JIT 컴파일러가 핫 코드를 최적화하는 시간이다. 워밍업 이후의 안정 구간이 실제 성능이다.
포화 — 서버가 한계에 도달했다
VUser를 올렸는데 TPS가 더 이상 올라가지 않고 수평을 유지한다. 이전 VUser 단계와 TPS가 비슷하다.
식당 비유: 가스레인지 4개가 전부 가동 중이다. 손님이 더 와도 동시에 만들 수 있는 요리 수는 4개로 똑같다. 나머지 주문은 대기열에 쌓인다.
TPS
│ ┌──────────────────┐
│ │ ← 올라가다 멈춤 │
│ ┌───┘ └──
│ ┌───┘
│──┘
└────────────────────────────── 시간
이 시점의 TPS가 서버의 최대 처리량이다. 리소스(CPU, 커넥션, 스레드) 중 하나가 풀가동 상태라는 뜻이다.
과부하 — 서버가 무너지고 있다
TPS가 올라가다가 꺾이면서 하락한다. 또는 급격하게 출렁인다.
식당 비유: 주방이 너무 붐벼서 요리사들이 서로 부딪힌다. 혼자였을 때보다 오히려 효율이 떨어진다. 주문 실수(에러)도 발생한다.
TPS
│ ╱╲
│ ╱╱ ╲╲ ← 불안정하게 출렁
│ ╱╱ ╲╲
│╱╱ ╲───── ← 하락
└────────────────────────────── 시간
Error Rate가 0%에서 벗어나기 시작한다면 과부하 신호다. 이 상태에서 VUser를 더 올리면 서버가 응답을 아예 못 하게 될 수 있다.
MTT 그래프의 의미
MTT 그래프는 TPS 그래프의 거울이다. TPS와 반대 방향으로 움직인다.
정상 상태
MTT가 일정하게 유지된다. VUser가 늘어도 각 요청의 응답 시간이 변하지 않는다. 서버가 추가 부하를 감당하고 있다는 뜻이다.
포화 상태
TPS는 안 올라가는데 MTT가 서서히 증가한다. 처리 속도는 한계인데 요청은 더 많이 들어오니까, 대기열에서 기다리는 시간이 늘어나는 것이다.
식당에서 주방은 풀가동인데 손님이 더 들어오면, 음식 만드는 시간은 같아도 대기 시간이 늘어나서 전체 시간이 길어진다.
과부하 상태
MTT가 급격하게 치솟는다. 수십 ms이던 응답이 수백 ms, 수 초로 늘어난다. 타임아웃이 발생하기 시작한다.
TPS와 MTT를 함께 읽기
두 그래프를 나란히 놓고 보면 서버 상태를 정확하게 판단할 수 있다.
| 서버 상태 | TPS | MTT | 의미 |
|---|---|---|---|
| 여유 | VUser 비례 상승 | 일정 | 리소스가 충분하다 |
| 포화 | 수평 유지 | 서서히 상승 | 최대 처리량 도달, 대기열 발생 |
| 과부하 | 하락 또는 출렁 | 급격히 상승 | 리소스 경합으로 효율 저하 |
리틀의 법칙으로 검증
01편에서 소개한 리틀의 법칙을 실전에 적용해본다.
VUser = TPS × MTT(초)
VUser 50, TPS 250, MTT 200ms(0.2초)이면: 50 = 250 × 0.2 = 50. 계산이 맞는다.
같은 테스트에서 VUser를 100으로 올렸더니 TPS가 여전히 250이라면, MTT는 100 ÷ 250 = 0.4초, 즉 400ms가 된다. TPS는 그대로인데 MTT만 2배가 됐다. 서버가 포화 상태에서 추가 VUser를 대기시키고 있다는 뜻이다.
실제 결과에서 VUser = TPS × MTT가 정확히 맞지 않을 수 있다. Think Time(요청 사이 대기)이 있거나, 에러로 중단된 요청이 있으면 오차가 생긴다. 방향성은 맞지만 소수점까지 일치하지 않는 것은 정상이다.
포화 곡선 그리기
가장 실용적인 분석 방법이다. VUser를 단계별로 올려가며 각 단계의 TPS와 MTT를 기록하면, 서버의 한계가 어디인지 한눈에 보인다.
테스트 실행 방법
VUser 1(기준선)은 이미 측정했다. 나머지를 순서대로 실행한다.
| 단계 | Process | Thread | VUser | Duration |
|---|---|---|---|---|
| 기준선 | 1 | 1 | 1 | 1분 |
| 2단계 | 1 | 10 | 10 | 3분 |
| 3단계 | 1 | 30 | 30 | 3분 |
| 4단계 | 2 | 25 | 50 | 3분 |
| 5단계 | 4 | 25 | 100 | 3분 |
각 단계를 별도 테스트로 만들어서 순차 실행한다. 한 테스트에서 VUser를 동적으로 바꾸는 것보다 결과가 깔끔하다.
2단계부터는 Duration을 3분으로 늘린다. VUser가 많아지면 워밍업과 안정화에 시간이 더 걸리기 때문이다. Ramp-up도 활성화해서 VUser가 점진적으로 추가되도록 한다.
결과 기록 표
각 단계의 안정 구간 값을 기록한다.
| 단계 | VUser | TPS | MTT (ms) | Error Rate | 비고 |
|--------|-------|-----|----------|------------|------|
| 기준선 | 1 | | | 0% | |
| 2단계 | 10 | | | | |
| 3단계 | 30 | | | | |
| 4단계 | 50 | | | | |
| 5단계 | 100 | | | | |
포화점 판단 기준
표를 채우면 이런 패턴이 보인다.
- VUser 1→10→30 : TPS가 비례해서 올라간다 → 서버에 여유가 있다
- VUser 30→50 : TPS가 조금 올라가거나 멈춘다 → 포화 진입
- VUser 50→100 : TPS가 안 올라가거나 오히려 떨어진다 → 포화 또는 과부하
TPS가 더 이상 올라가지 않기 시작하는 VUser 수가 포화점이다. 이 시점의 TPS가 서버의 최대 처리량이다.
예를 들어 VUser 30에서 TPS 180, VUser 50에서 TPS 190이라면 포화점은 VUser 30~50 사이다. 최대 처리량은 약 TPS 190이다.
서버 리소스(CPU, DB 커넥션, Thread 풀) 중 하나가 한계에 도달한 것이다. 어떤 리소스가 병목인지 찾는 방법은 다음 편에서 다룬다.
에러율 해석
Error Rate는 0%가 정상이다. 0%가 아닌 순간부터 주의가 필요하다.
에러가 갑자기 발생하는 경우
특정 VUser 수를 넘으면 에러가 나기 시작한다. 서버의 하드 리밋에 부딪힌 것이다.
- Connection timeout — DB 커넥션 풀이 고갈되어 대기 시간이 타임아웃을 초과했다
- 500 Internal Server Error — 서버 내부 예외. 메모리 부족, 스레드 풀 고갈 등
- 502/503 — 앞단 프록시(Nginx 등)가 백엔드 서버의 응답을 못 받았다
에러가 간헐적으로 발생하는 경우
VUser가 낮아도 가끔 에러가 나온다. 스크립트 문제일 가능성이 높다.
- assert 조건이 너무 엄격하다 (예: 200만 허용하는데 서버가 가끔 204를 돌려준다)
- 인증 토큰이 만료되었다 (Duration이 토큰 유효기간보다 길면)
- 테스트 데이터가 고갈되었다 (모든 VUser가 같은 데이터를 조회/수정해서 충돌)
자주 하는 실수
TPS 그래프의 앞부분(워밍업)은 JVM이 최적화되기 전이라 낮다. 이 구간을 포함하면 평균 TPS가 실제보다 낮게 나온다. 안정 구간의 값을 기준으로 판단한다.
[!WARNING] 한 번의 테스트로 성능을 판단
VUser 1에서 TPS 50이 나왔다고 "이 서버의 처리량은 TPS 50"이라고 결론 내리면 안 된다. VUser를 올리면 TPS도 올라간다. 포화 곡선을 그려야 서버의 실제 한계를 알 수 있다.
[!WARNING] VUser를 한 번에 너무 크게 점프
VUser 1 → 100으로 바로 뛰면 포화점이 어디인지 알 수 없다. 10 → 30 → 50처럼 단계적으로 올려야 "어디서부터 멈추는지"가 보인다.
[!WARNING] 다른 프로그램이 리소스를 잡아먹는 상태에서 테스트
IDE, 브라우저, 다른 Docker 컨테이너가 CPU와 메모리를 점유하면 결과가 왜곡된다. 부하 테스트 중에는 가능한 한 불필요한 프로세스를 종료한다.