Post

[Test] k6

[Test] k6

📌 k6란?

k6 는 Grafana Labs에서 개발한 오픈소스 성능 테스트 도구이다. 다양한 부하 테스트를 수행할 수 있으며, 테스트 시나리오는 JavaScript 또는 TypeScript로 작성되어 비교적 쉽게 작성할 수 있다. Go 언어로 작성되어 높은 성능을 보인다. 또한 다른 도구들에 비해 직관적이며 간단하다. 테스트 결과를 터미널이 출력하여 볼 수도 있지만 InfluxDBGrafana 와 같은 도구와 연동하여 결과를 저장 및 분석할 수 있다.

다만 Node.js 환경에서는 직접적으로 실행되지 않는다. 정 하고 싶다면 xk6-plugin 확장 기능을 사용하면 된다.

📌 성능 지표

세 가지 성능 지표가 존재한다.

Throughput (처리량)

일정 시간 동안 시스템이 얼마나 많은 요청을 성공적으로 처리할 수 있는지를 나타내는 지표이다. 주로 사용하는 단위는 TPS(Transactions Per Second) , RPS(Requests Per Second) 가 있다.

k6 결과에서 http_reqs 를 통해 전체 요청 수와 RPS를 확인할 수 있다.

만약 1초 당 1,000개의 트랜잭션이 발생했다면 이를 1000 TPS라고 표현할 수 있다.

Latency (지연)

서버가 클라이언트로부터 요청을 받은 시점부터 응답을 완료하기까지 걸리는 시간을 나타내는 지표이다. 간단히 말해 요청이 처리되는 데 소요된 총 시간이다. 지연 시간이 낮을수록 시스템의 응답이 빠르다는 것을 의미한다.

k6 결과에서 지연 시간의 평균(avg), 최소값(min), 중앙값(med), 최대값(max), 백분위수(p(90) or p(95))를 확인할 수 있다.

Iterations (반복)

테스트 스크립트에 정의된 기본 함수가 실행된 횟수이다. 즉, vu (가상 사용자)가 테스트 시나리오를 몇 번 반복했는지를 나타낸다.

k6 결과에서 iterations 를 통해 총 반복 횟수와 초당 반복 횟수를 확인할 수 있다.

📌 메트릭

유형

k6에는 네 가지 메트릭 유형이 존재한다.

  • Counter: 누적 값을 추적하며, 계속 증가하는 값을 가진다. http_reqs 등이 있다.
  • Gauge: 특정 시점의 값을 측정하며 최소(min), 최대(max), 마지막(last) 값을 저장한다. vu 등이 있다.
  • Rate: 특정 이벤트가 얼마나 자주 발생하는지 백분율로 표시한다. checks 등이 있다.
  • Trend: 시간 경과에 따른 값의 변화를 추적하고 평균, 최소, 백분위수와 같은 통계 지표를 계산한다. http_req_duration 등이 있다.

HTTP 관련 메트릭

HTTP 요청을 할 때 생성되는 메트릭이다.

Metric NameTypeDescription
http_reqsCounter생성한 총 HTTP 요청 수
http_req_blockedTrend요청을 시작하기 전 대기한 시간
http_req_connectingTrend원격 호스트에 TCP 연결을 설정하는 데 걸린 시간
http_req_tls_handshakingTrendTLS 핸드셰이킹에 걸린 시간
http_req_sendingTrend원격 호스트에 데이터를 전송하는 데 걸린 시간
http_req_waitingTrend원격 호스트의 응답을 기다리는 데 걸린 시간(TTFB)
http_req_receivingTrend원격 호스트로부터 응답 데이터를 받는 데 걸린 시간
http_req_durationTrend요청에 걸린 총 시간 (http_req_sending + http_req_waiting + http_req_receiving)
http_req_failedRate실패한 HTTP 요청의 비율

기본 메트릭

프로토콜과 상관없이 생성되는 메트릭이다.

