성능 테스트는 일반적으로 순수 Java 애플리케이션 오픈 소스 도구인 Apache JMeter를 사용한다. Python은 메뚜기 프레임워크라고 불리는 부하 테스트 도구 Locust가 존재한다. 특히 Locust는 여러 컴퓨터에 분산된 부하 테스트 실행을 지원하므로 수백만 명의 동시 사용자를 시뮬레이션 할 수 있다고 한다.
JavaScript로 활용 가능한 도구에는 K6가 존재하는데 엔지니어링 팀의 성능 테스트를 쉽고 생산적으로 만드는 오픈 소스 부하 테스트 도구이다. 다른 도구들과 마찬가지로 무료이다. K6를 학습하다가 굉장히 재밌는 사실을 알게 되었는데 이러한 점들을 포스팅해보려 한다.
프론트엔드 성능 테스트는 일반적으로 브라우저와 관련된 최종 사용자 경험에 영향을 미친다. UI 블랙박스 테스트와 마찬가지로 외부로 보이는 문제를 식별할 순 있어도 아키텍처의 문제를 발견하진 못한다.
페이지가 사용자 화면에서 빠르게 렌더링 되는지, 사용자와 애플리케이션 간의 상호 작용에 소요되는 시간 등이 대표적인 프론트엔드의 성능 테스트 유형이다.
이렇다 보니 클라이언트 측에서 실행되며 범위가 제한적이므로 프론트엔드 성능 테스트만으론 충분하지 않고 백엔드 성능 테스트도 병행되어야 한다.
특히 프론트엔드 성능 테스트 지표를 통해서만 전체적인 성능을 판단할 경우 트래픽 양이 증가할 때의 성능에 대한 잘못된 분석으로 이어질 수 있다.
백엔드 성능 테스트는 서버를 대상으로 시스템 전체의 확장성, 탄력성, 가용성, 신뢰성, 복원력, 대기 시간을 확인한다.
확장성이란 시스템이 꾸준히 증가하는 수요 수준에 적응할 수 있는지? 탄력성이란 시스템이 수요가 낮은 기간 동안 리소스를 보존할 수 있는지? 가용성이란 시스템의 각 구성 요소의 가동 시간은 얼마인지? 신뢰성이란 시스템이 다양한 환경 조건에서 일관되게 반응하는지? 복원력이란 시스템이 예기치 않은 이벤트를 정상적으로 견딜 수 있는지? 대기시간이란 시스템이 요청을 얼마나 빨리 처리하고 응답하는지에 대해서다.
프론트 엔드 보다 백엔드 성능 테스트의 범위가 더 넓으며 성능 문제를 보다 일찍 발견할 수 있다. 특히 프론트 엔드 성능 테스트보다 고부하 생성에 더 적합하다.
https://gist.github.com/Jiveloper/96c47022163d60f97545afc508c3ca16
k6를 설치하면 CLI 환경에서 실행 설정을 구성할 수 있다. 위 명령어를 통해 특정 명령에 대한 정보를 얻을 수 있다. 플래그에서 duration, iterations, 그리고 가상 사용자를 뜻하는 vus가 존재하는데 각각 아래와 같이 설정할 수 있다.
https://gist.github.com/Jiveloper/a343cb5e677237eb17ae175a0dbf05f1
터미널에서 실행 옵션과 여러 플래그에 대해 알아보았는데, 테스트 스크립트가 사용하는 도메인 및 다양한 정보를 환경 변수를 통해 제어 가능하다. 도메인을 예로 들면 Staging과 QA가 다를 수 있는데, 이러한 상황에서 환경 변수를 통해 스크립트 자체를 변경하지 않고 제어 가능하므로 유용하게 활용할 수 있다.
https://gist.github.com/Jiveloper/c2ddb76de90d0ea9b7164d5e90d3744d
https://gist.github.com/Jiveloper/bf559075c257b3e12a2915cbcae3d455
위 스크립트는 HTTP POST 요청에 대한 예시이다. 여러 프로토콜을 지원하지만 예시는 HTTP로 한정한다. k6는 Go로 만들어졌고 스크립트 작성은 JavaScript로 작성한다. 내장 모듈에서 HTTP 클라이언트를 가져와 k6 가상 사용자에 의해 실행되는 구조이다. K6는 exported default function 내부에 작성된 코드에 대해서만 가상 사용자가 실행하기 때문에 이 점을 유의해야 한다. 또한 HTTP 응답의 JSON 본문에 접근하기 위해 response.json()을 사용한다.
https://gist.github.com/Jiveloper/27ce8c33d8fe7f364b8d347918b19645
k6는 로컬과 클라우드 환경에서 테스트 스크립트 실행이 가능하다. 현재 로컬에서 실행한 결과이므로 execution이 local로 표시된다. script는 성능 테스트를 실행한 스크립트가 존재하는 js 파일을 의미한다.
1개의 시나리오에서 가상 사용자는 1, 최대 시간은 10분 30초가 설정된 상태로 실행되었다는 것을 의미한다. 비교적 간단한 테스트 스크립트를 실행했으므로 해당 시간에 도달하기 전 스크립트의 요청에 대한 응답을 받은 상황이다. 만약 해당 시간에 도달할 경우 테스트가 강제로 중단된다. default로 되어있는 부분은 시나리오의 이름을 의미하며 현재 테스트 스크립트에서 명시적으로 설정하지 않았기 때문에 k6에서 default 이름을 사용하여 출력한 모습이다. 반복이 1 iterations로 나타나는데 이는 실행 전 플래그를 통해 별 다른 설정을 하지 않았으므로 기본값인 1 iterations이 실행되었다. 1 VUs는 가상 사용자를 의미하며 실제 애플리케이션을 사용하는 1명의 사용자를 의미한다.
기능 테스트에서의 테스트 메트릭처럼(차수별 테스트 전체 테스트 케이스, 성공률, 결함 개수 등..) 성능 테스트에서도 테스트 결과 분석을 위한 메트릭이 존재한다. k6에서는 이러한 분석을 위해 내장 메트릭을 제공하는데, HTTP 요청이 발생할 경우 해당 메트릭이 생성된다. 수신된 데이터의 양, 전송된 데이터의 양, k6가 생성한 총 HTTP 요청 수, 요청 시작 전 차단된 시간, 원격 호스트에 대한 TCP 연결 설정에 소요된 시간, TLS 세션 핸드셰이킹에 소요된 시간, 원격 호스트에 데이터를 보내는데 소요된 시간, 원격 호스트의 데이터를 보내는데 소요된 시간, 원격 호스트에 응답을 기다리는 데 소요된 시간 등이 HTTP 관련 기본 메트릭으로 제공된다.
결론적으로 백엔드 성능 병목 현상이 프론트 성능에도 영향을 끼치며 프론트 성능 테스트는 브라우저에서 실제 사용자 경험을 측정하므로 어느 영역에서의 성능 테스트가 좋다 나쁘다가 아닌, 두 가지 영역에서의 성능 테스트가 상호 보완하는 형태로 이뤄져야 한다. 추후 실무에서 보다 깊게 다뤄본 경험을 토대로 Jmeter와 Locust 그리고 K6의 장단점을 비교 분석한 블로깅을 해볼 수 있길 기대해본다.
Ref