[Spring/스프링] - Apache JMeter로 성능 테스트 해보기
Apache JMeter
서버가 제공하는 성능 및 부하를 측정할 수 있는 테스트 도구로 순수 Java 애플리케이션 오픈소스이며 서버나 네트워크 또는 개체에 대해 과부화를 시물레이션하여 강도를 테스트하거나 다양한 부하 유형에서 전체 성능은 분석하는데 사용할 수 있다. 비슷한 부하테스트 도구로는 Apache Benchmark, Ngrinder, Pinpoint, Gatling등이 있다
주요 기능
- 부하 테스트 - 웹, SOP, REST 등 다양한 프로토콜/서버의 성능 및 기능 동작을 측정 가능
- 성능 측정 - 서버의 과부하를 시물레이션하여 강도를 테스트하거나 다양한 부하 유형에서 전체 성능 분석 가능
- 다양한 응답 포맷 지원 - HTML, JSON, XML, 텍스트 등 다양한 응답 포맷을 지원하여 추출된 데이터 분석
- CI/CD 연동 - CI 또는 CD 툴과 연동하여 자동화된 테스트 수행
- GUI 및 CLI 지원 - 그래픽 사용자 인터페이스와 명령중 인터페이스를 모두 제공
- 시나리오 기반 테스트 - 사용자 동작을 시물레이션하여 실제 사용자 환경과 유사한 부하 테스트 수행
- 다양한 플러그인 지원 - https://jmeter-plugins.org/
JMeter Plugins :: JMeter-Plugins.org
jmeter-plugins.org
주요 개념
- Thread Group - 몇 개의 쓰레드가 동시에 요청을 보내는 지
- Sampler - 어떤 유저가 해야 하는 액션
- Listener - 응답을 받았을 때 어떤 동작을 취하는 지(검증, 리포트, 그래프 그리기 등)
- Configuration - Sampler 또는 Listener가 사용할 설정 값(쿠키, JDBC 커넥션 등)
- Assertion - 응답 결과의 성공 여부를 판단하는 조건(응답 코드, 본문 내용 등)
설치 방법
JMeter 설치
appache-jmeter-5.6.3.zip sha512 pgp를 다운로드
설치 경로로 이동
./jmeter 입력
테스트 방법
Spring boot 코드 수정
1초당 최소 1000명의 요청을 처리하는 것이 목표
인증은 JWT로 구현한 상태, 그러나 1000개의 로그인 요청을 보내서 토큰 1000개를 만드는 과정은 상당히 번거로움, pass 본인 인증 + U.saint(숭실대학교) 인증 + JWT토큰 생성 단계
로그인 API의 코드 수정(임의로 1000개의 토큰 생성 후 csv파일에 저장)
BufferedWriter writer = null;
int batchSize = 1000;
try {
writer = new BufferedWriter(new FileWriter("tokens.csv"));
writer.write("accessToken,refreshToken\n");
for (int i = 1; i <= 100_000; i++) {
// 유저 생성
User testUser = User.builder()
.id(i)
.name("TestUser" + i)
.major("TestMajor" + i)
.degree(UserDegree.BACHELOR)
.status(UserStatus.GRADUATED)
.grade(3)
.semester(7)
.isAdmin(0)
.createdAt(LocalDateTime.now())
.isPaid(true)
.isPushNotificationEnabled(true)
.build();
userRepository.save(testUser);
// 토큰 생성
JWTPayLoadVo jwtPayLoadVo = JWTPayLoadVo.builder()
.studentId(i)
.name("TestUser" + i)
.major("TestMajor" + i)
.build();
String testAccessToken = tokenProvider.generateAccessToken(jwtPayLoadVo);
String testRefreshToken = tokenProvider.generateRandomHashToken(50);
// Redis 저장
RefreshToken testRefreshTokenDB = RefreshToken.builder()
.refreshToken(testRefreshToken)
.accessToken(testAccessToken)
.studentId(i)
.name("TestUser" + i)
.major("TestMajor" + i)
.build();
refreshTokenRepository.save(testRefreshTokenDB);
// CSV 파일 저장
writer.write(testAccessToken + "," + testRefreshToken + "\n");
// 중간 flush + 잠깐 쉬기
if (i % batchSize == 0) {
writer.flush();
System.out.println(i + " users processed...");
Thread.sleep(100); // 과부하 방지용 짧은 딜레이
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
환경 세팅
Thread Group 생성 - 본인은 한 개의 요청에 1000명이 몰린다는 가정으로 테스트를 진행할 예정
CSV Data Set Config 생성
필자는 API로 요청을 보낼 때마다 인증 헤더에 JWT토큰을 담아야 하는 상황이며 1000개의 토큰을 일일이 입력할 수 없기 때문에 토큰들을 저장한 CSV 파일을 읽어올 수 있도록 설정하였음
HTTP Request 생성
본인이 원하는 HTTP Method와 API경로, Port 번호를 입력하면 됨, 배포되어 있는 서버가 없는 경우에는 사진처럼 Server Name or IP를 localhost로 작성 , 요청값은 본인이 API에 맞게 신청, 필자는 POST 요청이기 때문에 Body Data를 선택
현재 사진에서 보면 HTTP Request가 Loop Controller라는 폴더 안에 감싸져 있는 구조인데 이는 본인이 원하는 시물레이션 환경에 따라서 수정 가능. 필자는 한 명의 유저가 최대 3번의 요청까지 가능하다는 가정으로 시나리오를 구성하여서 HTTP Request를 Loop Controller안으로 넣음
HTTP Header Manager 생성
이 부분도 선택사항으로 JWT토큰은 보통 Request Header에 인증 토큰을 담아서 요청을 보냄 HTTP Header Manager가 Request Header를 수정할 수 있게 해주는 역할 그래서 사진처럼 accessToken을 Authorization에 refreshToken을 Refresh-Token에 담아줌
Response Assertion 생성
이 부분은 API 요청을 날렸을 때 JMeter에서 API에 대한 응답의 성공 또는 실패를 선택하는 기준이다. 예를 들어, 두 번째 사진과 같이 응답에 대한 성공 메시지를 Success로 설정한 경우에는 Patterns to Test를 Success로 입력하면 된다. 그러면 JMeter에서는 이 응답 메시지만 성공으로 간주하여 결과를 분석해준다.
응답 분석 관련
아래 사진에서 보면 View Results in Table, View Results Tree 등을 볼 수 있는데 이는 모두 응답에 대한 결과를 볼 수 있게 해주는 기능이다. 본인의 취향 또는 기준에 맞춰서 추가하면 된다.
추가로 아래 사진처럼 설정하게 되면 결과를 csv파일로 저장할 수 있다
테스트 실행
Summary Report
아래 사진처럼 결과를 분석해 볼 수 있다. 초당 230개의 요청을 처리하였고 Error가 99퍼이다. 코드를 그렇게 정교하게 짜여진 것은 아니며, 예약 요청의 경우 모든 요청에 대해서 201의 응답 코드를 반환할 수 없는 상황이며 각각의 예외 메시지들은 성공으로 간주하지 않기 때문에 이런 결과가 나왔다고 예상
Graph Results
그래프로 보여주는 수치인데 잘 모르겠다…
View Results In Table
한계
- GUI가 제공되다 보니 사용자가 사용하거나 보는 입장에서는 상당히 편리한 것 같다.
- 좀 더 많은 요청을 해보기 위해 사용자 수를 10000명 이상으로 늘려서 Loop를 실행해보니 생각만큼 짧은 시간 안에 많은 요청을 보낼 수는 없다는 것 같다는 생각이 든다
- 단순 시나리오나 간단한 성능 테스트로는 유용하지만, 티켓팅 같이 고성능/고부하 시나리오에서는 한계가 존재하는 것 같다. 그래서 조금 찾아보니 아래와 같은 글들을 찾아보게 되었다.
- GUI 기반 실행의 자원 소모
- GUI 모드는 시각적인 인터페이스 때문에 CPU, 메모리 리소스를 많이 사용함
- 고부하 테스트를 위해서는 반드시 Non-GUI 모드 (jmeter -n) 로 실행해야 한다고 함
- 싱글 머신의 병목
- JMeter는 기본적으로 싱글 머신에서 실행되기 때문에 요청 수가 많아질수록 CPU, 메모리, 네트워크 I/O에서 병목이 발생
- 분산 테스트(Distributed Testing) 기능을 사용하여 여러 머신에 테스트를 분산해야 한다
- Java 기반의 한계
- Java는 GC(Garbage Collection)나 스레드 기반 구조 때문에 초당 수만 건의 HTTP 요청을 처리하기에는 구조적으로 한계가 있음
- 대규모 트래픽 시 부적합
- 1초당 15,000건 이상의 요청을 지속적으로 보낼 경우, JMeter의 기본 설정으로는 테스트 인프라가 감당하지 못하는 경우가 많다
- GUI 기반 실행의 자원 소모
- 그래서 좀 더 보다 정확한 결과를 위해 Node.js 기반의 Artillery를 사용해보려고 함.