부하테스트, Jmeter와 K6 - kakaotech-19/backend GitHub Wiki
부하 테스트와 고려 요소
예상되는 부하 상황에서 정상적으로 동작하는지 확인하는 성능 테스트
부하 테스트를 통해 1) 병목 현상 및 2) 메모리 누수 문제를 사전에 발견할 수 있다.
부하 테스트 개념
메트릭
메트릭이란, 시스템 성능을 측정 및 평가할 수 있는 지표이다.
CPU 사용률, 메모리 사용량, 응답 시간, 트랜잭션 처리량 등을 확인하면 되겠다. 아울러 정상적으로 응답 받은 동시 사용자를 확인한다.
테스트 종류
-
Stress Test
시스템의 한계점(수용 능력)을 찾기 위한 부하 테스트이다. 지속적으로 상당한 요청과 쿼리를 실행함으로써 메모리 누수 및 서버 다운 현상을 확인한다. -
Spike Test
일정 기간에 상당한 요청을 호출해서 안정성을 확인하는 테스트이다.
부하 테스트 툴
Jmeter
Jmeter 개념
- Thread Group 테스트 주체라고 볼 수 있는 Thread를 설정해야한다.
Number of Threads
: 동시 사용자 수 (가상 사용자(VU)/쓰레드 수)Ramp-up
: 설정한 전체 쓰레드가 생성되는데 걸리는 시간(초). 예를 들어, VU가 100이고 50초 램프업이면 2초마다 1쓰레드 생성Loop Count
: 각 쓰레드가 테스트를 반복할 횟수
- HTTP Request 설정
Protocol
: 통신 프로토콜 (http/https)Server
: 테스트할 서버의 도메인명/IP 주소 (ex: localhost)Port
: 포트 번호Path
: API 엔드포인트
Jmeter 설치하고 실행하기
- Mac 기준으로
brew install jmeter
를 한 후,jmeter
명령어를 실행하면 GUI가 표시된다. Test Plan
의 이름 적어주고, 왼쪽 상단의Save
버튼을 눌러서 어디에jmx(테스트 계획이 적힌 xml 형식 파일)
와jtl(테스트 실행 결과 로그 파일)
을 저장할지 설정한다.- 왼쪽 사이드바의
Test Plan
에 오른쪽 마우스 버튼을 누르고,Add > Threads > Thread Group
을 클릭한다. Thread Properties
를 설정한다.- 사이드바에 표시된
Thread Group
에 오른쪽 마우스 버튼을 누르고,Add > Sampler > Http Requset
를 클릭한다. Protocol(http)
,Server(localhost)
,Port Number(8080)
,Path(GET /hello)
를 설정한다.- 만약, jwt 토큰을 위해 헤더 설정이 필요하면,
Thread Group
에 오른쪽 마우스 버튼을 누르고,Add > Config Element > HTTP Header Manager
를 클릭해서 헤더를 설정한다. Thread Group
에 오른쪽 마우스 버튼을 누르고Add > Listener > View Results Tree
를 클릭하고, 테스트를 실행하면 테스트 결과를 확인할 수 있다.Summary Report
도 통계 형태로 보여줘서 만들면 좋다.- 아울러, jtl 저장 경로를 설정하면, 파일 형식으로 테스트 결과를 확인할 수 있다.
Jmeter로 성능 지표 확인
Summary Report
에는 다음과 같은 정보가 있다.
Average
,Min
,Max
: 응답 시간(밀리초)Throughput
: 초당 처리된 요청 수
간단한 PATCH API에 대해 3000 VU, Ramp up 10초, loop 5번 설정 후 테스트를 했는데
VU | Ramp up | Loop | Throughput(/s) | Max(ms) | Avg(ms) |
---|---|---|---|---|---|
3000 | 10 | 5 | 266 | 125 | 3 |
1000 | 10 | 20 | 2003 | 889 | 25 |
정도의 지표가 나왔다. |
K6
K6 개념
K6
는 자바스크립트 코드로 부하 테스트를 진행하는 방식이다.
Jmeter
의 경우 GUI 상에서 jmx
를 만들어야 테스트가 가능한 점에서 차이가 있다.
이런 점에서 K6
쪽이 부하 테스트 버전 관리하기 용이하다고 느껴진다.
또한, Jmeter
에 비해 웹소켓, gRPC 테스트 설정이 더 간편하다고 한다.
vus
: VU 수duration
: 테스트 실행 시간iterations
: 테스트동안 실행할 총 요청 횟수stages
: 단계적으로 VU 수를 변경하는 옵션sleep
: 요청 후 쉬는 시간
K6 설치하고 실행하기
brew install k6
를 설치해주자.
js 파일(test.js
)을 만들어서 스크립트를 작성하고,
k6 run test.js
를 실행하면 결과가 나온다.
--out
옵션으로 json, csv 등으로 결과를 저장할 수 있다. 시계열 DB에도 저장 가능하다.
import http from "k6/http";
import { check, sleep } from "k6";
// duration 동안 vus가 총 iterations만큼 요청
// sleep이 1이면, 모든 vus가 동시에 1초 간격으로 요청.
export const options = {
vus: 100,
duration: "10s", // 총 테스트 시간. 10s, 1m, 1h 같은 형식으로 적는다.
iterations: 1000, // 테스트 시간동안의 요청 횟수 총합. vus보다 크거나 같아야 한다.
};
const headers = {
Authorization: `Bearer ${JWT_KEY}`,
"Content-Type": "application/json",
};
const payload = JSON.stringify({
nickname: "123",
});
export default function () {
const response = http.patch(
"http://localhost:8080/api/v1/member/nickname",
payload,
{ headers }
);
// 특정 조건을 만족하며 테스트가 진행됐는지 확인
check(response, {
"status is 200": (r) => r.status === 200,
"response time < 500ms": (r) => r.timings.duration < 500,
});
sleep(1); // VU는 1초마다 요청을 보낸다.
}
결과는 다음과 같이 나온다.
왼쪽 항목들을 thresholds
라고 한다.
✓ status is 200
✓ response time < 500ms
checks.........................: 100.00% 2000 out of 2000
data_received..................: 402 kB 39 kB/s
data_sent......................: 440 kB 43 kB/s
http_req_blocked...............: avg=360.65µs min=0s med=2µs max=5.19ms p(90)=232µs p(95)=3.66ms
http_req_connecting............: avg=261.42µs min=0s med=0s max=3.14ms p(90)=128µs p(95)=2.68ms
http_req_duration..............: avg=21.23ms min=1.52ms med=13.78ms max=77.41ms p(90)=59.28ms p(95)=65.83ms
{ expected_response:true }...: avg=21.23ms min=1.52ms med=13.78ms max=77.41ms p(90)=59.28ms p(95)=65.83ms
http_req_failed................: 0.00% 0 out of 1000
http_req_receiving.............: avg=520.79µs min=6µs med=40µs max=62.68ms p(90)=181.3µs p(95)=578.89µs
http_req_sending...............: avg=21.18µs min=2µs med=7µs max=8.34ms p(90)=23µs p(95)=32µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=20.69ms min=1.51ms med=13.21ms max=77.15ms p(90)=59.08ms p(95)=65.3ms
http_reqs......................: 1000 97.540078/s
iteration_duration.............: avg=1.02s min=1s med=1.01s max=1.07s p(90)=1.06s p(95)=1.06s
iterations.....................: 1000 97.540078/s
vus............................: 100 min=100 max=100
vus_max........................: 100 min=100 max=100
점진적으로 VU 늘려 테스트하기
export const options = {
stages: [
{ duration: "10s", target: 100 },
{ duration: "2s", target: 100 },
],
};
이런 식으로 option을 stages
바꿔주면 단계적으로 수행하게 된다.
위의 코드를 예로들면,
- 10초동안 100 VU까지 점진적으로 늘린다. (생성된 VU는 설정된 sleep 간격으로 지속적으로 요청)
- 2초동안 100 VU까지 점진적으로 늘린다.
스트레스와 스파이크 테스트 모두를 진행해줄 수 있겠다.
메트릭 설정하기
thresholds
를 설정하면 테스트 성공 기준을 구체적으로 작성할 수 있다.
export const options = {
vus: 3000,
duration: "10s",
iterations: 30000,
thresholds: {
http_req_failed: ["rate<0.01"], // 실패 비율이 1% 미만이어야 함 (4XX, 5XX status)
},
};
sleep(0.01);
이 코드는 3000 VU가 0.01초마다 요청, 총 3만 번 요청하는 테스트이다.
✗ status is 200
↳ 88% — ✓ 26532 / ✗ 3468
✗ response time < 500ms
↳ 35% — ✓ 10523 / ✗ 19477
checks.........................: 61.75% 37055 out of 60000
data_received..................: 11 MB 1.8 MB/s
(중략...)
✗ http_req_failed................: 11.55% 3468 out of 30000
running (06.0s), 0000/3000 VUs, 30000 complete and 0 interrupted iterations
default ✓ [======================================] 3000 VUs 06.0s/10s 30000/30000 shared iters
ERRO[0006] thresholds on metrics 'http_req_failed' have been crossed
Docker K6로 테스트하기
K6
도커 컨테이너로 스크립트를 실행해보자.
docker pull grafana/k6
docker run --name myk6 --network=host -v .:/app -i grafana/k6 run --out json=/app/results.json /app/k6.js
Jmeter와 K6에 대한 생각
K6
가 유지보수 측면에서 Jmeter
보다 낫다고 느낀다.
즉, JS 코드가 jmx보다 유지보수 및 버전 관리하기 쉬울 것 같다.(jmx는 Jmeter GUI에서 만들어야 하며, xml이어서 태그 이름을 알아야함)