해당 게시글은 내용 정리 목적으로 작성되었습니다. 틀린 내용이 있다면 언제든지 말씀해 주세요

목차

1. k6란
2. 테스트 시나리오 
3. k6 설치 방법 (docker) 
4. 결론



API를 개발할 때는 부하 테스트가 필수적이다. 예를 들어, 선착순 티켓팅이나 이벤트 응모 기능의 경우, 갑작스러운 사용자 접속 증가에 대비해 서버의 안정성을 테스트하고 최적의 인프라 환경을 구축해야 한다

그러려면 예상 접속자를 산정하고 부하 테스트를 해보아야 하는데, 그럴 때 유용한 툴이 k6이다.

1. k6란

  • k6는 오픈 소스 성능 테스트 도구이다.
  • 주로 웹 애플리케이션이나 API의 성능을 테스트하고 부하를 시뮬레이션하는 데 사용된다.
  • Go 언어로 개발되어 높은 성능과 효율성을 제공하며, 대규모 테스트를 빠르고 효과적으로 실행할 수 있다.

2. 테스트 시나리오

  • 사용자 접속 로그를 저장하는 Spring Boot API가 있다. 
  • 엔드 포인트는 POST /api/v1/user-access-logs 이며 body에 userId를 application/json 방식으로 저장한다.
  • userId는 1 ~ 10000 사이로 무작위 값이 입력된다.
  • 50~ 100명의 가상 유저가 해당 API를 1분에 1만번 호출하는 부하테스트를 진행한다.

3. k6 설치 방법 (docker)

1) docker 설치 (생략)

2) 프로젝트 구조 설정

my-k6-project/
├── docker-compose.yml
├── scripts/
│   └── myscript.js
  • 위 와 같이 my-k6-project 디렉토리를 생성하고 하위에 docker-compose.yml와 scripts/myscript.js를 생성한다
  • docker-compose.yml
# docker-compose.yml
version: '3.8'

services:
  k6:
    image: loadimpact/k6
    volumes:
      - ./scripts:/scripts
    entrypoint: ""
    command: ["k6", "run", "/scripts/myscript.js"]
  • scripts/myscript.js
// scripts/myscript.js

import http from 'k6/http';
import { check } from 'k6';

function getRandomUserId(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

export let options = {
    scenarios: {
        constant_request_rate: {
            executor: 'ramping-arrival-rate',
            startRate: 167,  // 초당 약 167개의 요청으로 시작 (1분 동안 10,000 요청)
            timeUnit: '1s',
            preAllocatedVUs: 50,  // 초기 할당 VUs 수
            maxVUs: 100,  // 최대 VUs 수
            stages: [
                { duration: '1m', target: 167 },  // 1분 동안 초당 약 167개의 요청을 유지
            ],
        },
    },
};

export default function () {
    const userId = getRandomUserId(1, 10000);
    const payload = JSON.stringify({
        userId: userId
    });
    const params = {
        headers: {
            'Content-Type': 'application/json',
        },
    };

    let response = http.post('http://host.docker.internal:8080/api/v1/user-access-logs', payload, params);

    check(response, {
        'status is 204': (r) => r.status === 204,
    });
}
  • 아래 github에서 k6 테스트를 위한 k6-stress-test-sample-project.git를 clone한 다음 실행한다. (JDK17 필요)
https://github.com/Bamdule/k6-stress-test-sample-project.git
  • k6-stress-test-sample-project를 8080포트로 실행하는 것에 성공했다면 my-k6-project 디렉토리로 이동해서 docker-compose up 명령을 실행한다
  • (다른 api를 테스트하고 싶으면 myscript.js 부분에 테스트 요청 url을 변경하면 된다.)
docker-compose up

부하 테스트 결과

k6-1  |      ✓ status is 204
k6-1  |
k6-1  |      checks.........................: 100.00% ✓ 10017      ✗ 0
k6-1  |      data_received..................: 542 kB  9.0 kB/s
k6-1  |      data_sent......................: 1.8 MB  30 kB/s
k6-1  |      dropped_iterations.............: 3       0.049966/s
k6-1  |      http_req_blocked...............: avg=27.26µs min=1.63µs  med=4.47µs  max=14.78ms  p(90)=6.27µs  p(95)=7.32µs
k6-1  |      http_req_connecting............: avg=21.56µs min=0s      med=0s      max=14.71ms  p(90)=0s      p(95)=0s
k6-1  |      http_req_duration..............: avg=52ms    min=41.45ms med=50ms    max=364.05ms p(90)=56.16ms p(95)=57.17ms
k6-1  |        { expected_response:true }...: avg=52ms    min=41.45ms med=50ms    max=364.05ms p(90)=56.16ms p(95)=57.17ms
k6-1  |      http_req_failed................: 0.00%   ✓ 0          ✗ 10017
k6-1  |      http_req_receiving.............: avg=33.46µs min=3.96µs  med=27.77µs max=5.19ms   p(90)=56.15µs p(95)=65.21µs
k6-1  |      http_req_sending...............: avg=36.32µs min=7.12µs  med=31.64µs max=5.14ms   p(90)=52.46µs p(95)=60.71µs
k6-1  |      http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s
k6-1  |      http_req_waiting...............: avg=51.93ms min=41.41ms med=49.93ms max=363.94ms p(90)=56.09ms p(95)=57.09ms
k6-1  |      http_reqs......................: 10017   166.835382/s
k6-1  |      iteration_duration.............: avg=52.18ms min=41.54ms med=50.17ms max=366.28ms p(90)=56.33ms p(95)=57.35ms
k6-1  |      iterations.....................: 10017   166.835382/s
k6-1  |      vus............................: 9       min=7        max=13
k6-1  |      vus_max........................: 53      min=53       max=53
k6-1  |
k6-1  |
k6-1  | running (1m00.0s), 000/053 VUs, 10017 complete and 0 interrupted iterations
k6-1  | constant_request_rate ✓ [ 100% ] 000/053 VUs  1m0s  167.00 iters/s
  • 위 결과 지표에 대해서 간단하게 설명하면 다음과 같다
    • 성공률: 100% 성공률을 기록
    • 성능: 평균 요청 처리 시간 52ms로, 요청 대기 시간 평균 51.93ms 최대 요청 처리 시간 364ms
    • 부하: 1분 동안 초당 약 167개의 요청을 처리
    • 가상 사용자: 평균 9명의 가상 사용자가 테스트 동안 활동했으며, 최대 53명의 VU가 사용되었음
  • Spring Boot API 요청 시 0.04초의 sleep을 주어서 API 응답 지연시간을 조절했다.
  • sleep을 길게 잡을 수록 1분에 1만번의 요청을 하지 못하기 때문에 k6 자체적으로 드롭시키는 요청이 많아진다.

4. 결론

유의미한 테스트를 진행하려면 로컬 테스트가 아닌 실제 운영 환경과 동일한 환경에서 부하테스트를 해야 한다.
AWS를 사용해서 인프라를 구축했다면 테스트용으로 복제한 다음 가상의 사용자 수를 산정하고 테스트 환경에서 부하 테스트를 하는 것이 바람직하다.

그리고 톰캣의 스레드, heap 메모리 등을 조절해서 서버 스펙에 따른 최적의 설정 값을 찾는 것도 백엔드 개발자의 역할이고, 필요하다면 서버 스펙 변경을 고려해보는 것도 좋다

+ Recent posts