본문 바로가기

Infra/Server

k6 부하 테스트 가이드

728x90
반응형

신규 프로젝트 운영 반영 전, 예상 트래픽을 기준으로 성능과 병목을 미리 점검했습니다. 사전에 구성한 Grafana 대시보드와 k6 부하 테스트를 이용해 실제 트래픽 수준을 재현했고, 그 과정에서 헷갈리기 쉬운 용어들을 정리했습니다. 아래에 k6 핵심 개념과 지표 해석 기준을 설명합니다.

k6

  • 자바스크립트로 시나리오를 작성하는 CLI 기반 부하테스트 도구
  • VU(가상 사용자) 모델과 도착률(RPS) 모델 모두 지원
  • influxDB/Prometheus/JSON 등으로 메트릭을 보내 Grafana 시각화 가능

용어

트래픽·사용자 관련

  • VU (Virtual User): 가상 사용자. 동시 접속자 수에 해당. 각 VU는 JS 스크립트의 default 함수를 반복 실행
  • Iteration: VU가 default 루프를 한 번 수행한 것. 한 번의 요청이 아니라, 시나리오에 정의된 “여정(여러 요청+검증)” 1회
  • RPS (Requests Per Second): 초당 요청 수. 시스템 처리량(Throughput)의 대표 지표
  • TPS (Transactions Per Second): 초당 트랜잭션 수. RPS와 비슷하지만, “트랜잭션” 경계(여러 요청 묶음)가 분명할 때 사용

시간·지연 관련

  • Latency / Response Time: 요청~응답 완료까지 총 시간
  • TTFB (Time To First Byte): 첫 바이트가 도착까지의 시간(서버 처리 + 네트워크 왕복)
  • Percentile(퍼센트타일): p50, p95, p99 등. ex) p95=300ms는 95% 요청이 300ms 이내

k6 기본 내장 메트릭(일부)

  • http_req_duration: 요청~응답 전체 시간(핵심)
  • http_req_waiting: 서버가 응답을 처리하는 시간
  • http_req_connecting, http_req_tls_handshaking, http_req_sending, http_req_receiving: 연결/핸드셰이크/전송/수신 단계별 시간
  • http_reqs: 총 HTTP 요청 수
  • vus: 현재 활성 VU 수
  • iterations: 수행된 Iteration 수
  • data_sent, data_received: 전송/수신 바이트

시나리오(실행기, Executor)

  • constant-vus: 고정 VU 수로 일정 시간 동안 실행
  • ramping-vus: VU를 단계적으로 증감
  • constant-arrival-rate: 초당 N건의 요청 도착률(ARR)을 유지. RPS 기반 목표에 적합
  • ramping-arrival-rate: 요청 도착률을 단계적으로 증감
  • shared-iterations / per-vu-iterations: 전체/각 VU가 정해진 횟수만큼 수행
  • externally-controlled: 실행 중에 외부에서 VU 수 등을 제어

워크로드 모델

  • Closed Model(VU 기반): constant-vus, ramping-vus서버가 느려지면 RPS가 자연히 감소
  • Open Model(도착률 기반): constant-arrival-rate, ramping-arrival-rateRPS 유지, 느려지면 대기열/지연↑(실트래픽 유사)

품질 검증 도구

  • check: 응답 내용 검증(예: 상태코드 200, JSON 필드 존재 등). 실패율을 추적.
  • threshold: 테스트 실패 기준. ex) http_req_duration{tag:api}.p(95) < 300를 만족 못하면 테스트 실패 처리.

etc

  • Stages: VU 수를 시간대별로 증감하는 단계들(예: 0→50→100→0)
  • Think Time: 사용자 머무름/대기 시간. sleep(t)로 현실적인 사용 패턴을 반영
  • Tags/Groups: 요청에 라벨을 달아 엔드포인트별 지표를 분리·분석

