본문 바로가기
Framework/Spring Boot

[Spring Boot] JMeter 부하 테스트: 트래픽 폭탄을 쏘고 병목을 부숴라

반응형

여러분이 만든 수강신청 시스템이 내일 아침 9시에 오픈합니다. 개발자 본인이 로컬 환경에서 포스트맨(Postman)으로 /api/enroll API를 한 번 클릭했을 때는 응답 속도가 0.1초(100ms)로 완벽했습니다. 하지만 다음 날 아침 9시, 1만 명의 학생이 동시에 접속(새로고침 연타)을 시작하자마자 서버는 "Connection Timeout"을 뱉으며 장렬히 전사했습니다. 1만 개의 트래픽이 쏟아질 때 DB 커넥션 풀(HikariCP)이 버티는지, CPU가 몇 퍼센트까지 치솟는지, 톰캣(Tomcat)의 기본 쓰레드 200개가 부족하진 않은지 미리 검증하지 않은 대가입니다. 오픈 당일 사직서를 쓰고 싶지 않다면, "실제 유저 1만 명이 접속하는 것과 똑같은 환경을 시뮬레이션해서 가짜 트래픽 폭탄을 서버에 쏴보는 훈련"이 필수적입니다. 이 극한의 스트레스 테스트를 도와주는 전 세계 표준 도구가 바로 Apache JMeter(제이미터)입니다.

이번 [Spring Boot 실무 완벽 가이드] 시리즈에서는 "서버의 한계를 측정하고 병목(Bottleneck)을 찾아내는 부하 테스트(Load Testing) 완벽 가이드"에 대하여 딥다이브 해보겠습니다.

💣 1. 부하 테스트의 핵심 용어: TPS와 응답 속도

부하 테스트를 할 때 경영진에게 보고해야 할 가장 핵심적인 지표 두 가지가 있습니다.

  • TPS (Transactions Per Second): 1초에 서버가 처리해 낸 요청의 개수입니다. (JMeter에서는 Throughput이라고 부릅니다.) "우리 서버는 초당 500개의 결제를 처리할 수 있습니다(500 TPS)."라는 것이 백엔드 성능의 절대적 척도가 됩니다.
  • 응답 속도 (Response Time): 요청을 보내고 응답받기까지 걸린 시간입니다. 평소엔 0.1초 였다가도 유저가 몰려 대기열(Queue)에 쌓이면 3초, 5초로 늘어납니다. 한국인의 인내심 한계인 '3초'를 넘어가면 사실상 서버가 죽은 것과 다름없습니다.

⚙️ 2. JMeter 시나리오 세팅: 가짜 1만 명 만들기

JMeter(GUI 환경)를 실행하고 트래픽 시나리오를 구성해 봅니다.

1. Thread Group (가상 유저 세팅)
몇 명이 얼마나 빠르게 접속할지 정합니다.
- Number of Threads (users): 1000 (가상 유저 1000명)
- Ramp-up period (seconds): 10 (10초 동안 1000명이 서서히 들어오게 만듦. 즉 1초에 100명씩 투입!)
- Loop Count: 1 (유저 1명당 API를 1번만 호출)

2. HTTP Request (공격할 타겟 지정)
스프링 부트 서버의 IP와 GET /api/v1/courses/1/enroll 같은 API 경로를 적습니다.

3. View Results in Table & Summary Report (결과 보기)
초록색 재생(▶) 버튼을 누르면, JMeter가 내 PC의 네트워크를 풀가동하여 1초에 100발씩 서버를 향해 트래픽 폭격을 날립니다!


🩺 3. 병목(Bottleneck) 찾기와 튜닝의 예술

테스트가 끝난 후 Summary Report를 보니 Error% 가 15%가 찍혀있고, 평균 응답 속도가 8초가 넘습니다. 서버가 버티지 못한 것입니다! 이제 21단계에서 배운 Actuator + Grafana 대시보드를 열어서 서버 상태를 엑스레이 찍듯 들여다봐야 합니다.

  • [상황 A] DB 커넥션 수치가 바닥을 치고 있다면?
    1,000명의 유저가 HikariCP의 기본값인 10개의 커넥션을 서로 차지하려 싸우느라 타임아웃(Timeout)이 발생한 것입니다. 풀 사이즈를 10에서 50으로 늘려보고 다시 쏴봅니다.
  • [상황 B] CPU 사용률이 99%를 치고 있다면?
    애플리케이션 내부에 복잡한 for 루프 연산이나 암호화 작업(BCrypt)이 CPU를 갉아먹고 있는 것입니다. 코드를 리팩토링하여 연산을 줄이거나, 스케일 아웃(서버 증설)을 해야 합니다.
  • [상황 C] 무거운 쿼리가 발견됐다면?
    JPA N+1 문제가 터졌거나, DB 테이블에 인덱스(Index)가 안 걸려 있어서 디스크를 풀 스캔하느라 느려진 것입니다. 쿼리를 최적화하거나 13단계에서 배운 Redis 캐시(Cache)를 덮어씌워 DB 부하를 0으로 만들어 버립니다.

🎯 4. 마무리 및 다음 단계

지금까지 오픈 당일 서버가 다운되는 대참사를 막기 위해, JMeter를 활용해 트래픽 폭탄을 던져보고 모니터링 툴과 연계하여 병목 지점을 찾아내 튜닝하는 시니어급 백엔드 성능 최적화(Performance Tuning) 기법에 대해 아주 치밀하게 다루어 보았습니다. 이제 여러분은 "우리 서버는 1만 명도 거뜬합니다!"라고 당당하게 외칠 수 있습니다.

튜닝을 극한으로 깎아보니, 전통적인 스프링 부트(Spring Web MVC)가 가진 태생적인 한계에 부딪히게 됩니다. 하나의 요청이 들어올 때마다 하나의 쓰레드(Thread)를 꽉 물고(Blocking) 놔주지 않으니, 트래픽이 폭주하면 톰캣의 쓰레드 200개가 순식간에 고갈(Thread Exhaustion)되어 서버가 뻗어버리는 것이죠. "쓰레드 하나가 절대 멈춰있지(Block) 않고, 일 시켜놓고 바로 다음 유저를 응답하러 날아다니면 안 될까?" 이 미친 효율의 비동기 논블로킹(Non-Blocking) 철학을 실현한 차세대 기술이 등장했습니다. 이어지는 29단계 포스팅에서는 "넷플릭스, 라인 등 대규모 기업이 사랑하는 적은 자원으로 극한의 성능을 뽑아내는 마법!" Spring WebFlux(웹플럭스)와 리액티브 프로그래밍(Reactive Programming) 아키텍처에 대해 아주 뼈 때리게 파헤쳐 보겠습니다!

반응형