조만간 트래픽이 평소보다 많이 늘어날 일이 예정되어 있어 API 중 병목이 발생할 수 있는 부분이 있는지 확인을 하기 위해 부하 테스트를 사전에 진행하게 되었다. 사실 계속 해야겠다고 속으로만 생각했지만 미루고 있었는데 이번 기회에 Apache JMeter를 활용해 부하 테스트를 도입하였다.
https://jmeter.apache.org/
Apache JMeter는 익히 알려진 성능 테스트 도구로서 CLI, GUI 모두 지원한다.
그 중에서 CLI는 CI / CD 파이프라인에서 배포 전 성능 테스트를 할 때 많이 사용한다.
기본적으로 JMeter는 테스트 플랜을 기반으로 동작하며, 테스트 플랜 아래에는 다음과 같은 요소들이 있다.
이렇게 성능 테스트에 필요한 시나리오를 여러 가지 종류의 컴포넌트를 조합하여 다양하게 처리할 수 있으며 다양한 플러그인을 통해 기능 확장이 가능하며 간단한 리포팅 기능 역시 제공하고 있다.
일단 필자는 Apache JMeter를 사용해 본 경험이 많지 않았고, CI / CD 파이프라인에 배포 전 성능 테스트를 추가하는 것은 아직 시기상조라고 생각하여 GUI를 활용해 부하 테스트에 필요한 테스트 플랜을 만들게 되었다.
테스트 플랜을 생성한다. 필자의 경우 여러 개의 쓰레드 그룹을 순차적으로 테스트하고 싶어서 '쓰레드 그룹을 순차적으로 실행' 옵션을 활성화하였다. 이 경우, 쓰레드 그룹의 순서대로 테스트가 진행이 된다. 이 화면에서 테스트 플랜 내에서 전역적으로 사용할 사용자 정의 변수들을 설정해줄 수 있으며 설정 엘리먼트에서도 추가로 사용자 정의 변수들을 설정할 수 있다.
위에서 언급한 대로 테스트 플랜에서 설정 엘리먼트 > 사용자 정의 변수로 테스트 플랜에서 설정했던 사용자 정의 변수와는 별도로 추가로 변수들을 설정할 수 있다.
그리고 모든 API 요청에 공통적으로 사용할 HTTP 요청 세팅을 하기 위해 설정 엘리먼트 > HTTP 요청 기본 설정을 추가할 수 있다.
여기서 프로토콜, IP 또는 도메인 주소, 포트 등을 지정할 수 있는데 위의 사용자 정의 변수와 연계하여 테스트하려는 환경에 따라 동적으로 테스트 대상을 변경해줄 수 있다. (ex : 로컬 환경, 개발 환경 등...)
또한 설정 엘리먼트 > HTTP 헤더 관리자를 통해 공통적인 HTTP 헤더 설정도 가능한데, Content-Type 헤더나 인증에 사용될 Authorization 헤더 등을 테스트 플랜 내에서 공통적으로 사용할 수 있도록 지정할 수 있다. (물론 이 설정들은 쓰레드 그룹 단위에서 재정의 또한 가능하다)
기본적으로 API는 인증이 필요하지 않은 경우도 있지만 인증이 필요한 API들도 반드시 있기 때문에 Authorization 헤더를 통해 인증을 해야 하는 API들을 위한 별도의 세팅이 필요하다.
필자의 경우 테스트하려는 API 서버에 로그인 API가 있었기 때문에 다음과 같은 흐름으로 인증 처리를 진행하였다.
이를 위해 setUp 쓰레드 그룹을 먼저 생성하는데 여기서 setUp 쓰레드 그룹은 사전 준비 작업을 분리하기 위해 만들어진 별도의 쓰레드 그룹으로 보통 메인 쓰레드 그룹 실행 이전에 실행된다.
로그인은 1번만 처리하면 되므로 사용자 수 및 Ramp-Up 시간을 각각 1초로 처리하였다.
그 후 로그인 API를 호출하는 HTTP 요청을 생성(이 때 로그인에 필요한 정보는 사용자 정의 변수에서 받아 온다)한다.
// 로그인 API 요청
{
"email": "${USERNAME}", // 사용자 정의 변수
"password": "${PASSWORD}" // 사용자 정의 변수
}
// 로그인 API 응답
{
"data": {
// ...
"accessToken": "액세스 토큰...."
}
}
그리고 사후 처리기에서 JSON Extractor를 설정한 뒤, 응답 값에서 accessToken을 파싱해서 변수(TOKEN)로 저장한다.
그 다음, JSR223 사후처리기를 통해 위에서 저장한 변수를 JMeter Properties로 지정한다.
// JSR 사후처리기 스크립트
String token = vars.get('TOKEN')
if (!token.isEmpty()) {
props.put('ACCESS_TOKEN', token)
log.info("토큰 추가 완료");
}
이렇게 하면 메인 쓰레드 그룹이 실행되기 전 setUp 쓰레드 그룹이 실행되면서 로그인 API 호출 -> 토큰 파싱 -> JMeter Properties로 저장을 하게 된다.
이후, 테스트 플랜 내에서 (테스트 플랜 내 전체 적용) HTTP 헤더 관리자를 설정하는데 Authorization 헤더의 값을
Bearer ${__property(ACCESS_TOKEN)}
로 지정해준다. 이는 JMeter Properties로 저장된 토큰을 파싱해서 헤더에 넣어주는 것이다.
이후부터는 각 API에 대한 테스트용 쓰레드 그룹을 생성하고 내부에 HTTP 요청을 생성한다.
필자의 경우 사용자 수는 100, Ramp-Up 시간을 60초로 지정하였고, 스레드의 지속 시간을 300ms로 설정하였다.
여기서 쓰레드 그룹을 하나로 하고 API 요청을 몰아넣을 수도 있고, API 요청마다 쓰레드 그룹을 만들 수도 있다.
중요한 API이고 테스트에 대한 설정을 세부적으로 하고 싶다면 API 요청마다 쓰레드 그룹을 만들면 좋을 것 같다.
쓰레드 그룹까지 전부 생성했다면, 테스트 플랜 내에 리스너를 생성해준다.
대표적인 리스너들의 예시는 다음과 같다.
이후 테스트를 실행하여 결과값을 확인한다.
일단 요청 / 응답 및 사용자 정의 변수, 프로퍼티 등에 대한 디버깅을 위해서 디버깅 샘플러를 사용하면 좋다.
디버깅 샘플러를 추가한 후 결과 트리 보기를 확인하게 되면, 요청에 대한 정보를 확인할 수 있다.
또, 사전/사후처리기에 대한 디버깅이 필요할 경우가 있는데 (필자도 초기에 고생을 좀 했다) GUI의 경우 옵션에서 로그 뷰어를 활성화하면 테스트에 대한 로그를 확인할 수 있다.
예전에는 API에 대한 성능 비교만 하려고 아주 간단하게 썼던 JMeter를 좀 더 본격적으로 사용하게 되다 보니 GUI를 사용해 테스트 플랜을 구성하게 되었는데, 실행 자체는 CLI에서 해볼 필요가 있을 것 같다.
그리고 성능 테스트에 대한 설정값을 튜닝하는 것도 좀 더 공부를 해야 할 것으로 보인다.
그래도 테스트에 필요한 세팅을 이제 할 수 있게 되었으니 많이 활용해보면서 성능 개선을 해봐야겠다.