| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 자바스크립트
- vscode
- list
- junit5
- 정규식
- 자바문법
- input
- javascript
- 배열
- string
- CSS
- ArrayList
- 테스트자동화
- java테스트
- 단위테스트
- Eclipse
- Visual Studio Code
- 인텔리제이
- Java
- Array
- 자바
- junit
- SpringBoot
- IntelliJ
- 스프링부트
- math
- HashMap
- js
- 문자열
- html
- Today
- Total
어제 오늘 내일
[Spring Boot] "내 서버는 몇 명까지 버틸까?" nGrinder로 부하 테스트(Load Test) 하고 병목 지점 찾기 본문
[Spring Boot] "내 서버는 몇 명까지 버틸까?" nGrinder로 부하 테스트(Load Test) 하고 병목 지점 찾기
hi.anna 2026. 3. 29. 08:57
서버 개발의 끝은 배포가 아니라 성능 튜닝입니다.
API 응답 속도가 0.1초인지 3초인지는 혼자 테스트할 땐 모릅니다.
네이버가 만들고 오픈소스로 공개한 nGrinder는 엔터프라이즈급 부하 테스트 도구입니다. 스크립트 하나로 수천 명의 가상 사용자(VUser)가 동시에 로그인하고, 상품을 조회하고, 결제하는 시나리오를 만들 수 있습니다.
1. nGrinder 구조 (Controller + Agent)
nGrinder는 크게 두 가지로 나뉩니다.
- Controller (지휘관): 웹 UI를 제공하고, 테스트 스크립트를 관리하고, 결과를 보여줍니다.
- Agent (병사): 실제 부하(트래픽)를 발생시키는 일꾼입니다. 서버가 여러 대라면 Agent도 여러 대 띄워서 엄청난 트래픽을 만들 수 있습니다.
2. 설치하기 (Docker Compose)
설치가 까다롭기로 유명하지만, 도커를 쓰면 5분 컷입니다.
docker-compose.yml
version: '3'
services:
controller:
image: ngrinder/controller
restart: always
ports:
- "80:80" # 웹 UI 접속 포트
- "16001:16001"
- "12000-12009:12000-12009"
volumes:
- ./ngrinder-controller:/opt/ngrinder-controller
agent:
image: ngrinder/agent
restart: always
links:
- controller
environment:
- CONTROLLER_HOST=controller
volumes:
- ./ngrinder-agent:/opt/ngrinder-agent
docker-compose up -d 후 http://localhost:80에 접속합니다. (초기 ID/PW: admin/admin)
3. 테스트 스크립트 작성 (Groovy)
nGrinder는 Groovy(자바 문법)로 스크립트를 짭니다. JUnit 테스트 코드와 거의 똑같습니다.
- Script 메뉴 -> Create -> Groovy Maven Project 선택
- 테스트할 URL 입력 (예:
http://192.168.0.5:8080/api/products)
- 주의:
localhost라고 적으면 안 됩니다! (도커 컨테이너 기준이라 자기 자신을 찾게 됨) 내 PC의 사설 IP를 적으세요.
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptgen.HTTPRequest
import HTTPClient.NVPair
import HTTPClient.Cookie
import HTTPClient.HTTPResponse
import HTTPClient.CookieModule
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
@BeforeProcess
public static void beforeProcess() {
test = new GTest(1, "Product API Test")
request = new HTTPRequest()
}
@BeforeThread
public void beforeThread() {
// 로그인 쿠키 설정 등이 필요하면 여기서
grinder.statistics.delayReports = true
}
@Test
public void test() {
// GET 요청 보내기
HTTPResponse result = request.GET("http://192.168.0.5:8080/api/products")
// 응답 코드가 200인지 검증
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response is 301 or 302.")
} else {
assertThat(result.statusCode, is(200))
}
}
}
4. 테스트 실행 (Performance Test)
이제 시나리오를 돌려봅시다.
- Performance Test 메뉴 -> Create Test
- Basic Configuration:
- Agent: 1 (에이전트 몇 대 쓸래?)
- Vuser per Agent: 100 (에이전트 1대당 가상 사용자 100명 = 총 100명 동시 접속)
- Duration: 1분 (1분 동안 공격!)
- Target Host: 내 서버 IP
- Save and Start 클릭!
5. 결과 분석: TPS가 떨어지면 망한 겁니다
테스트가 끝나면 그래프가 나옵니다. 가장 중요한 지표는 TPS (Transactions Per Second)입니다.
- TPS 그래프가 우상향하거나 일정하다: 서버가 잘 버티고 있음.
- TPS가 오르다가 뚝 떨어진다: 서버 한계 도달 (DB 커넥션 풀 부족, 메모리 부족, 스레드 풀 고갈 등).
- Errors 그래프가 치솟는다: 서버가 500 에러를 뱉기 시작함.
튜닝 포인트 찾기:
이때 우리가 배웠던 VisualVM이나 Pinpoint를 같이 켜두고 보면, 어디서 막히는지(병목) 정확히 알 수 있습니다.
- "어? DB 커넥션을 못 얻어서 대기하네?" -> HikariCP 늘리기
- "어? 쿼리가 느리네?" -> 인덱스 추가 또는 Redis 도입
마치며
오늘의 결론입니다.
- nGrinder는 가상의 사용자(Bot)를 만들어 내 서버를 디도스(DDoS) 공격하듯 테스트한다.
- TPS가 꺾이는 지점이 내 서버의 최대 성능 한계점이다.
- 부하 테스트 없이 오픈하는 것은 안전벨트 없이 고속도로를 달리는 것과 같다.
이제 여러분은 "이 서버는 동시 접속자 5,000명까지는 거뜬합니다!"라고 숫자로 증명할 수 있는 개발자가 되었습니다.
다음 포스팅에서는 이 시리즈의 마지막 주제, "새로고침 없이 알림이 오네요?" HTTP가 아닌 양방향 통신, WebSocket과 STOMP로 실시간 채팅방 만들기에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