Metric NameTypeDescription
vusGauge현재 활성화된 가상 사용자 수
vus_maxGauge가능한 최대 가상 사용자 수
iterationsCounter가상 사용자가 테스트 스크립트를 실행한 총 횟수
iteration_durationTrend테스트 스크립트를 한 번 실행하는 데 소요된 시간
dropped_iterationsCounterVU 또는 시간 부족으로 실행되지 않은 반복 횟수
data_receivedCounter수신된 데이터의 양
data_sentCounter전송된 데이터의 양
checksRate성공적인 체크의 비율

📌 테스트

다양한 테스팅 방법이 존재한다. 테스트 목적에 따라 적절한 방법을 선택해야 한다.

  • Smoke Test: 테스트의 테스트이다. 테스트 스크립트에 오류가 없는지, 시스템이 최소한의 부하에서 정상적으로 동작하는지 검증한다.
  • (Average) Load Test: 시스템이 특정 부하 조건에서 어떻게 작동하는지 확인하는 테스트이다.
  • Stress Test: 포화 지점과 첫 번째 병목 지점을 식별하는 테스트이다. 극한의 조건 또는 과도한 부하 상황을 부여한다.
  • Soak(Endurance) Test: 오랜 시간 평균적인 부하를 주었을 때 정상적으로 시스템이 동작하는지 확인하는 테스트이다.
  • Spike Test : 예상치 못한 트래픽 급증에 대한 시스템의 상태를 확인하는 테스트이다.
  • Breakpoint Test: 시스템의 한계점을 찾기 위한 테스트이다. 일반적인 테스트와 달리 목표 부하량이 없다.

📌 어떻게 테스팅 계획을 세워야 하는가?

정해진 정답은 없다. 서비스에 맞게 적합한 테스팅 방법을 선택해야 한다. 다만 좋은 테스팅을 위해 고려해야 할 점들은 다음과 같다.

  • 하나의 테스팅 방법으로 모든 문제를 해결할 수 없다. 여러 방법을 통합하여 테스트를 수행해야 한다.
  • 초반 테스팅은 Smoke Test로 시작하는 것이 좋다. 스크립트가 예상대로 잘 동작하는지 먼저 검증하는 것이 좋다.
  • 주요 이벤트 전 Spike Test를 수행하는 것이 좋다.
  • 최대 트래픽에서 정기적으로 Load Test를 수행하는 것이 좋다.
  • ramp-up, plateau, ramp-down과 같이 간단한 부하 패턴을 사용한다. 부하의 증감이 많이 바뀌는 롤러코스터 패턴은 피하라.

ramp-up: 점진적으로 부하를 증가시키는 방법 plateau: 부하를 일정 수준으로 유지시키는 방법 ramp-down: 점진적으로 부하를 감소시키는 방법

📌 설치

  • Mac OS
1
brew install k6
  • Window
1
choco install k6

또는 공식 홈페이지 설치 문서를 참고하여 설치해도 된다.

https://grafana.com/docs/k6/latest/set-up/install-k6/

📌 예제

간단한 테스트 스크립트

1
2
3
4
5
6
7
8
9
10
11
12
import http from "k6/http";
import { sleep } from "k6";

export const options = {
    vus: 100, // 가상 사용자 수
    duration: "10s", // 테스트 시간
};

export default function () {
    http.get("http://localhost:8080/api/v1/posts");
    sleep(1);
}

options 에서 테스트에 대한 설정을 명시한다.

function 은 각 vu가 반복적으로 수행할 작업이다.

위 코드는 100명의 가상 사용자가 10초 동안 GET 요청을 1초마다 보내는 스크립트이다. k6 run test1.js 로 테스트를 수행할 수 있다.

image.png

테스트가 수행 중일 때 출력되는 화면이다.

image.png

테스트가 완료되면 결과에 대한 메트릭을 확인할 수 있다.

k6 run --out json=result.json test1.js 를 실행하면 테스트 결과를 JSON 형태의 파일로 저장한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import http from "k6/http";
import { sleep } from "k6";
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";

export const options = {
    vus: 100,
    duration: "10s",
};

export default function () {
    http.get("http://localhost:8080/api/v1/posts");
    sleep(1);
}

// 테스트 결과를 html로 추출
export function handleSummary(data) {
    return {
        "result.html": htmlReport(data),
    };
}

