기존 코드를 리팩토링하며 성능의 차이를 눈으로 확인하고 싶어 성능 테스트 도구를 찾게 되었다.
개발자라면 성능 테스트, 부하테스트, 스트레스 테스트.. 등 많이 들어봤을 것이다.
성능테스트에 대해 알아보고, 그 하위 개념인 부하테스트, 강도 테스트, 스파이크 테스트 등에 대해 정리해보자.
그리고 실제 프로젝트를 진행하며 진행한 부하테스트 도구인 Jmeter로 테스트하는 방법과 리팩토링 결과를 비교해보자!
초당 시스템에서 처리하는 요청 수이다.
RPS(Request Per Second) : 1초에 처리하는 HTTP 요청 수
TPS(Transaction per Second) : 1초당 처리할 수 있는 트랜잭션의 갯수
요청을 보내고 첫 번째 응답을 받는데 걸리는 시간을 의미한다.
즉, 사용자가 어떤 요청을 보내고, 시스템이 해당 요청에 대한 응답을 전송하는 데 걸리는 시간이다.
응답시간은 일반적으로 밀리초 단위로 측정되며, 시스템 또는 네트워크의 성능에 대한 전반적인 인식을 제공한다.
요청을 처리하는 데 걸리는 시간을 의미한다.
즉, 시스템이 요청을 받은 후, 해당 요청을 처리하는 데 소요되는 시간이다.
처리시간은 일반적으로 응답시간보다 짧은 시간으로 측정된다.
처리시간은 시스템이 얼마나 효율적으로 요청을 처리하고 있는지를 나타내는 중요한 지표이다.
데이터가 전송되는 데 걸리는 시간을 의미한다.
즉, 사용자가 어떤 동작을 수행하고, 해당 동작에 대한 결과를 시스템에서 수신하는 데 걸리는 시간이다.
지연은 일반적으로 응답시간과 처리시간을 포함하며, 데이터가 네트워크를 통해 이동하는 데 걸리는 시간도 포함된다.
성능 테스트는 기본적으로 매우 광범위하다. 다음 그림은 성능 테스트가 부하 및 스트레스 테스트 등 모두 상위 집합임을 보여준다.
부하 테스트는 성능 테스트의 하위 집합으로, 임계치 한계에 도달 할 때까지 시스템의 부하를 지속적으로 지속적으로 증가시켜 시스템을 테스트하는 것을 의미한다.
부하 테스트의 유일한 목적은 시스템의 내구성을 테스트하고 결과를 모니터링하기 위해 처리 할 수 있는 가장 큰 작업을 시스템에 할당하는 것이다.
부하 테스트에서 모니터링되는 속성에는 최대 성능, 서버 처리량, 다양한 부하 수준 (중단 임계 값 미만)에서의 응답 시간, H / W 환경의 적절성, 성능에 영향을주지 않고 처리 할 수있는 사용자 애플리케이션 수 등이 있다.
시스템이 향후 예상되는 로드를 관리할 수 있도록 데이터베이스, 하드웨어등과 같은 시스템의 모든 구성 요소에 대한 상한을 정해야 한다. 이런 목표치를
SLA(Service Level Agreement)
라고 한다.
시스템이 실패할 때까지 최대 부하 용량을 초과하였을 때 시스템이 어떻게 정상적으로 복구하는 지, 손상된 데이터가 없는 지를 확인한다.
네거티브 테스트: 시스템에서 구성 요소를 제거하는 작업도 스트레스 테스트의 일부로 수행된다.
피로 테스트: 이 테스트는 대역폭 용량을 넘을 정도로 테스트하여 애플리케이션의 안정성을 포착해야 한다.
-> 따라서 기본적으로 스트레스 테스트는 최대 부하 및 정상 조건을 넘어서는 애플리케이션의 동작을 평가한다.
성능 및 부하를 측정할 수 있는 툴(도구) 이다.
앞서 말했듯 서버나 네트워크 또는 개체에 대해 과부하를 시뮬레이션하여 강도를 테스트하거나 다양한 부하 유형에서 전체 성능을 분석하는 데 사용할 수 있다.
비슷한 도구로는 Ngrinder, k6 등이 있다.
Jmeter가 가장 오래된 프로젝트이면서 최근까지도 유지보수가 이어져오고 있기에 자료가 방대하여 이를 선택하였다.
JMeter 기본 개념
Thread Group : 각종 테스트가 발생하는 루트
Sampler : 사용자의 행동을 대신 작동
Listener : 처리 상황 및 결과를 데이터나 그래프로 노출
실습을 진행하기 앞서 필자는 윈도우 PC를 사용 중
Apach에서 제공하는 오픈소스로 위 링크로 이동하여 설치가 가능하다.
압축 파일을 다운받은 후 원하는 디렉토리에 압축을 풀어준다.
명령프롬프트 창으로 /bin 폴더로 이동하여 ./jmeter.bat
을 입력해 실행시켜준다.
Test Plan에 우클릭 -> Add -> Threads -> Tread Group 으로 이동, 클릭해준다.
Thread Group -> Add -> Sampler -> Http Request를 클릭해 샘플러를 하나 만들어준다.
호출하고자 하는 API를 적어준다.
필자는 POST로 로그인을 요청하는 엔드포인트를 호출하였다.
Body Data는 Json 형식으로 Id와 Password를 요청하고 있음을 볼 수 있다.
로그인 요청을 보내면 AccessToken을 발급하여 헤더에 Authorization이라는 이름으로 응답한다.
따라서 응답헤더에 AccessToken을 포함해주는 작업을 추가적으로 해주어야 한다.
만든 Http Request에 우클릭 -> Add -> Post Processors -> Regular Expression Extractor로 이동, 클릭해준다.
Name of create variable: myToken
Regular Expression: Authorization: (Bearer .+)
Template: $1$
Match No.: 1
위와 같이 설정해주면 myToken이라는 매개변수가 Baearer 뒤에 포함하여 토큰이 응답헤더에 포함된다.
이번엔 로그인을 보낼 때 Body Data에 json으로 보내고 있다는 점, 로그인 외의 사용자 인증 요청이 필요한 엔드포인트 호출 시 토큰을 요청헤더에 포함해야 된다는 점을 감안하여 HTTP Header Manager를 세팅해주자.
Test Plan에 우클릭 -> Add -> Config Element -> Http Header Manager 으로 이동, 클릭해준다.
Add 버튼을 눌러 이름은 Authorization, Value는 ${myToken}으로 설정,
이름은 Content-Type, Value는 application/json으로 설정해준다.
앞서 설정한 쓰레드 그룹 속성을 설정해보자.
Number of Threads (users) : 가상 사용자를 몇 명으로 설정할 것인가
Ramp-up period (seconds) : 쓰레드 수를 얼마동안 테스트할 것인가
Loop Count : 사용자들이 몇번 요청할지 설정
따라서 100명의 사용자가 50번씩 요청하여 총 5000번 요청이 전송된다.
테스트 수행 결과를 자세히 보여주는 리스터를 만들어보자.
Thread Group -> Add -> Listener -> View Results Tree, Summary Report, Graph Results를 선택해주었다.
각각 결과들의 트리 보기
, 요약 보고서
, 결과 그래프
이다.
상단에 있는 초록색 세모 버튼을 클릭해 테스트를 실행할 수 있다.
만약 저장할 것인지 물어보는 팝업이 나온다면 아니오를 먼저 눌러주자.
총 5000건 테스트라 시간이 조금 걸렸다.
요약 보고서이다.
총 5000번의 요청이 수행되었고, Average(평균) & Min(최소) & Max(최대)는 ms단위이다.
따라서 평균적으로 0.5초 정도의 응답시간이 소요되었음을 알 수 있다.
오류 0%는 HTTP 응답 코드로 판단한다.
예외에 해당하는 400번대 또는 500번대 응답이 없었음을 예상할 수 있다.
처리량은 Throughput이며 TPS(=Transaction Per Second)라고도 한다.
개별 요청에 대한 요청, 응답 정보를 확인할 수 있다.
응답 body까지 확인이 가능하다.
테스트 결과를 그래프로 확인이 가능하다.
테스트 결과를 리포트로 생성해보자.
앞서 만들었던 프로젝트를 리팩토링 하기 전과 리팩토링이 완료된 후로 나눠 각각 테스트를 진행하였다.
테스트 하고자 하는 엔드포인트를 HTTP Request로 명시해주고 테스트 결과를 출력을 하기 위해 설정을 해주자.
리스너로 만들어 두었던 요약 보고서 설정화면에서 파일 선택을 클릭하고 결과를 출력할 경로와 파일명을 설정한다.
파일명은 자유롭게 하되, 확장자를 csv로 변경해야 한다.
열기를 눌렀을 때 오류 팝업이 나온다면 무시하고 확인을 눌러주면 된다.
상단에 있는 빗자루를 클릭하면 테스트 이력을 제거할 수 있다.
제거가 끝나면 초록색 세모 버튼을 눌러 테스트를 다시 실행해주자.
그러면 지정된 경로에 테스트 결과 csv 파일이 생성된다.
Tools -> Generate HTML report를 눌러준다.
Result file: 방금 만든 csv 파일을 브라우저를 눌러 선택해준다.
user.properties file: /bin 폴더에 jemeter.properties를 선택해준다.
Output directory: Report를 위치시킬 빈 폴더를 생성해 선택해준다.
모두 선택하였다면 Generate report 를 클릭하여 생성해준다.
참고로 리팩토링 전 후 테스트에서는 테스트 DB를 사용하였다.
테스트 DB는 로컬, 클라우드 등에서 실행하지 않고 도커에 mariadb를 설치해 진행하였다.
방대한 데이터가 들어가는 작업이기 때문에 개발 DB(=로컬), 운영 DB(=클라우드)에서 실행하기엔 부적합하다고 판단하여 도커 환경의 테스트 DB에서 진행되었다는 점을 알아두어야 한다.
(응답속도가 로컬, 클라우드보다 차이가 있을 수 있음. 단지 리팩토링 전 후 비교를 위한 부하테스트임을 기억하자.)
생성한 폴더를 열어 index.html을 선택하면 HTML Report를 확인할 수 있다.
리팩토링 전 Report를 확인해보자.
스크롤을 내리면 전체 통계가 표로 나와있다.
그렇다면 리팩토링 후 Report를 확인해보자.
드라마틱해보이진 않지만, 차이가 있는 것을 확인할 수 있다.
이제 둘을 본격적으로 비교하기 위해 구글 시프레드 시트에 정리 후, 연산으로 비교해보자.
연산 방법은 다음과 같다.
응답 시간 : 개선율 (%) = ((리팩토링 전 - 리팩토링 후) / 리팩토링 전 ) x 100
초당 요청(TPS) : 개선율 (%) = ((리팩토링 후 - 리팩토링 전) / 리팩토링 전 ) x 100
리팩토링 전후에 수행한 부하 테스트 비교 결과, 전반적으로 모두 개선되었음을 확인할 수 있다.
모든 주요 지표에서 응답 시간이 감소되었고, 조회와 목록 확인보다는 POST요청 및 PATCH 등 사용자 인증이 필요한 부분에서 크게 개선되었다.
추가로 재능교환 게시물 삭제
부분은 5000건의 테스트를 계속 보낼 수 없어서 같은 조건에서 리팩토링 전후 1건의 요청을 보냈을 때 얼마나 차이가 있는지 확인해보았다.
1개의 재능교환 게시물에 이미지 2개를 첨부하고,
해당 게시글에 50개의 댓글이 있다는 가정 하에 삭제 테스트를 진행하였다.
위 구글 시프레드 시트 지표에서 보이듯이 1개의 건을 가지고 테스트를 진행했는데도 불구하고 크게 성능이 향상되었음을 확인할 수 있었다.
평균 응답시간은 13% 향상되고, TPS는 35%나 향상되었다.
응답시간(Response time), 처리시간(Processing time), 지연(Latency)
성능 테스트, 부하 테스트, 스트레스 테스트에 대해 알아보자