키워드 : VU, RPS, p95, 에러율, Tomcat Thread Pool, HikariCP Connection Pool
[9]편에서 모니터링 스택을 갖췄다 !
Prometheus가 메트릭을 긁어오고,
Grafana 대시보드에서 서버의 CPU와 메모리를 실시간으로 볼 수 있게 됐다.
이제 실제로 때려볼 차례다.
🤔 왜 맥북에서 k6를 돌리나?
k6는 맥북에서 실행한다. 갤북(서버)이 아니라.
MacBook (k6 실행) → 트래픽을 생성하는 클라이언트
갤북 → 트래픽을 받아서 처리하는 서버
네트워크 구간도 없고, k6가 CPU를 갉아먹어서 서버 측정값도 오염이 된다.
반면 맥북에서 공인 IP로 쏘면 실제 인터넷 → 공유기 → nginx 전체 경로를 탄다.
즉, 실제 사용자가 접속하는 것과 동일한 구조 !
💥 그래서 얼마나 때릴건데?
CloudAttend는 출석 시스템이다.
서비스의 핵심 트래픽은 출석이 오픈되고, 학생들이 동시에 출석 버튼을 누르는 그 순간에 집중된다.
이 부하테스트에서 가장 먼저 결정해야 할 건 목표 VU다.
정확한 트래픽 수치는 실제 운영 데이터 없이는 알 수 없다.
재학생 수가 20,000명인 상황을 가정을 했지만,
어느 시간대에 트래픽이 얼마나 몰릴지 정확히 알 수 없기 때문에 가장 보수적인 기준을 잡았다.
VU 500명을 안정적으로 처리할 수 있는가?
출석 가능 시간은 수업 시작 시간 전후로 10분이다.
500명의 학생이 동시에 출석 버튼을 누르는 상황은 현실에서 거의 발생하지 않을 듯 하다.
⚙️ 테스트 환경 구성
🔹 테스트 계정
학번이 서로 겹치지 않도록 2000001 ~ 2000500.
500개 계정을 DB에 직접 insert했다.
각 계정은 모두 동일하게 3개 과목을 수강 중인 상태로 구성했다.
출석 시도 API는 "이 학생이 이 수업을 수강 중인가"를 먼저 확인한다.
takes 테이블에 수강 데이터가 없으면 출석시도 자체가 에러가 나기 때문에 사전 구성이 필요했다.
🔹 k6 stages
총 10분. 갑자기 500명을 때리는 게 아니라 서서히 올라가는 구조다.
export const options = {
stages: [
{ duration: '1m', target: 10 }, // 워밍업
{ duration: '2m', target: 100 }, // 가벼운 부하
{ duration: '2m', target: 200 }, // 적당한 부하
{ duration: '2m', target: 350 }, // 현실적인 피크
{ duration: '2m', target: 500 }, // 최대 부하
{ duration: '1m', target: 0 }, // 쿨다운
],
};
📊 k6 테스트 결과

주요 지표는 4가지다.
| 지표 | 결과 | 평가 |
| 로그인 에러율 | 7.23% | 문제있음 |
| 출석시도 에러율 | 0% | Good |
| p95 응답시간 | 658ms | 양호 |
| RPS | 227/s | 홈서버 처리 한계 수준 |
*RPS(Request Per Second)
: 서버가 1초에 처리하는 요청 수.
: 227 RPS는 1초에 227번 요청을 처리했다는 뜻
*p(95) 응답시간
: 전체 요청 중 95%가 이 시간 안에 처리됐음을 의미
: 658ms라는건 대부분의 사용자가 0.65초 안에 응답을 받았다는 뜻
📈 Grafana 서버 메트릭
k6 테스트와 동시에 Grafana로 서버 자원을 모니터링했다.



| 구간 | VU | CPU Busy | Sys Load | RAM Used |
| 적당한 부하 | 200명 | 12% | 8.9% | 29.1% |
| 현실적 피크 | 350명 | 18.8% | 13% | 29.3% |
| 최대 부하 | 500명 | 27.3% (양호) | 30.1% | 29.7% |
| 종료 후 | 0명 | 1% | 2.3% | 28.5% |
🔍 병목의 원인은 서버의 CPU가 아니었다
VU 500명 → CPU 27.3% (자원은 충분히 남아있음)
VU 500명 → 로그인 에러 7.23% (에러는 발생)
CPU가 문제가 아니다.
자원은 남아있는데 에러가 난다는 건 설정값이 병목이라는 뜻이다.
원인은 일단 두 가지로 보인다.
(1) Tomcat thread pool 기본값 : 200개
→ VU가 늘어날수록 thread 소진
→ 대기 큐 초과 → 요청 튕겨냄 → 로그인 에러
(2) HikariCP connection pool 기본값 : 10개
→ thread가 DB connection을 못 잡고 대기
→ connection-timeout 초과 → 에러 발생
CPU 여유가 있음에도 에러가 발생하는 패턴은 thread/connection pool 병목의 전형적인 증상이다.
출석시도 에러율이 0%인걸 보니 이걸 더 뒷받침한다.
로그인을 통과한 요청은 정상 처리됐다.
문제는 로그인 단계에서 thread가 소진되면서 요청 자체가 튕겨나간 것
📌 다음 편 예고
원인은 파악했다. CPU가 아니라 설정값이 병목이다.
그러면 pool 값을 그냥 올리면 해결되는 거 아닐까?
단순한 생각이었지만, 그게 아니었다.
pool size에는 기준이 있다고 한다.
무작정 늘리면 오히려 DB가 터진다.
어떤 기준으로 얼마나 조정해야 하는지, 다음 편에서 튜닝을 진행한다.