test1.js와 테스트 동작은 동일하나, 테스트 결과를 HTML 형태로 저장한다.

InfluxDB, Grafana 연동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: '3.4'

services:
  influxdb:
    image: influxdb:1.8
    ports:
      - "8086:8086"
    environment:
      - INFLUXDB_HTTP_AUTH_ENABLED=false
      - INFLUXDB_DB=k6

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_BASIC_ENABLED=false
    volumes:
      - ./grafana:/etc/grafana/provisioning/

docker-compose를 사용하여 InfluxDB와 Grafana를 설치한다.


Grafana에 InfluxDB를 연결한다.

image.png

localhost:3000에 접속하면 위와 같은 Grafana 대시보드를 확인할 수 있다. 우측에 Coonection - Data sources를 클릭한다.

image.png

우측 상단에 Add new data source를 클릭한다.

image.png

InfluxDB를 클릭한다.

image.png

HTTP에서 URL을 http://influxdb:8086 로, Database를 k6로 설정한다.


Grafana 대시보드 테마 중 하나를 적용시키자.

image.png

Dashboards 메뉴에서 우측 상단에 New - New dashboard를 클릭한다.

image.png

우측 하단에 import dashboard를 클릭한다.

image.png

https://grafana.com/grafana/dashboards/2587 를 붙여넣고 Load 버튼을 클릭한다.

image.png

Import 버튼을 클릭한다.


간단한 테스트 코드의 동작을 Grafana를 통해 확인하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import http from 'k6/http';
import { sleep } from 'k6';

// 3분 동안 7000개의 vu가 요청을 점진적으로 보냄
export const options = {
    stages: [
        { duration: '3m', target: 7000 }
    ],
};

export default function () {
    http.get('http://localhost:8080/api/v1/posts');
    sleep(1);
}

image.png

K6_WEB_DASHBOARD=true k6 run test3.js 를 통해 테스트 과정을 웹 대시보드에서 볼 수 있다. k6에서 제공하는 웹 대시보드이다.

image.png

k6 run --out influxdb=localhost:8086/k6 test3.js 를 통해 테스트 결과를 InfluxDB에 저장하고, Grafana 대시보드에서 이를 시각화하여 볼 수 있다.

Load Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import http from "k6/http";
import { check } from "k6";

export const options = {
    stages: [
        { duration: "1m", target: 20 }, // 1분 동안 vu 20명까지 증가
        { duration: "1m", target: 20 }, // 1분 동안 20명 유지
        { duration: "1m", target: 50 }, // 1분 동안 50명까지 증가
        { duration: "1m", target: 50 }, // 1분 동안 피크 유지
    ],

    thresholds: {
        http_req_duration: ["p(95)<200"], // 95%의 요청이 200ms 이하로 응답해야 성공
    },
};

export default function () {
    const response = http.get("http://localhost:8080/api/v1/posts");

    // 상태 코드가 200이면 성공
    check(response, {
        "success": (res) => res.status === 200,
    });
}

vu 수를 점진적으로 증가시키고, 유지하면서 시스템의 성능을 측정한다.

stages 를 통해 시간에 따른 vu 수를 조절한다.

thresholds 는 테스트의 성공 여부를 판단하는 기준이다.

check 를 통해 요청이 성공적으로 처리되었는지 확인한다.

Stress Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import http from "k6/http";
import { check } from "k6";

export const options = {
    stages: [
        { duration: "1m", target: 20 },
        { duration: "1m", target: 20 },
        { duration: "1m", target: 40 },
        { duration: "1m", target: 40 },
        { duration: "1m", target: 50 },
        { duration: "1m", target: 50 },
    ],

    thresholds: {
        http_req_duration: ["p(95)<300"],
    },
};

export default function () {
    const response = http.get("http://localhost:8080/api/v1/posts");
    check(response, {
        "success": (res) => res.status === 200,
    });
}

vu 수를 증가시키면서 breakpoint를 찾는다.

📌 깃허브 링크

https://github.com/whqtker/practice-k6

This post is licensed under CC BY 4.0 by the author.