지표 결과 해석하기

  • RPS(처리량)는 증가했는데 p95 급상승: 큐 대기, DB 락, 스토리지 IO, 스레드/커넥션 풀 부족, GC 튀는 시점을 의심하고 확인하기
  • 에러율 상승: 타임아웃, 5xx, 외부 연동 실패. 응답코드별 분리 태깅 추천(tags: { name: "merge" })
  • http_req_waiting ↑: 서버 처리/의존성(디비·스토리지) 병목 의심하기
  • receiving/sending ↑: 응답 바디/네트워크 병목(대용량 다운로드·이미지 변환 결과 주의 해야함)
  • connecting/tls_handshaking ↑: 커넥션 재활용 안됨, Keep-Alive/풀 설정 확인하기

설치 및 샘플 테스트

k6 설치

brew install k6
k6 version

실행 방법

k6 run {작성한k6명}.js

테스트 1) RPS(도착률) 기반 테스트: 목표 RPS를 정확하게 설정하기

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

export const options = {
  discardResponseBodies: true,
  summaryTrendStats: ['min','avg','med','p(90)','p(95)','p(99)','max'],
  scenarios: {
    rpsTarget: {
      executor: 'constant-arrival-rate',
      rate: 30,                    // 목표: 초당 30 요청(RPS)
      timeUnit: '1s',
      duration: '3m',              // 총 3분 (첫 1분은 워밍업)
      preAllocatedVUs: 50,         // 내부적으로 필요한 VU 풀
      maxVUs: 100,
    },
  },
  thresholds: {
    'http_req_duration{api:merge}': ['p(95)<300'], // SLO: p95<300ms
    'http_req_failed': ['rate<0.01'],              // 실패율 <1%
  },
};

export default function () {
  const res = http.post('https://api.example.com/api/test', JSON.stringify({
    imageUrl: 'https://...',
    code: 'A',
    title: 'hello',
  }), { headers: { 'Content-Type': 'application/json' }, tags: { api: 'test' } });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'has data':   (r) => r.json('data') !== undefined,
  });

  sleep(Math.random() * 0.3);
}

테스트 2) 동시 사용자 기반: 단계적으로 올려보기

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

export const options = {
  stages: [
    { duration: '1m', target: 20 }, // 워밍업
    { duration: '2m', target: 50 }, // 본 구간
    { duration: '1m', target: 0 },  // 쿨다운
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get('https://api.example.com/health', { tags: { api: 'health' } });
  check(res, { '200 OK': (r) => r.status === 200 });
  sleep(1); // 사용자의 머무름
}

테스트 3) 엔드포인트별 분리/분석 (태그/그룹)

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

export default function () {
  group('Visitor Journey', () => {
    // 1) 로그인
    let res = http.post('https://api.example.com/login', { id: 'u', pw: 'p' }, { tags: { api: 'login' } });
    check(res, { 'login 200': (r) => r.status === 200 });

    // 2) 요청
    res = http.post('https://api.example.com/api/test', JSON.stringify({ data: '...' }),
      { headers: { 'Content-Type': 'application/json' }, tags: { api: 'test' } });
    check(res, { 'merge 200': (r) => r.status === 200 });

    // 3) 조회
    res = http.get('https://api.example.com/api/test?id=123', { tags: { api: 'get' } });
    check(res, { 'status 200': (r) => r.status === 200 });
  });
}

테스트 4) 커스텀 Metric(성공률/큐 대기 등): 필요 시

import { Rate, Trend, Counter } from 'k6/metrics';
export const successRate = new Rate('biz_success_rate');
export const dequeueWait = new Trend('queue_wait_ms');
export const mergeCount = new Counter('merge_count');

export default function () {
  // 비즈니스 로직에 맞게 측정치 기록
  // successRate.add(true/false);
  // dequeueWait.add(waitMs);
  // mergeCount.add(1);
}
728x90
반응형

'Infra > Server' 카테고리의 다른 글

PM2 restart vs reload  (2) 2025.08.18
ppk to pem  (0) 2025.06.16
hombrew 설치 및 zsh: command not found 해결  (0) 2021.11.08
빌드의 자동화를 위한 Jenkins(젠킨스)  (0) 2021.10.13