TL;DR

  • k6는 JavaScript 기반 “testing as code”로 시나리오·지표·CI 통합에 강한 부하 테스트 도구다.
  • hey는 단일 엔드포인트를 즉시 벤치마크해 RPS와 지연 분포를 빠르게 파악하는 경량 CLI다.
  • 목적에 따라 k6로 현실적인 부하 시나리오를 검증하고, hey로 빠른 탐색 측정을 병행한다.

1. 개념

k6와 hey는 시스템의 처리량과 응답 지연을 측정해 성능 병목을 찾는 부하 테스트 도구다. k6는 시나리오 기반 테스트를 코드로 관리하고, hey는 단일 요청 패턴을 빠르게 벤치마크한다.

2. 배경

마이크로서비스와 클라우드 환경에서 트래픽 패턴이 다양해지며, 실제 사용자 부하를 재현할 수 있는 시나리오 테스트와 즉석 성능 측정 도구가 동시에 필요해졌다.

3. 이유

단순한 평균 지연이 아니라 퍼센타일, 병목 구간, 포화 시점을 빠르게 확인해야 안정적인 서비스 운영과 용량 계획이 가능하다. 도구 선택은 테스트 목적과 비용/속도 요구에 따라 달라진다.

4. 특징

k6는 확장성과 자동화에 강하고, hey는 가벼운 실행과 간단한 결과 해석이 장점이다. 두 도구 모두 RPS와 지연 분포를 제공해 병목을 수치로 드러낸다.

5. 상세 내용

k6 & hey 부하 테스트 완전 가이드

작성일: 2026-04-06 카테고리: Backend / Performance / Load Testing / HTTP Benchmarking 키워드: k6, hey, Load Testing, Stress Testing, Performance Testing, Virtual User, RPS, Latency, Coordinated Omission, HDR Histogram, Grafana, wrk, vegeta, oha, JMeter, Gatling, Locust, CI/CD


목차

  1. 개요
  2. 용어 사전
  3. 등장 배경과 이유
  4. 역사적 기원과 진화
  5. 학술적/이론적 배경
  6. k6 아키텍처 상세
  7. hey 아키텍처 상세
  8. 대안 비교
  9. 상황별 최적 선택
  10. 베스트 프랙티스
  11. 안티패턴과 함정
  12. 빅테크 실전 사례
  13. 참고 자료

1. 개요

1.1 부하 테스트란 무엇인가

부하 테스트(Load Testing)는 시스템이 예상 트래픽 조건에서 정상적으로 동작하는지 검증하는 비기능(non-functional) 테스트다. 단위 테스트가 “코드가 올바른가”를 검증한다면, 부하 테스트는 “시스템이 실전 부하를 견디는가”를 검증한다.

┌─────────────────────────────────────────────────────────────────┐
│                    부하 테스트의 핵심 질문                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 동시 사용자 1,000명일 때 응답 시간은?        (Latency)      │
│  2. 초당 몇 건의 요청을 처리할 수 있는가?        (Throughput)   │
│  3. 어느 지점에서 시스템이 포화되는가?            (Saturation)   │
│  4. 포화 이후 시스템은 우아하게 저하되는가?       (Degradation)  │
│  5. 장시간 부하에서 메모리 누수는 없는가?         (Stability)    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

1.2 k6: “Testing as Code” 철학

k6는 Grafana Labs가 소유한 오픈소스 부하 테스트 도구다. Go 엔진 + JavaScript ES6 스크립팅이라는 독특한 조합으로, 개발자 친화적인 “testing as code” 철학을 구현한다.

핵심 특징:

  • Go 엔진: 높은 성능과 낮은 리소스 사용. 단일 머신에서 수만 VU 생성 가능
  • JavaScript ES6 스크립팅: 테스트 시나리오를 코드로 작성. 버전 관리, 코드 리뷰, 모듈화 가능
  • CLI-first: GUI 없이 터미널에서 실행. CI/CD 파이프라인에 자연스럽게 통합
  • 내장 프로토콜: HTTP/1.1, HTTP/2, WebSocket, gRPC, Redis, Browser(Chromium)
  • 확장 가능: xk6 시스템으로 Go 기반 커스텀 확장 개발
// k6의 철학: 테스트를 코드로 관리한다
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },  // 2분간 100 VU까지 증가
    { duration: '5m', target: 100 },  // 5분간 100 VU 유지
    { duration: '2m', target: 0 },    // 2분간 0 VU까지 감소
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% 요청이 500ms 이내
  },
};

export default function () {
  const res = http.get('https://api.example.com/users');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);  // Think Time: 실제 사용자처럼 1초 대기
}

1.3 hey: 경량 HTTP 벤치마킹

hey는 Go 기반의 경량 HTTP 벤치마킹 도구다. Apache Bench(ab)의 현대적 대체로, 단일 엔드포인트에 대한 빠른 성능 측정에 최적화되어 있다.

핵심 특징:

  • 즉시 실행: 스크립트 작성 없이 CLI 한 줄로 벤치마크 시작
  • HTTP/2 기본 지원: 현대 프로토콜 자동 활용
  • 정확한 Latency 분포: Percentile 기반 히스토그램 출력
  • Go 네이티브: 크로스 플랫폼, 단일 바이너리 배포
# hey의 철학: 빠르고 정확하게 측정한다
hey -n 10000 -c 200 https://api.example.com/health

# -n 10000: 총 10,000개 요청
# -c 200:   동시 200개 워커

1.4 포지셔닝 차이

┌──────────────────────────────────────────────────────────────┐
│                     도구 포지셔닝 스펙트럼                     │
│                                                              │
│  단순 ◄────────────────────────────────────────────► 복잡    │
│                                                              │
│  hey    vegeta   wrk    k6        Gatling    JMeter          │
│  oha    ab       wrk2   Artillery Locust     LoadRunner      │
│  ├──────┤        ├──┤   ├─────────────┤      ├──────┤        │
│  즉석 벤치마크   경량    시나리오 기반 테스트  엔터프라이즈     │
│                  부하    CI/CD 통합                           │
│                  생성    모니터링 연계                         │
└──────────────────────────────────────────────────────────────┘
기준 k6 hey
목적 복잡한 사용자 시나리오 시뮬레이션 단일 엔드포인트 빠른 벤치마크
스크립팅 JavaScript ES6 (필수) CLI 옵션만 (스크립트 불필요)
프로토콜 HTTP, WebSocket, gRPC, Redis, Browser HTTP/1.1, HTTP/2
시나리오 복잡도 다단계 사용자 플로우, 인증, 데이터 파라미터화 단일 요청 반복
CI/CD 통합 Threshold 기반 자동 Pass/Fail Exit code 기반 수동 판정
모니터링 Grafana, InfluxDB, Prometheus, Cloud 터미널 출력
학습 곡선 중간 (JS 지식 필요) 매우 낮음
설치 크기 ~30MB ~5MB
사용 시점 Sprint 내 성능 테스트, 릴리스 전 검증 배포 직후 빠른 확인, 로컬 개발 중 체크

1.5 부하 테스트 유형 비교

테스트 유형 목적 VU 패턴 기간 k6 지원 hey 적합
Smoke Test 최소 부하에서 기본 동작 확인 1-5 VU 1-2분 O O
Load Test 예상 트래픽에서 성능 검증 예상 VU 15-60분 O 부분적
Stress Test 시스템 한계점(breaking point) 탐색 점진적 증가 → 한계 초과 30-60분 O X
Soak Test 장시간 안정성 검증 (메모리 누수 등) 일정 VU 유지 수 시간~수 일 O 부분적
Spike Test 급격한 트래픽 증가 대응 확인 순간 급증 → 급감 5-15분 O X
Breakpoint Test 최대 처리 용량 측정 무한 증가 가변 O X
Benchmark Test 특정 엔드포인트 기준선 측정 고정 1-5분 O O

2. 용어 사전

2.1 부하 테스트 핵심 용어

용어 정의
RPS (Requests Per Second) 초당 처리 요청 수. 시스템의 처리량(throughput)을 나타내는 가장 기본적인 지표. k6에서는 http_reqs Rate 메트릭으로 측정한다.
VU (Virtual User) 가상 사용자. 실제 사용자의 행동을 시뮬레이션하는 동시 실행 단위. k6에서는 각 VU가 하나의 goroutine으로 실행된다.
Latency 요청을 보낸 시점부터 응답을 받은 시점까지의 시간. k6에서는 http_req_duration으로 측정하며, DNS 조회, TCP 연결, TLS 핸드셰이크, 서버 처리, 응답 전송 시간을 포함한다.
Ramp-up 테스트 시작 시 VU 수를 점진적으로 증가시키는 구간. 실제 트래픽 패턴을 시뮬레이션하고, 시스템이 점진적으로 부하를 받도록 한다.
Ramp-down 테스트 종료 시 VU 수를 점진적으로 감소시키는 구간. 시스템의 부하 해제 후 복구 능력을 검증한다.
Think Time 사용자의 “생각 시간”. 실제 사용자가 페이지를 읽거나 폼을 작성하는 동안의 대기 시간. k6에서 sleep()으로 구현하며, 이를 빠뜨리면 비현실적으로 높은 부하가 발생한다.
Iteration VU가 테스트 스크립트의 default 함수를 한 번 완전히 실행하는 것. 하나의 iteration은 하나의 사용자 세션에 해당할 수 있다.

2.2 Latency 측정 용어

용어 정의
p50 (Median) 전체 요청의 50%가 이 시간 이내에 완료됨. “일반적인” 사용자 경험을 나타낸다.
p90 전체 요청의 90%가 이 시간 이내에 완료됨. “대부분의” 사용자 경험.
p95 전체 요청의 95%가 이 시간 이내에 완료됨. SLO 설정에 가장 흔히 사용되는 percentile.
p99 전체 요청의 99%가 이 시간 이내에 완료됨. “거의 최악의” 사용자 경험. Tail latency라고도 한다.
p99.9 1,000개 중 999개의 요청이 이 시간 이내. 대규모 서비스에서 중요한 지표. 10만 DAU 서비스에서 p99.9는 매일 100명의 사용자 경험이다.
Average (Mean) 전체 응답 시간의 산술 평균. 부하 테스트에서 가장 위험한 지표. 극단값에 민감하고, latency 분포의 실제 형태를 숨긴다.
Percentile Distribution 응답 시간의 누적 분포. 히스토그램으로 시각화하면 bimodal distribution 등 평균으로는 발견할 수 없는 패턴이 드러난다.

2.3 네트워킹/프로토콜 용어

용어 정의
Connection Pooling TCP 연결을 재사용하여 핸드셰이크 오버헤드를 줄이는 기법. k6는 VU당 커넥션 풀을 유지한다. hey는 -disable-keepalive로 비활성화 가능.
HTTP/2 Multiplexing 단일 TCP 연결에서 여러 HTTP 요청을 동시에 전송하는 HTTP/2 기능. hey는 HTTP/2를 기본 지원하며, k6도 HTTP/2를 자동 감지한다.
Keep-Alive TCP 연결을 닫지 않고 유지하여 후속 요청에 재사용하는 메커니즘. HTTP/1.1의 기본 동작이다.
TLS Handshake HTTPS 연결 수립 시 암호화 파라미터를 교환하는 과정. 부하 테스트 시 TLS 핸드셰이크 시간이 전체 latency의 상당 부분을 차지할 수 있다.

2.4 부하 모델 용어

용어 정의
Open Workload Model 새 요청의 도착률이 시스템 응답과 독립적인 모델. 이전 요청의 완료 여부와 관계없이 일정한 비율로 새 요청이 도착한다. 실제 웹 트래픽에 가깝다. k6의 constant-arrival-rate executor가 이를 구현한다.
Closed Workload Model 고정된 수의 VU가 “요청 → 응답 대기 → 다음 요청” 사이클을 반복하는 모델. 응답이 느려지면 요청률도 함께 감소하여 시스템에 부하를 덜 주는 효과가 있다. k6의 constant-vus executor가 이를 구현한다.
Coordinated Omission Closed model에서 느린 응답이 후속 요청 발생을 지연시켜, 결과적으로 높은 latency 구간의 요청 수가 줄어들고 측정 결과가 낙관적으로 왜곡되는 현상. Gil Tene이 2013년 명명했다.
HDR Histogram High Dynamic Range Histogram. Gil Tene이 개발한 알고리즘으로, 넓은 범위의 값(1us ~ 1시간)을 설정 가능한 정밀도로 기록하면서도 고정 메모리를 사용한다. Coordinated Omission 보정 기능이 내장되어 있다.

2.5 SRE/운영 용어

용어 정의
SLA (Service Level Agreement) 서비스 제공자와 사용자 간의 계약. “월 가용성 99.9%” 같은 법적 구속력 있는 약속. 위반 시 크레딧 환불 등의 조치가 따른다.
SLO (Service Level Objective) 내부적으로 설정하는 목표. “p95 응답 시간 500ms 이하” 같은 기준. SLA보다 엄격하게 설정하여 SLA 위반을 사전에 방지한다.
SLI (Service Level Indicator) SLO를 측정하는 실제 지표. “지난 5분간 p95 latency = 423ms” 같은 측정값. k6의 Threshold가 SLI 기반 자동 판정을 구현한다.
Threshold k6에서 테스트 성공/실패를 자동으로 판정하는 기준. SLO를 코드로 표현한 것이다. 위반 시 k6는 non-zero exit code를 반환하여 CI/CD 파이프라인을 중단시킨다.
Check k6에서 응답의 정확성을 검증하는 assertion. HTTP 상태 코드, 응답 본문, 헤더 등을 검증한다. Threshold와 달리 Check 실패가 테스트 자체를 실패시키지는 않지만, Check 성공률에 대한 Threshold를 설정할 수 있다.

2.6 테스트 유형 용어

용어 정의
Smoke Test 최소 부하(1-5 VU)로 시스템의 기본 동작을 확인하는 테스트. 스크립트 오류나 환경 설정 문제를 빠르게 발견한다. 모든 부하 테스트의 첫 단계.
Load Test 예상 최대 트래픽을 시뮬레이션하여 SLO 충족 여부를 검증하는 테스트. 가장 일반적인 부하 테스트 유형.
Stress Test 예상 트래픽을 초과하는 부하를 가하여 시스템의 한계점(breaking point)을 찾는 테스트. 시스템이 과부하에서 어떻게 동작하는지(graceful degradation vs crash) 확인한다.
Soak Test (Endurance Test) 장시간(수 시간~수 일) 동안 일정 부하를 유지하여 메모리 누수, 커넥션 풀 고갈, 로그 파일 증가 등 시간 의존적 문제를 발견하는 테스트.
Spike Test 극단적으로 짧은 시간 내에 급격한 트래픽 증가를 시뮬레이션하는 테스트. Flash sale, 뉴스 이벤트, DDoS 등의 시나리오를 검증한다.

3. 등장 배경과 이유

3.1 전통적 부하 테스트 도구의 한계

3.1.1 Apache Bench (ab)의 근본적 한계

Apache HTTP Server 프로젝트의 부산물로 1996년에 등장한 ab는 가장 오래된 HTTP 벤치마킹 도구다. 그러나 현대적 요구사항을 충족하지 못한다:

한계 설명
단일 스레드 하나의 이벤트 루프로 동작. 멀티코어 활용 불가. 클라이언트 측 병목 발생
HTTP/1.0 Only HTTP/1.1 Keep-Alive 불완전, HTTP/2 미지원
Coordinated Omission Closed model만 지원. 느린 응답이 측정을 왜곡
통계 부족 Percentile 분포 미제공. 평균과 중앙값만 표시
스크립팅 불가 복잡한 시나리오 표현 불가능
유지보수 중단 사실상 개발이 중단된 상태

3.1.2 JMeter의 구조적 문제

Apache JMeter(1998)는 가장 널리 사용되는 부하 테스트 도구지만, 근본적인 아키텍처 한계가 있다:

┌──────────────────────────────────────────────────────────┐
│                  JMeter 아키텍처의 문제점                   │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  1. Thread-per-VU 모델                                   │
│     ┌────────┐ ┌────────┐ ┌────────┐ ... ┌────────┐    │
│     │Thread 1│ │Thread 2│ │Thread 3│     │Thread N│    │
│     └────────┘ └────────┘ └────────┘     └────────┘    │
│     → 1 VU = 1 OS Thread = ~1MB 스택 메모리              │
│     → 1000 VU = ~1GB 메모리 (오버헤드 제외)               │
│     → Context switching 오버헤드 급증                     │
│                                                          │
│  2. XML 기반 테스트 계획                                  │
│     → 버전 관리 어려움 (diff 불가능)                      │
│     → 코드 리뷰 불가능                                    │
│     → 모듈화/재사용 어려움                                │
│                                                          │
│  3. GUI 의존성                                            │
│     → CI/CD 통합 번거로움                                 │
│     → 헤드리스 모드가 있지만 설계가 GUI 중심              │
│                                                          │
│  4. JVM 오버헤드                                          │
│     → GC 일시정지(Stop-the-World)가 측정 결과 왜곡        │
│     → 워밍업 필요                                         │
│                                                          │
└──────────────────────────────────────────────────────────┘

3.1.3 Gatling의 진입장벽

Gatling(2012)은 Scala/Akka 기반으로 JMeter보다 효율적이지만:

  • Scala DSL: Java/JS 개발자에게 학습 부담
  • JVM 의존: 여전히 GC 영향, 높은 메모리 사용
  • 사이드카 패턴 어려움: 컨테이너 환경에서 경량 배포 불리
  • 상용화 경향: Gatling Enterprise (유료)가 핵심 기능 흡수 추세

3.1.4 Locust의 GIL 한계

Locust(2013)는 Python 기반으로 접근성이 뛰어나지만:

  • Python GIL: Global Interpreter Lock으로 인해 진정한 병렬 실행 불가
  • 단일 프로세스 RPS 한계: 수천 RPS에서 클라이언트 병목
  • 분산 모드 필수: 높은 부하 생성에 multi-process/multi-machine 필요
  • 통계 정밀도: percentile 계산이 근사치

3.1.5 LoadRunner의 비용 장벽

Micro Focus LoadRunner는 엔터프라이즈 표준이었지만:

  • 라이선스 비용: VU당 과금. 대규모 테스트에 수만 달러
  • 독점적 스크립팅: C, Java, VBScript 등 독자적 API
  • 무거운 인프라: Windows 중심, 전용 Controller/Agent 필요
  • 클라우드 네이티브 부적합: 컨테이너/Kubernetes 환경에 맞지 않음

3.2 k6가 제시한 해법

┌─────────────────────────────────────────────────────────────┐
│              기존 도구의 한계 → k6의 해법                     │
├─────────────────────────────┬───────────────────────────────┤
│         문제                │          k6 해법              │
├─────────────────────────────┼───────────────────────────────┤
│ Thread-per-VU (JMeter)      │ Goroutine-per-VU (경량)      │
│ XML 테스트 계획              │ JavaScript ES6 스크립트       │
│ GUI 의존                    │ CLI-first, CI/CD 네이티브     │
│ JVM/Python GIL 오버헤드      │ Go 네이티브 바이너리          │
│ 독점 라이선스                │ AGPL-3.0 오픈소스            │
│ 제한적 프로토콜              │ HTTP, WS, gRPC, Redis, 등    │
│ Closed model only           │ Open + Closed 모델 모두 지원  │
│ 분산 설정 복잡               │ k6 Cloud / k6-operator(K8s)  │
│ 결과 해석 어려움              │ Grafana 생태계 통합           │
└─────────────────────────────┴───────────────────────────────┘

k6의 설계 원칙:

  1. Developer Experience First: 테스트를 코드로 관리하고, IDE에서 작성하고, Git으로 버전 관리
  2. Efficiency: Go 엔진으로 최소 리소스에서 최대 부하 생성
  3. Extensibility: xk6 시스템으로 커스텀 프로토콜/출력 확장
  4. Automation: Threshold 기반 자동 Pass/Fail로 CI/CD 게이트 구현

3.3 hey가 제시한 해법

hey는 ab의 근본적 한계를 Go의 동시성 모델로 해결했다:

ab의 한계 hey의 해법
단일 스레드 Go goroutine으로 멀티코어 활용
HTTP/1.0 HTTP/2 기본 지원
불완전한 통계 Percentile 히스토그램
빌드 복잡 단일 바이너리 (go install)
OpenSSL 의존 Go crypto/tls 내장

hey의 핵심 가치는 단순함이다. 스크립트 없이, 설정 파일 없이, CLI 한 줄로 정확한 벤치마크를 얻을 수 있다.


4. 역사적 기원과 진화

4.1 부하 테스트 도구 진화 연대표

1996 ─── Apache Bench (ab)
         │  Apache HTTP Server에 포함된 최초의 CLI 벤치마크 도구
         │  단일 스레드, HTTP/1.0, 여전히 "빠른 확인"용으로 사용됨
         │
1998 ─── Apache JMeter
         │  Stefano Mazzocchi가 Apache Tomcat 테스트용으로 개발
         │  Java GUI, Thread-per-VU 모델
         │  → 20년간 사실상의 업계 표준
         │
1998 ─── httperf (HP Labs)
         │  David Mosberger & Tai Jin
         │  학술 연구용 HTTP 벤치마크. Open model 최초 구현 중 하나
         │
2000 ─── Siege
         │  Joe Dog Software. ab의 개선판
         │  다중 URL, HTTP/1.1 지원
         │
2012 ─── wrk
         │  Will Glozer. C 기반, epoll/kqueue
         │  ★ 극단적 RPS 생성 (단일 머신 수십만 RPS)
         │  Lua 스크립팅으로 요청 커스터마이징
         │
2012 ─── Gatling
         │  Stéphane Landelle. Scala + Akka + Netty
         │  비동기 논블로킹, HTML 리포트
         │
2013 ─── Locust
         │  Python, gevent 기반 coroutine
         │  직관적인 Python 스크립팅
         │  Web UI 내장 분산 모드
         │
2013 ─── vegeta
         │  Tomás Senart. Go 기반 HTTP 부하 생성기
         │  ★ Open model (constant-rate) 최초 Go 구현 중 하나
         │  Unix 파이프라인 친화적 설계
         │
2014 ─── boom → hey
         │  Jaana Dogan (rakyll). Go 기반 ab 대체
         │  Python boom과 이름 충돌 → hey로 개명
         │
2014 ─── wrk2
         │  Gil Tene. wrk 포크
         │  ★ Coordinated Omission 보정 최초 구현
         │  Constant-throughput 모드
         │
2017 ─── k6 (오픈소스 출시)
         │  Load Impact → k6 프로젝트
         │  Go 엔진 + JavaScript ES6 스크립팅
         │  ★ "Testing as Code" 패러다임 대중화
         │
2020 ─── oha
         │  Ryo Hatanaka. Rust 기반 HTTP 벤치마크
         │  TUI 실시간 차트, HTTP/2
         │
2021 ─── Grafana Labs, k6 인수
         │  Grafana 관측성 생태계에 편입
         │  Grafana Cloud k6 서비스 출시
         │
2025 ─── k6 v1.0 릴리스
         │  ★ 안정 API 보장. JS 런타임 Sobek(goja fork)
         │  k6 browser GA, 확장 에코시스템 성숙

4.2 k6의 탄생과 진화

4.2.1 이름의 유래

k6의 이름에는 흥미로운 역사가 있다:

  1. Load Impact (2010): 스웨덴 기반 SaaS 부하 테스트 회사로 시작
  2. Speedboat 프로젝트 (2016): Load Impact 내부에서 CLI 기반 오픈소스 부하 테스트 도구 개발 시작. “빠른 보트”라는 의미로 성능 테스트의 민첩함을 상징
  3. 이름 선정: 팀이 짧고 기억하기 쉬운 이름을 찾는 과정에서, 알파벳+숫자 조합을 탐색. “k6”는 세계에서 두 번째로 높은 산 K2에서 영감을 받되, 아직 등록되지 않은 도메인을 찾아 k6로 결정
  4. 오픈소스 (2017.02): GitHub에 loadimpact/k6로 공개
  5. Load Impact → k6 리브랜딩 (2020): 회사 이름 자체를 k6로 변경. 도구와 회사의 정체성을 통일
  6. Grafana 인수 (2021.06): Grafana Labs가 k6 인수. grafana/k6로 리포지토리 이전
  7. k6 v1.0 (2025.02.25): 8년의 개발 끝에 안정 버전 선언. JS 런타임을 goja에서 Sobek(자체 fork)으로 전환

4.2.2 Grafana 인수의 의미

Grafana Labs의 k6 인수는 관측성(Observability) + 테스트의 융합을 의미한다:

┌─────────────────────────────────────────────────────┐
│              Grafana 관측성 생태계                    │
│                                                     │
│  Metrics:  Prometheus / Mimir                       │
│  Logs:     Loki                                     │
│  Traces:   Tempo                                    │
│  Testing:  k6 ← 여기에 편입                         │
│  Viz:      Grafana Dashboard                        │
│  Alerting: Grafana Alerting                         │
│                                                     │
│  → 성능 테스트 결과를 메트릭/로그/트레이스와         │
│    단일 대시보드에서 상관분석(correlation) 가능       │
└─────────────────────────────────────────────────────┘

4.3 hey의 탄생과 진화

4.3.1 boom에서 hey로

hey의 이름 변경 과정은 오픈소스 네이밍 충돌의 대표적 사례다:

  1. Python boom (2012): Tarek Ziadé가 Python으로 작성한 HTTP 벤치마크 도구. ab의 Python 대체를 목표로 함
  2. Go boom (2014): Jaana Dogan(GitHub: rakyll)이 Go로 작성한 HTTP 벤치마크 도구. Python boom과 동일한 이름을 사용
  3. 이름 충돌 (2016): PyPI의 boom과 Go의 boom이 공존하면서 혼란 발생. 특히 brew install boom에서 어떤 boom이 설치되는지 불명확
  4. hey로 개명 (2016): Jaana Dogan이 Go boom을 hey로 개명. 짧고 친근한 인사말에서 착안. “Hey, how fast is your server?”

4.3.2 Jaana Dogan (rakyll)

hey의 창시자 Jaana Dogan은 Go 생태계의 핵심 기여자다:

  • Google Go 팀 소속 (2014-2019)
  • golang.org/x/net, golang.org/x/oauth2 등 핵심 패키지 기여
  • Google Ads 성능 엔지니어 (JCTool과 hey 개발의 실전적 동기)
  • hey 외에도 go-cloud, google-cloud-go 등 Go 클라우드 생태계 기여

4.4 핵심 전환점

2014: wrk2와 Coordinated Omission 인식 - Gil Tene이 wrk를 포크하여 Coordinated Omission 보정을 구현한 wrk2를 출시. 이후 모든 부하 테스트 도구가 CO 문제를 인식하기 시작했으며, k6는 Open model executor로 이를 구조적으로 해결했다.

2017: k6 오픈소스화 - “Testing as Code” 패러다임을 대중화. 부하 테스트가 개발자의 영역으로 진입하는 전환점이 되었다.

2021: Grafana 인수 - 부하 테스트가 관측성 생태계의 일부로 편입. 테스트 결과를 Grafana 대시보드에서 메트릭/로그/트레이스와 함께 분석할 수 있게 되었다.

2025: k6 v1.0 - 8년간의 개발 후 안정 API를 보장하는 v1.0 출시. JS 런타임을 Sobek으로 전환하여 ES2015+ 모듈을 네이티브 지원하고, 확장 생태계가 성숙했다.


5. 학술적/이론적 배경

5.1 Little’s Law: L = λW

Little’s Law(1961)는 대기 이론(Queueing Theory)의 가장 기본적인 법칙으로, 부하 테스트의 수학적 기초를 제공한다.

┌─────────────────────────────────────────────┐
│           Little's Law                       │
│                                             │
│           L = λ × W                          │
│                                             │
│   L: 시스템 내 평균 요청 수 (= 동시 사용자)  │
│   λ: 평균 도착률 (= RPS)                    │
│   W: 평균 체류 시간 (= Latency)              │
│                                             │
│   → VU = RPS × (Latency + Think Time)       │
│                                             │
│   예시:                                      │
│   목표 RPS = 1000, 평균 Latency = 200ms,    │
│   Think Time = 1s                            │
│   → VU = 1000 × (0.2 + 1.0) = 1200 VU      │
│                                             │
└─────────────────────────────────────────────┘

부하 테스트에서의 실전 적용:

시나리오 RPS Latency Think Time 필요 VU
경량 API 5,000 50ms 0s 250
웹 페이지 500 200ms 3s 1,600
결제 API 100 500ms 5s 550
배치 조회 50 2s 0s 100

주의사항: Little’s Law는 안정 상태(steady state)에서만 유효하다. Ramp-up 구간이나 시스템이 포화된 상태에서는 적용할 수 없다. k6에서는 ramping 이후 일정 VU를 유지하는 구간에서 Little’s Law를 적용하여 결과를 검증한다.

5.2 Coordinated Omission

5.2.1 Gil Tene의 발견

Gil Tene(Azul Systems CTO)은 2013년 “How NOT to Measure Latency” 발표에서 대부분의 부하 테스트 도구가 가지는 근본적 측정 오류를 지적했다.

┌─────────────────────────────────────────────────────────────┐
│            Coordinated Omission 메커니즘                     │
│                                                             │
│  정상 상태 (Closed Model):                                  │
│  VU:  요청──→응답(10ms)──→요청──→응답(10ms)──→요청──→...    │
│  측정: 10ms, 10ms, 10ms, 10ms, 10ms                        │
│                                                             │
│  GC 일시정지 발생 (500ms):                                   │
│  VU:  요청──────────────→응답(500ms)──→요청──→응답(10ms)    │
│  측정: 500ms, 10ms                                          │
│                                                             │
│  ★ 문제: 정상이었으면 500ms 동안 50개 요청이 있었을 것이다   │
│    하지만 Closed model에서는 1개만 측정된다.                 │
│    → 나머지 49개의 "지연된" 요청이 누락된다.                 │
│    → 평균 latency가 낙관적으로 왜곡된다.                    │
│                                                             │
│  실제 (보정 후):                                             │
│    500ms 동안 영향받은 50개 요청 모두 높은 latency로 기록    │
│    → 훨씬 높은 p99, p99.9 값                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2.2 구체적 숫자 예시

GC 일시정지로 100ms 지연이 1초에 1번 발생하는 서비스:

메트릭 CO 미보정 CO 보정 후 차이
p50 1ms 1ms 동일
p99 5ms 50ms 10x
p99.9 10ms 100ms 10x
Max 100ms 100ms 동일

CO 미보정 결과를 기반으로 SLO를 설정하면, 프로덕션에서 실제 사용자 경험과 큰 괴리가 발생한다.

5.2.3 해결 방법

접근 도구 메커니즘
Open Model k6 (constant-arrival-rate) 응답 대기 없이 일정 비율로 요청 발사. 구조적으로 CO 불가능
wrk2 보정 wrk2 Closed model이지만 “예정된 시간”과 “실제 응답 시간”의 차이를 보정
HDR Histogram 보정 HdrHistogram 라이브러리 기록 시 예상 간격(expected interval)을 기반으로 누락된 샘플을 보간

5.3 HDR Histogram

Gil Tene이 개발한 HDR(High Dynamic Range) Histogram은 넓은 범위의 latency 값을 효율적으로 기록하는 알고리즘이다.

5.3.1 알고리즘 구조

┌──────────────────────────────────────────────────────────┐
│                HDR Histogram 내부 구조                     │
│                                                          │
│  범위: 1μs ~ 3,600,000,000μs (1시간)                    │
│  정밀도: 유효숫자 3자리                                   │
│                                                          │
│  인덱스 구조 (Sub-bucket 방식):                           │
│                                                          │
│  Bucket 0: [1, 2)      → 2048 sub-buckets               │
│  Bucket 1: [2, 4)      → 2048 sub-buckets               │
│  Bucket 2: [4, 8)      → 2048 sub-buckets               │
│  Bucket 3: [8, 16)     → 2048 sub-buckets               │
│  ...                                                     │
│  Bucket N: [2^N, 2^(N+1)) → 2048 sub-buckets            │
│                                                          │
│  메모리 사용: ~수십 KB (고정)                              │
│  기록 시간: O(1)                                          │
│  Percentile 조회: O(1)                                    │
│                                                          │
│  ★ CO 보정: 기록 시 expected_interval을 받아               │
│    누락된 중간 값들을 자동으로 보간                        │
│                                                          │
└──────────────────────────────────────────────────────────┘

5.3.2 기존 히스토그램과의 차이

특성 고정 버킷 히스토그램 HDR Histogram
범위 사전 정의 필요 자동 확장
정밀도 버킷 크기에 따라 가변 전 범위에서 일정 (유효숫자 기준)
메모리 범위 비례 고정 (~수십 KB)
CO 보정 미지원 내장
Percentile 정확도 버킷 경계에서 오차 설정 정밀도 보장

5.4 Open vs Closed Workload Model

Schroeder, Wierman, Harchol-Balter의 NSDI 2006 논문 “Open Versus Closed: A Cautionary Tale”은 부하 모델 선택이 성능 측정 결과에 미치는 근본적 영향을 분석했다.

┌──────────────────────────────────────────────────────────┐
│              Open vs Closed Workload Model                │
│                                                          │
│  Closed Model:                                           │
│  ┌────┐    ┌────────┐    ┌────┐                         │
│  │ VU │───→│ Server │───→│ VU │──→ (think) ──→ 재요청   │
│  └────┘    └────────┘    └────┘                         │
│  → 서버 응답 후에만 다음 요청                             │
│  → 서버가 느려지면 요청률도 감소 (자동 백프레셔)          │
│  → 실제보다 낙관적인 결과                                │
│                                                          │
│  Open Model:                                             │
│  요청1 ──→ ┌────────┐                                    │
│  요청2 ──→ │ Server │                                    │
│  요청3 ──→ │        │  ← 서버 상태와 무관하게             │
│  요청4 ──→ │        │    일정 비율로 요청 도착            │
│  요청5 ──→ └────────┘                                    │
│  → 서버가 느려져도 요청률 유지                             │
│  → 큐잉 효과가 현실적으로 반영                            │
│  → Coordinated Omission 발생하지 않음                    │
│                                                          │
└──────────────────────────────────────────────────────────┘

핵심 발견: 동일한 시스템에 대해 Open model과 Closed model로 측정하면, Closed model의 평균 응답 시간이 Open model 대비 최대 수십 배 낮게 나올 수 있다. 이는 Closed model의 자동 백프레셔 효과 때문이다.

실전 규칙:

  • 웹 서비스의 외부 사용자 트래픽: Open model (사용자는 서버 상태를 모름)
  • 내부 배치 처리, 작업 큐: Closed model (워커 수 고정)
  • 확실하지 않다면: Open model 사용 (더 보수적이고 현실적)

5.5 Brendan Gregg의 성능 분석 방법론

5.5.1 USE Method

Brendan Gregg의 USE(Utilization, Saturation, Errors) Method는 부하 테스트 결과 분석의 체계적 프레임워크를 제공한다:

구성 요소 정의 부하 테스트 적용
U (Utilization) 리소스가 사용 중인 시간 비율 CPU 사용률, 메모리 사용률, 디스크 I/O 사용률
S (Saturation) 리소스의 대기열 길이 커넥션 풀 대기, 스레드 풀 대기, TCP backlog
E (Errors) 에러 이벤트 수 HTTP 5xx, 타임아웃, 커넥션 거부

부하 테스트 중 USE 메트릭을 함께 수집하면, 병목 지점을 체계적으로 식별할 수 있다:

# k6 실행 중 서버 측 USE 메트릭 수집 예시

# Utilization
mpstat 1       # CPU 사용률
free -m        # 메모리 사용률
iostat -x 1    # 디스크 I/O 사용률

# Saturation
ss -s          # TCP 소켓 상태 (SYN_RECV, ESTABLISHED, TIME_WAIT)
netstat -an | grep -c ESTABLISHED  # 활성 연결 수

# Errors
dmesg | grep -i "out of memory"
journalctl -u nginx --since "5 min ago" | grep -c 502

5.5.2 Flame Graphs

Brendan Gregg가 개발한 Flame Graph는 부하 테스트 중 서버 측 프로파일링에 필수적이다:

  • CPU Flame Graph: k6 부하 중 서버의 CPU 핫스팟 식별
  • Off-CPU Flame Graph: I/O 대기, 잠금 대기 등 블로킹 원인 식별
  • k6 + Continuous Profiling(Pyroscope) 연계로 부하와 프로파일을 동시에 분석

6. k6 아키텍처 상세

6.1 전체 아키텍처

┌─────────────────────────────────────────────────────────────────┐
│                        k6 엔진 아키텍처                          │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    CLI / API Layer                        │   │
│  │  k6 run script.js --vus 100 --duration 30s               │   │
│  └──────────────┬───────────────────────────────────────────┘   │
│                 │                                               │
│  ┌──────────────▼───────────────────────────────────────────┐   │
│  │              Execution Scheduler                          │   │
│  │  ┌─────────────────┐  ┌──────────────────────────────┐   │   │
│  │  │ Executor Config  │  │ 7가지 Executor 유형           │   │   │
│  │  │ (options.scenarios)│ │ (Open/Closed model 모두)     │   │   │
│  │  └─────────────────┘  └──────────────────────────────┘   │   │
│  └──────────────┬───────────────────────────────────────────┘   │
│                 │                                               │
│  ┌──────────────▼───────────────────────────────────────────┐   │
│  │              VU Runtime Pool                              │   │
│  │                                                          │   │
│  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐     ┌──────┐     │   │
│  │  │VU #1 │ │VU #2 │ │VU #3 │ │VU #4 │ ... │VU #N │     │   │
│  │  │      │ │      │ │      │ │      │     │      │     │   │
│  │  │Sobek │ │Sobek │ │Sobek │ │Sobek │     │Sobek │     │   │
│  │  │JS RT │ │JS RT │ │JS RT │ │JS RT │     │JS RT │     │   │
│  │  └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘     └──┬───┘     │   │
│  │     │        │        │        │             │         │   │
│  │  goroutine goroutine goroutine goroutine  goroutine    │   │
│  │                                                          │   │
│  └──────────────┬───────────────────────────────────────────┘   │
│                 │                                               │
│  ┌──────────────▼───────────────────────────────────────────┐   │
│  │              Output / Metrics Engine                      │   │
│  │                                                          │   │
│  │  ┌────────┐ ┌────────┐ ┌──────────┐ ┌───────────────┐   │   │
│  │  │ stdout │ │  JSON  │ │ InfluxDB │ │ Prometheus     │   │   │
│  │  │        │ │  CSV   │ │          │ │ Remote Write   │   │   │
│  │  └────────┘ └────────┘ └──────────┘ └───────────────┘   │   │
│  │                                                          │   │
│  │  ┌──────────────┐ ┌─────────────────────────────────┐   │   │
│  │  │ Grafana Cloud│ │ Thresholds Engine                │   │   │
│  │  │ k6           │ │ (Pass/Fail 자동 판정)            │   │   │
│  │  └──────────────┘ └─────────────────────────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.2 Go 엔진 + Sobek JS 런타임

k6 v1.0부터 JavaScript 런타임은 Sobek을 사용한다. Sobek은 goja (Go로 작성된 ES5.1 JS 엔진)를 포크하여 ES2015+ 기능을 추가한 것이다.

┌────────────────────────────────────────────────────────┐
│                 k6 런타임 구조                           │
│                                                        │
│  ┌──────────────────────────────────────────────────┐  │
│  │  JavaScript (ES2015+)                             │  │
│  │  - import/export modules                          │  │
│  │  - Arrow functions, template literals             │  │
│  │  - Destructuring, default params                  │  │
│  │  - for...of, Map, Set, Promise(제한적)            │  │
│  └──────────────────┬───────────────────────────────┘  │
│                     │ Sobek (goja fork)                 │
│                     │ JS → Go 바인딩                    │
│  ┌──────────────────▼───────────────────────────────┐  │
│  │  Go Native Layer                                  │  │
│  │  - net/http (HTTP 클라이언트)                      │  │
│  │  - goroutine (VU 실행)                            │  │
│  │  - channel (VU 간 조율)                           │  │
│  │  - sync/atomic (메트릭 수집)                       │  │
│  └──────────────────────────────────────────────────┘  │
│                                                        │
│  ★ Node.js 런타임이 아님:                              │
│    - npm 패키지 직접 사용 불가                          │
│    - fs, path 등 Node API 없음                         │
│    - 비동기는 Go 레벨에서 처리 (JS는 동기적)            │
│    - 번들러(webpack/esbuild)로 npm 패키지 변환 가능     │
│                                                        │
└────────────────────────────────────────────────────────┘

VU 실행 모델 상세:

// ★ init context: VU당 1회 실행. 파일 읽기, 모듈 import 등
import http from 'k6/http';
import { check } from 'k6';

const payload = JSON.parse(open('./data.json'));  // init에서만 가능

export const options = {
  vus: 10,
  duration: '30s',
};

// ★ default function: VU당 반복 실행. 실제 테스트 로직
export default function () {
  // 각 VU는 독립된 goroutine에서 이 함수를 반복 실행
  const res = http.post('https://api.example.com/items', JSON.stringify(payload), {
    headers: { 'Content-Type': 'application/json' },
  });
  check(res, { 'created': (r) => r.status === 201 });
}

// ★ setup: 전체 테스트에서 1회 실행. 테스트 데이터 준비
export function setup() {
  const loginRes = http.post('https://api.example.com/auth', JSON.stringify({
    username: 'testuser',
    password: 'testpass',
  }));
  return { token: loginRes.json('token') };
}

// ★ teardown: 전체 테스트 종료 시 1회 실행. 정리 작업
export function teardown(data) {
  http.del('https://api.example.com/test-data', {
    headers: { Authorization: `Bearer ${data.token}` },
  });
}

6.3 7가지 Executor

k6의 가장 강력한 기능 중 하나는 7가지 실행 모델(Executor)을 제공하여 Open/Closed 모델을 모두 지원하는 것이다.

Closed Model Executors (VU 기반)

Executor 설명 사용 시점
shared-iterations 지정된 총 iteration을 VU들이 나누어 실행 “정확히 N번 실행”이 필요할 때
per-vu-iterations 각 VU가 지정된 횟수만큼 실행 VU별 동일 작업량 보장이 필요할 때
constant-vus 지정된 기간 동안 고정 VU 수 유지 가장 기본적인 부하 테스트
ramping-vus 단계별로 VU 수를 증감 (stages) Load/Stress/Spike 테스트

Open Model Executors (도착률 기반)

Executor 설명 사용 시점
constant-arrival-rate 지정된 기간 동안 고정 RPS 유지 가장 권장. 정확한 throughput 측정
ramping-arrival-rate 단계별로 RPS를 증감 점진적 부하 증가 테스트

특수 Executor

Executor 설명 사용 시점
externally-controlled REST API로 VU 수를 실시간 제어 수동 탐색적 테스트, 외부 오케스트레이션

Executor 선택 상세

// ★ 권장: constant-arrival-rate (Open Model)
// 서버 응답 속도와 관계없이 일정 RPS를 유지
export const options = {
  scenarios: {
    // 시나리오 1: API 정상 부하
    normal_load: {
      executor: 'constant-arrival-rate',
      rate: 100,              // 초당 100 iteration
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,    // 사전 할당 VU
      maxVUs: 200,            // 최대 VU (응답 느려지면 자동 증가)
    },
    // 시나리오 2: 관리자 API (Closed Model도 병행 가능)
    admin_checks: {
      executor: 'per-vu-iterations',
      vus: 5,
      iterations: 100,
      exec: 'adminFlow',      // 별도 함수 지정 가능
    },
  },
};

export default function () {
  // normal_load 시나리오 실행
  http.get('https://api.example.com/products');
}

export function adminFlow() {
  // admin_checks 시나리오 실행
  http.get('https://api.example.com/admin/stats');
}
// ramping-arrival-rate: 점진적 RPS 증가
export const options = {
  scenarios: {
    stress: {
      executor: 'ramping-arrival-rate',
      startRate: 10,           // 시작: 초당 10 iteration
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '2m', target: 50 },    // 2분간 50 RPS로 증가
        { duration: '5m', target: 200 },   // 5분간 200 RPS로 증가
        { duration: '2m', target: 500 },   // 2분간 500 RPS로 증가 (Stress)
        { duration: '1m', target: 0 },     // 1분간 0으로 감소
      ],
    },
  },
};

6.4 Extension 시스템 (xk6)

xk6는 k6에 커스텀 기능을 Go 모듈로 추가하는 빌드 시스템이다:

# xk6로 커스텀 k6 빌드
xk6 build latest \
  --with github.com/grafana/xk6-sql \
  --with github.com/grafana/xk6-dashboard \
  --with github.com/grafana/xk6-disruptor

# 결과: SQL 쿼리, 실시간 대시보드, Chaos Engineering이 가능한 k6 바이너리

주요 확장:

확장 기능
xk6-sql PostgreSQL, MySQL 등 직접 쿼리
xk6-dashboard 실시간 웹 대시보드
xk6-disruptor Kubernetes Pod 장애 주입 (Chaos)
xk6-kafka Apache Kafka 프로듀서/컨슈머
xk6-amqp RabbitMQ 메시지 발행/소비
xk6-output-prometheus-remote Prometheus Remote Write 출력

6.5 k6 Cloud vs OSS 비교

기능 k6 OSS k6 Cloud (Grafana)
실행 환경 로컬/자체 서버 Grafana Cloud
분산 실행 수동 (k6-operator) 자동 (글로벌 PoP)
최대 VU 하드웨어 한계 수십만 VU
결과 저장 자체 관리 Cloud 자동 저장
트렌드 분석 자체 구축 내장
테스트 비교 수동 자동 diff
팀 협업 Git 기반 웹 UI + Git
비용 무료 (인프라 비용만) 사용량 기반 과금
Performance Insights 없음 AI 기반 분석
적합 대상 개인/소규모 팀 중대형 조직, 글로벌 테스트

6.6 출력 시스템

k6는 다양한 백엔드로 메트릭을 출력할 수 있다:

# JSON 파일 출력
k6 run --out json=results.json script.js

# CSV 파일 출력
k6 run --out csv=results.csv script.js

# InfluxDB 출력
k6 run --out influxdb=http://localhost:8086/k6 script.js

# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js

# 여러 출력 동시 사용
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js

# Grafana Cloud k6
k6 cloud run script.js

6.7 Thresholds & Checks 시스템

Thresholds는 테스트의 자동 Pass/Fail 판정 시스템이다:

export const options = {
  thresholds: {
    // ── 응답 시간 Thresholds ──
    http_req_duration: [
      'p(95)<500',          // 95%ile이 500ms 미만
      'p(99)<1500',         // 99%ile이 1500ms 미만
      'med<250',            // 중앙값이 250ms 미만
      'max<3000',           // 최대값이 3000ms 미만
    ],

    // ── 에러율 Threshold ──
    http_req_failed: [
      'rate<0.01',          // 에러율 1% 미만
    ],

    // ── Check 성공률 Threshold ──
    checks: [
      'rate>0.99',          // Check 성공률 99% 이상
    ],

    // ── 특정 태그에 대한 Threshold ──
    'http_req_duration{name:login}': [
      'p(95)<1000',         // 로그인 API는 별도 기준 적용
    ],

    // ── 커스텀 메트릭 Threshold ──
    'my_custom_trend': [
      'p(95)<100',
    ],

    // ── abort 옵션: 기준 초과 시 즉시 테스트 중단 ──
    http_req_duration: [
      { threshold: 'p(99)<2000', abortOnFail: true, delayAbortEval: '10s' },
    ],
  },
};

Checks는 응답의 정확성 검증이다:

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

export default function () {
  group('사용자 API', function () {
    // GET: 사용자 목록 조회
    const listRes = http.get('https://api.example.com/users');
    check(listRes, {
      '상태코드 200': (r) => r.status === 200,
      '응답이 배열': (r) => Array.isArray(r.json()),
      '최소 1명 존재': (r) => r.json().length > 0,
    });

    // POST: 사용자 생성
    const createRes = http.post('https://api.example.com/users', JSON.stringify({
      name: 'test-user',
      email: `test-${Date.now()}@example.com`,
    }), { headers: { 'Content-Type': 'application/json' } });

    check(createRes, {
      '생성 성공 (201)': (r) => r.status === 201,
      'ID 반환': (r) => r.json('id') !== undefined,
      '이름 일치': (r) => r.json('name') === 'test-user',
    });
  });
}

6.8 Custom Metrics

k6는 4가지 커스텀 메트릭 타입을 제공한다:

import http from 'k6/http';
import { Counter, Gauge, Rate, Trend } from 'k6/metrics';

// Counter: 누적 합계 (단조 증가)
const errorCount = new Counter('custom_errors');

// Gauge: 최신 값 (현재 상태)
const activeConnections = new Gauge('active_connections');

// Rate: 비율 (0과 1의 비율)
const cacheHitRate = new Rate('cache_hit_rate');

// Trend: 분포 (min, max, avg, med, p90, p95, p99)
const apiLatency = new Trend('api_latency', true);  // true = ms 단위 표시

export default function () {
  const res = http.get('https://api.example.com/data');

  // 커스텀 메트릭 기록
  if (res.status !== 200) {
    errorCount.add(1);
  }

  activeConnections.add(res.headers['X-Active-Connections'] || 0);
  cacheHitRate.add(res.headers['X-Cache'] === 'HIT');
  apiLatency.add(res.timings.duration);
}

6.9 k6 browser

k6 v0.46+에서 도입된 k6 browser는 Chromium 기반의 브라우저 테스트를 k6 스크립트 내에서 실행한다:

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

export const options = {
  scenarios: {
    ui: {
      executor: 'constant-vus',
      vus: 5,
      duration: '1m',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
  thresholds: {
    browser_web_vital_lcp: ['p(95)<2500'],  // LCP 2.5초 미만
    browser_web_vital_fid: ['p(95)<100'],   // FID 100ms 미만
    browser_web_vital_cls: ['p(95)<0.1'],   // CLS 0.1 미만
  },
};

export default async function () {
  const page = await browser.newPage();

  try {
    await page.goto('https://example.com/login');

    // 실제 사용자처럼 폼 입력
    await page.locator('#username').fill('testuser');
    await page.locator('#password').fill('testpass');
    await page.locator('#login-btn').click();

    // 페이지 로드 완료 대기
    await page.waitForNavigation();

    check(page, {
      '대시보드 로드': (p) => p.url().includes('/dashboard'),
    });
  } finally {
    await page.close();
  }
}

7. hey 아키텍처 상세

7.1 내부 구조

┌─────────────────────────────────────────────────────────┐
│                    hey 내부 아키텍처                      │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │                  CLI Parser                        │  │
│  │  hey -n 10000 -c 200 -m POST -H "..." URL        │  │
│  └──────────────────────┬────────────────────────────┘  │
│                         │                               │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Request Generator                     │  │
│  │  - HTTP Method, Headers, Body 구성                 │  │
│  │  - http.Request 객체 생성                          │  │
│  └──────────────────────┬────────────────────────────┘  │
│                         │                               │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Worker Goroutine Pool                 │  │
│  │                                                   │  │
│  │  ┌─────────┐ ┌─────────┐       ┌─────────┐      │  │
│  │  │Worker #1│ │Worker #2│  ...  │Worker #C│      │  │
│  │  │         │ │         │       │         │      │  │
│  │  │Transport│ │Transport│       │Transport│      │  │
│  │  │(net/http)│ │(net/http)│     │(net/http)│      │  │
│  │  └────┬────┘ └────┬────┘       └────┬────┘      │  │
│  │       │           │                  │           │  │
│  │  goroutine   goroutine          goroutine        │  │
│  │                                                   │  │
│  │  -c 옵션으로 워커 수 결정 (기본: 50)               │  │
│  │  각 워커는 독립적인 HTTP Transport 사용            │  │
│  └──────────────────────┬────────────────────────────┘  │
│                         │                               │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Result Collector                      │  │
│  │  - 각 요청의 latency, status code, 크기 수집       │  │
│  │  - channel 기반 수집 (race condition 없음)          │  │
│  └──────────────────────┬────────────────────────────┘  │
│                         │                               │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Report Generator                      │  │
│  │  - Percentile 계산 (p10, p25, p50, p75, p90,      │  │
│  │    p95, p99)                                       │  │
│  │  - Latency 히스토그램                               │  │
│  │  - 상태 코드 분포                                   │  │
│  │  - DNS, 연결, TLS, 서버 처리 시간 분리              │  │
│  └───────────────────────────────────────────────────┘  │
│                                                         │
└─────────────────────────────────────────────────────────┘

7.2 커넥션 관리

hey는 Go의 net/http 패키지를 기반으로 커넥션을 관리한다:

  • Keep-Alive: 기본 활성화. -disable-keepalive로 비활성화 가능
  • HTTP/2: https:// URL에 대해 자동 활성화. -http2로 강제 비활성화 가능
  • Connection Pooling: 각 워커가 독립적인 http.Transport를 사용
  • TLS 재사용: Keep-Alive 연결에서 TLS 세션 재사용

7.3 출력 형식

$ hey -n 1000 -c 50 https://api.example.com/health

Summary:
  Total:        2.3456 secs
  Slowest:      0.5234 secs
  Fastest:      0.0023 secs
  Average:      0.1123 secs
  Requests/sec: 426.3456

  Total data:   256000 bytes
  Size/request: 256 bytes

Response time histogram:
  0.002 [1]     |
  0.054 [412]   |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.106 [298]   |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.158 [156]   |■■■■■■■■■■■■■■■
  0.211 [72]    |■■■■■■■
  0.263 [34]    |■■■
  0.315 [15]    |■
  0.367 [7]     |■
  0.419 [3]     |
  0.471 [1]     |
  0.523 [1]     |

Latency distribution:
  10% in 0.0312 secs
  25% in 0.0523 secs
  50% in 0.0891 secs
  75% in 0.1456 secs
  90% in 0.2134 secs
  95% in 0.2678 secs
  99% in 0.4123 secs

Details (average, fastest, slowest):
  DNS+dialup:   0.0012 secs, 0.0023 secs, 0.5234 secs
  DNS-lookup:   0.0003 secs, 0.0000 secs, 0.0089 secs
  req write:    0.0001 secs, 0.0000 secs, 0.0012 secs
  resp wait:    0.1098 secs, 0.0019 secs, 0.5189 secs
  resp read:    0.0011 secs, 0.0001 secs, 0.0123 secs

Status code distribution:
  [200] 987 responses
  [503] 13 responses

Error distribution:
  [13] Get "https://api.example.com/health": context deadline exceeded

7.4 CLI 옵션 전체 표

옵션 기본값 설명
-n 200 총 요청 수
-c 50 동시 워커 수 (goroutine)
-q 0 (무제한) 워커당 초당 요청 수 (Rate limit)
-z - 기간 기반 실행 (예: -z 30s, -z 5m)
-m GET HTTP 메서드 (GET, POST, PUT, DELETE, …)
-H - HTTP 헤더 (반복 가능). 예: -H "Authorization: Bearer xxx"
-d - 요청 본문 (인라인)
-D - 요청 본문 파일 경로
-T text/html Content-Type 헤더
-a - Basic Auth (username:password)
-x - HTTP 프록시 주소
-t 20 요청 타임아웃 (초)
-o - 출력 형식 (csv 지원)
-h2 false HTTP/2 강제 비활성화
-host - Host 헤더 오버라이드
-disable-compression false Accept-Encoding 비활성화
-disable-keepalive false HTTP Keep-Alive 비활성화
-disable-redirects false HTTP 리다이렉트 비활성화
-cpus 모든 코어 사용할 CPU 코어 수

7.5 실전 사용 예시

# 기본 GET 벤치마크
hey -n 10000 -c 100 https://api.example.com/health

# POST with JSON body
hey -n 5000 -c 50 \
  -m POST \
  -T "application/json" \
  -d '{"name":"test","value":42}' \
  https://api.example.com/items

# 인증 헤더 포함
hey -n 1000 -c 20 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \
  https://api.example.com/protected

# 기간 기반 실행 (30초 동안)
hey -z 30s -c 100 https://api.example.com/health

# Rate limiting (워커당 초당 10개 = 총 50 워커 × 10 = 500 RPS)
hey -z 60s -c 50 -q 10 https://api.example.com/api

# Keep-Alive 비활성화 (매 요청마다 새 연결)
hey -n 1000 -c 50 -disable-keepalive https://api.example.com/health

# CSV 출력 (후처리용)
hey -n 5000 -c 100 -o csv https://api.example.com/health > results.csv

# 파일에서 요청 본문 읽기
hey -n 1000 -c 20 -m POST -T "application/json" -D payload.json \
  https://api.example.com/bulk

# 프록시 경유
hey -n 1000 -c 50 -x http://proxy:8080 https://api.example.com/health

8. 대안 비교

8.1 15개 도구 상세 비교표

도구 언어 프로토콜 스크립팅 분산 CO 대응 리소스 효율 CI/CD 학습곡선 실시간 모니터링 라이선스
k6 Go+JS HTTP, WS, gRPC, Redis, Browser JavaScript ES6 k6 Cloud, k6-operator ★★★ Open model executor ★★★ goroutine ★★★ Threshold exit code Grafana, Cloud AGPL-3.0
hey Go HTTP/1.1, HTTP/2 없음 (CLI only) 없음 ★☆☆ Closed only ★★★ goroutine ★☆☆ 수동 매우 낮음 없음 Apache-2.0
wrk C HTTP/1.1 Lua (제한적) 없음 ★☆☆ Closed only ★★★ epoll/kqueue ★☆☆ 낮음 없음 Apache-2.0
wrk2 C HTTP/1.1 Lua (제한적) 없음 ★★★ CO 보정 ★★★ epoll/kqueue ★☆☆ 낮음 없음 Apache-2.0
JMeter Java HTTP, FTP, JDBC, JMS, LDAP, SOAP XML/GUI + BeanShell/Groovy JMeter 분산 ★☆☆ Thread 기반 ★☆☆ Thread-per-VU ★★☆ CLI 가능 높음 JMeter GUI Apache-2.0
Gatling Scala/Java HTTP, WS, JMS, MQTT Scala/Java DSL Gatling Enterprise ★★☆ ★★☆ Akka ★★☆ 중-높음 HTML 리포트 Apache-2.0 / Enterprise
Locust Python HTTP (커스텀 가능) Python 내장 분산 ★☆☆ Closed only ★★☆ gevent ★★☆ 낮음 Web UI MIT
Artillery Node.js HTTP, WS, Socket.io YAML + JS Artillery Cloud ★★☆ ★★☆ ★★★ 낮음 Cloud MPL-2.0
vegeta Go HTTP CLI pipe 없음 ★★★ Open model ★★★ goroutine ★★☆ 낮음 없음 MIT
oha Rust HTTP/1.1, HTTP/2 없음 없음 ★☆☆ ★★★ tokio ★☆☆ 매우 낮음 TUI 실시간 차트 MIT
bombardier Go HTTP/1.1, HTTP/2 없음 없음 ★★☆ Rate limit ★★★ goroutine ★☆☆ 매우 낮음 실시간 통계 MIT
Tsung Erlang HTTP, WS, XMPP, LDAP, MySQL, PostgreSQL XML 내장 분산 ★★☆ ★★★ Erlang 프로세스 ★☆☆ 높음 웹 리포트 GPL-2.0
nGrinder Java/Jython HTTP Jython/Groovy 내장 분산 (Agent) ★☆☆ ★☆☆ Thread-per-VU ★★☆ 웹 UI 웹 UI Apache-2.0
ab C HTTP/1.0 없음 없음 ★☆☆ ★★☆ ★☆☆ 매우 낮음 없음 Apache-2.0
siege C HTTP/1.1 URL 파일 없음 ★☆☆ ★★☆ ★☆☆ 낮음 없음 GPL-3.0

8.2 성능 벤치마크 비교

동일 조건에서의 대략적 RPS 생성 능력 비교 (단일 머신, 4코어, 16GB RAM, localhost 대상):

도구 최대 RPS (approx.) 메모리 사용량 비고
wrk ~300,000+ ~50MB C + epoll. 순수 RPS 생성 최강
wrk2 ~200,000+ ~50MB CO 보정 오버헤드
oha ~250,000+ ~30MB Rust + tokio
bombardier ~200,000+ ~50MB Go
hey ~100,000+ ~80MB Go. 통계 수집 오버헤드
k6 ~40,000-60,000 ~200-500MB JS 런타임 오버헤드. 시나리오 복잡도에 따라 변동
vegeta ~80,000+ ~100MB Go. Open model
Gatling ~30,000-50,000 ~500MB-1GB JVM 워밍업 후
Locust ~5,000-10,000 ~200MB Python GIL. 분산 시 향상
JMeter ~5,000-15,000 ~1-4GB Thread-per-VU. GC 영향

8.3 k6 vs wrk vs hey 성능 차이 원인

┌──────────────────────────────────────────────────────────┐
│            RPS 차이의 근본 원인                            │
│                                                          │
│  wrk (300K RPS):                                         │
│  ┌──────────┐                                            │
│  │    C     │  → epoll/kqueue 직접 사용                  │
│  │ (native) │  → 메모리 할당 최소화                       │
│  │          │  → 스크립팅 없으면 순수 소켓 I/O            │
│  └──────────┘                                            │
│                                                          │
│  hey (100K RPS):                                         │
│  ┌──────────┐                                            │
│  │   Go    │  → goroutine 스케줄러 오버헤드               │
│  │(net/http)│  → GC (짧은 일시정지)                       │
│  │          │  → 통계 수집/정렬                           │
│  └──────────┘                                            │
│                                                          │
│  k6 (50K RPS):                                           │
│  ┌──────────┐                                            │
│  │   Go    │  → goroutine 오버헤드                       │
│  │    +    │  → Sobek JS 런타임 인터프리터 오버헤드        │
│  │ Sobek  │  → 메트릭 수집/집계 오버헤드                   │
│  │  (JS)  │  → VU당 JS 컨텍스트 메모리                    │
│  └──────────┘                                            │
│                                                          │
│  ★ k6의 RPS가 낮은 것은 설계상 의도적이다.               │
│    k6는 "최대 RPS 생성"이 아닌 "현실적 시나리오 시뮬레이션"│
│    이 목표이다. 복잡한 사용자 플로우를 JS로 표현하는      │
│    유연성의 대가로 순수 RPS를 교환한 것이다.              │
│                                                          │
└──────────────────────────────────────────────────────────┘

9. 상황별 최적 선택

9.1 10가지 시나리오별 추천

시나리오 1: 빠른 단일 엔드포인트 벤치마크

“배포 직후 health check 엔드포인트가 얼마나 빠른지 확인하고 싶다”

추천: hey 또는 oha

# hey: 즉시 실행, 결과 확인
hey -n 5000 -c 100 https://api.example.com/health

# oha: 실시간 TUI 차트
oha -n 5000 -c 100 https://api.example.com/health

시나리오 2: 복잡한 사용자 시나리오

“로그인 → 상품 검색 → 장바구니 추가 → 결제 플로우를 테스트하고 싶다”

추천: k6

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

export default function () {
  group('01_로그인', function () {
    const loginRes = http.post('https://api.example.com/auth/login', JSON.stringify({
      email: 'user@test.com',
      password: 'pass123',
    }), { headers: { 'Content-Type': 'application/json' } });
    check(loginRes, { '로그인 성공': (r) => r.status === 200 });
    var token = loginRes.json('token');
    sleep(2);  // 사용자가 대시보드를 봄

    group('02_상품검색', function () {
      const searchRes = http.get('https://api.example.com/products?q=laptop', {
        headers: { Authorization: `Bearer ${token}` },
      });
      check(searchRes, { '검색 결과 존재': (r) => r.json('items').length > 0 });
      sleep(3);  // 사용자가 검색 결과를 봄

      group('03_장바구니', function () {
        const cartRes = http.post('https://api.example.com/cart', JSON.stringify({
          productId: searchRes.json('items')[0].id,
          quantity: 1,
        }), {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
        });
        check(cartRes, { '장바구니 추가 성공': (r) => r.status === 201 });
        sleep(1);

        group('04_결제', function () {
          const payRes = http.post('https://api.example.com/checkout', JSON.stringify({
            cartId: cartRes.json('cartId'),
            paymentMethod: 'card',
          }), {
            headers: {
              Authorization: `Bearer ${token}`,
              'Content-Type': 'application/json',
            },
          });
          check(payRes, { '결제 성공': (r) => r.status === 200 });
        });
      });
    });
  });
}

시나리오 3: CI/CD 파이프라인 성능 게이트

“PR 머지 전에 자동으로 성능 테스트를 실행하고, SLO 위반 시 머지를 차단하고 싶다”

추천: k6 (Threshold + GitHub Actions)

# .github/workflows/performance-gate.yml
name: Performance Gate
on:
  pull_request:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    services:
      app:
        image: myapp:$
        ports: ['8080:8080']

    steps:
      - uses: actions/checkout@v4

      - name: Install k6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
            --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
            | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update && sudo apt-get install k6

      - name: Run Performance Test
        run: k6 run tests/load/api-gate.js
        # k6는 Threshold 위반 시 exit code 99를 반환
        # → GitHub Actions가 자동으로 실패 처리

시나리오 4: 대규모 분산 부하

“글로벌 서비스에 여러 리전에서 동시에 100만 VU를 생성하고 싶다”

추천: k6 Cloud 또는 k6 + k6-operator (Kubernetes)

# k6-operator CRD (Kubernetes)
apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: global-load-test
spec:
  parallelism: 20        # 20개 Pod에 분산
  script:
    configMap:
      name: k6-test-script
  runner:
    resources:
      limits:
        cpu: "2"
        memory: "4Gi"

시나리오 5: gRPC 서비스 테스트

“gRPC 서비스의 성능을 측정하고 싶다”

추천: k6 (내장 gRPC 지원)

import grpc from 'k6/net/grpc';
import { check } from 'k6';

const client = new grpc.Client();
client.load(['./proto'], 'user_service.proto');

export default function () {
  client.connect('grpc.example.com:443', { plaintext: false });

  const res = client.invoke('user.UserService/GetUser', { id: '12345' });

  check(res, {
    'gRPC status OK': (r) => r.status === grpc.StatusOK,
    '사용자 이름 존재': (r) => r.message.name !== '',
  });

  client.close();
}

시나리오 6: 비개발자 QA팀

“QA 엔지니어가 코드 작성 없이 부하 테스트를 수행하고 싶다”

추천: JMeter (GUI) 또는 Locust (Python Web UI)

JMeter는 GUI 기반으로 드래그 앤 드롭으로 테스트 계획을 구성할 수 있고, Locust는 Web UI로 실시간 부하를 조절할 수 있다.

시나리오 7: 브라우저 렌더링 성능

“실제 브라우저에서의 Core Web Vitals(LCP, FID, CLS)를 부하 상황에서 측정하고 싶다”

추천: k6 browser

k6 browser는 Chromium을 내장하여 실제 브라우저 렌더링 성능을 k6 스크립트 내에서 측정한다. (Section 6.9 참고)

시나리오 8: 마이크로서비스 전체 부하

“여러 마이크로서비스에 동시에 다른 패턴의 부하를 생성하고 싶다”

추천: k6 (Scenarios)

export const options = {
  scenarios: {
    user_api: {
      executor: 'constant-arrival-rate',
      rate: 200, timeUnit: '1s', duration: '10m',
      preAllocatedVUs: 50, maxVUs: 300,
      exec: 'userApi',
    },
    product_api: {
      executor: 'ramping-arrival-rate',
      startRate: 50, timeUnit: '1s',
      preAllocatedVUs: 30, maxVUs: 200,
      stages: [
        { duration: '3m', target: 100 },
        { duration: '5m', target: 300 },
        { duration: '2m', target: 50 },
      ],
      exec: 'productApi',
    },
    payment_api: {
      executor: 'constant-vus',
      vus: 10, duration: '10m',
      exec: 'paymentApi',
    },
  },
};

export function userApi() {
  http.get('https://user-service.example.com/users');
}
export function productApi() {
  http.get('https://product-service.example.com/products');
}
export function paymentApi() {
  http.post('https://payment-service.example.com/process', JSON.stringify({
    amount: 9900, currency: 'KRW',
  }));
}

시나리오 9: API 회귀 테스트

“매 배포마다 API 성능이 이전 버전 대비 저하되지 않았는지 확인하고 싶다”

추천: k6 (Threshold + 이전 결과 비교)

export const options = {
  thresholds: {
    // 이전 배포의 p95 기준 20% 마진
    http_req_duration: ['p(95)<600'],  // 이전: 500ms → 600ms까지 허용
    http_req_failed: ['rate<0.01'],
  },
};

시나리오 10: 비용 제약 (오픈소스만)

“예산 없이 오픈소스만으로 프로덕션 수준의 부하 테스트 체계를 구축하고 싶다”

추천: k6 OSS + Grafana + InfluxDB/Prometheus

k6 (부하 생성) → InfluxDB (메트릭 저장) → Grafana (시각화)
                     ↑
              Prometheus (서버 메트릭) ─→ Grafana

9.2 도구 선택 결정 트리

                        시작
                         │
                    스크립팅이 필요한가?
                    ┌────┤────┐
                   YES        NO
                    │          │
               시나리오가      단순 HTTP
               복잡한가?      벤치마크인가?
               ┌───┤───┐      ┌───┤───┐
              YES      NO    YES       NO
               │        │     │         │
           언어 선호?   k6    TUI가     특수
           ┌──┤──┐          필요?    프로토콜?
          JS  Py  Scala   ┌──┤──┐      │
          │   │    │     YES   NO    도구별
          k6  │  Gatling  │     │    확인
              │           oha   │
           Locust         │   CO 보정
                          │   필요?
                          │  ┌──┤──┐
                          │ YES   NO
                          │  │     │
                          │ wrk2  hey
                          │       wrk
                          │    bombardier
                          │
                     CI/CD 통합
                     필요한가?
                     ┌──┤──┐
                    YES   NO
                     │     │
                    k6   사용 편의성
                    │    기준 선택
                    │
                분산 실행
                필요한가?
                ┌──┤──┐
               YES   NO
                │     │
           k6 Cloud  k6 OSS
           또는
           k6-operator

10. 베스트 프랙티스

10.1 k6 베스트 프랙티스

10.1.1 테스트 순서: Smoke → Load → Stress → Soak

// ── 1단계: Smoke Test (기본 동작 확인) ──
// smoke.js
export const options = {
  vus: 1,
  duration: '1m',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<2000'],  // 넉넉한 기준
  },
};

// ── 2단계: Load Test (예상 부하 검증) ──
// load.js
export const options = {
  stages: [
    { duration: '5m', target: 100 },   // ramp-up
    { duration: '30m', target: 100 },  // 유지
    { duration: '5m', target: 0 },     // ramp-down
  ],
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
  },
};

// ── 3단계: Stress Test (한계 탐색) ──
// stress.js
export const options = {
  stages: [
    { duration: '2m', target: 100 },
    { duration: '5m', target: 100 },
    { duration: '2m', target: 200 },
    { duration: '5m', target: 200 },
    { duration: '2m', target: 300 },   // 예상 트래픽의 3배
    { duration: '5m', target: 300 },
    { duration: '5m', target: 0 },
  ],
};

// ── 4단계: Soak Test (장시간 안정성) ──
// soak.js
export const options = {
  stages: [
    { duration: '5m', target: 100 },
    { duration: '8h', target: 100 },   // 8시간 유지
    { duration: '5m', target: 0 },
  ],
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
  },
};

10.1.2 Open Model (constant-arrival-rate) 권장

// ★ 권장: Open Model - 현실적인 부하 시뮬레이션
export const options = {
  scenarios: {
    api_test: {
      executor: 'constant-arrival-rate',
      rate: 500,               // 초당 500 iteration
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 100,    // 사전 할당 VU
      maxVUs: 1000,            // 서버가 느려져도 요청률 유지를 위해 VU 자동 증가
    },
  },
};

// ★ 비권장: Closed Model - Coordinated Omission 위험
// (단, 내부 배치 처리 테스트 등 의도적 사용은 OK)
export const options = {
  vus: 100,
  duration: '10m',
  // → 서버가 느려지면 요청률도 함께 감소
  // → 결과가 실제보다 낙관적으로 왜곡될 수 있음
};

10.1.3 Threshold 설정 전략

export const options = {
  thresholds: {
    // ── 전역 Threshold ──
    http_req_duration: [
      'p(95)<500',                        // SLO 기준
      'p(99)<1500',                       // Tail latency 기준
      { threshold: 'p(99)<3000',          // 극단적 지연 감지
        abortOnFail: true,                // 위반 시 테스트 즉시 중단
        delayAbortEval: '30s' },          // 30초 후부터 평가 (워밍업 고려)
    ],
    http_req_failed: ['rate<0.01'],       // 에러율 1% 미만
    checks: ['rate>0.99'],                // Check 성공률 99% 이상

    // ── API별 Threshold (태그 기반) ──
    'http_req_duration{api:login}': ['p(95)<1000'],    // 로그인은 1초 이내
    'http_req_duration{api:search}': ['p(95)<300'],    // 검색은 300ms 이내
    'http_req_duration{api:payment}': ['p(95)<2000'],  // 결제는 2초 이내

    // ── 커스텀 메트릭 Threshold ──
    'business_transaction_duration': ['p(95)<5000'],
  },
};

// 태그 설정
export default function () {
  http.get('https://api.example.com/login', { tags: { api: 'login' } });
  http.get('https://api.example.com/search', { tags: { api: 'search' } });
  http.post('https://api.example.com/payment', null, { tags: { api: 'payment' } });
}

10.1.4 스크립트 모듈화 패턴

tests/
├── load/
│   ├── lib/
│   │   ├── auth.js           # 인증 헬퍼
│   │   ├── checks.js         # 공통 Check 함수
│   │   ├── metrics.js        # 커스텀 메트릭 정의
│   │   └── config.js         # 환경별 설정
│   ├── scenarios/
│   │   ├── user-flow.js      # 사용자 플로우
│   │   ├── admin-flow.js     # 관리자 플로우
│   │   └── api-benchmark.js  # API 벤치마크
│   ├── smoke.js              # Smoke 테스트 진입점
│   ├── load.js               # Load 테스트 진입점
│   ├── stress.js             # Stress 테스트 진입점
│   └── soak.js               # Soak 테스트 진입점
└── data/
    ├── users.json            # 테스트 사용자 데이터
    └── products.csv          # 테스트 상품 데이터
// lib/config.js
const BASE_URL = __ENV.BASE_URL || 'https://staging.example.com';
const API_KEY = __ENV.API_KEY || 'test-key';

export { BASE_URL, API_KEY };
// lib/auth.js
import http from 'k6/http';
import { BASE_URL } from './config.js';

export function login(username, password) {
  const res = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
    username, password,
  }), { headers: { 'Content-Type': 'application/json' } });
  return res.json('token');
}

export function authHeaders(token) {
  return {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  };
}
// scenarios/user-flow.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { login, authHeaders } from '../lib/auth.js';
import { BASE_URL } from '../lib/config.js';

export function userFlow() {
  const token = login('testuser', 'testpass');
  const headers = authHeaders(token);

  const productsRes = http.get(`${BASE_URL}/products`, { headers });
  check(productsRes, {
    '상품 목록 조회 성공': (r) => r.status === 200,
  });
  sleep(2);
}

10.1.5 환경별 설정

# Staging 환경
k6 run -e BASE_URL=https://staging.example.com -e API_KEY=staging-key load.js

# Production (읽기 전용)
k6 run -e BASE_URL=https://api.example.com -e API_KEY=prod-readonly-key load.js

10.1.6 Group과 Tag

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

const loginTrend = new Trend('login_duration', true);
const searchTrend = new Trend('search_duration', true);

export default function () {
  // Group: 논리적 트랜잭션 그룹핑 (리포트에서 구분)
  group('인증 플로우', function () {
    const res = http.post('https://api.example.com/login',
      JSON.stringify({ user: 'test', pass: 'test' }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { name: 'login', type: 'auth' },  // Tag: 메트릭 필터링용
      }
    );
    loginTrend.add(res.timings.duration);
    check(res, { '로그인 성공': (r) => r.status === 200 });
  });

  group('검색 플로우', function () {
    const res = http.get('https://api.example.com/search?q=test', {
      tags: { name: 'search', type: 'query' },
    });
    searchTrend.add(res.timings.duration);
    check(res, { '검색 성공': (r) => r.status === 200 });
  });
}

10.1.7 CI/CD 통합 (GitHub Actions, GitLab CI)

# GitHub Actions
name: Load Test
on:
  schedule:
    - cron: '0 2 * * *'  # 매일 새벽 2시
  workflow_dispatch:       # 수동 실행

jobs:
  k6-load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: grafana/k6-action@v1
        with:
          filename: tests/load/load.js
        env:
          BASE_URL: $
          API_KEY: $
# GitLab CI
load_test:
  stage: test
  image:
    name: grafana/k6:latest
    entrypoint: ['']
  script:
    - k6 run --out json=results.json tests/load/load.js
  artifacts:
    paths:
      - results.json
    expire_in: 30 days
  variables:
    BASE_URL: ${STAGING_URL}
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

10.1.8 모니터링 통합 (Grafana + InfluxDB)

# docker-compose.yml로 모니터링 스택 구성
# InfluxDB + Grafana + k6
docker-compose up -d influxdb grafana

# k6 실행 시 InfluxDB 출력
k6 run --out influxdb=http://localhost:8086/k6 script.js
// Prometheus Remote Write 출력
export const options = {
  // k6 v0.42+
  // K6_PROMETHEUS_RW_SERVER_URL 환경변수 또는:
};

// 실행:
// K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write \
//   k6 run --out experimental-prometheus-rw script.js

10.2 hey 베스트 프랙티스

10.2.1 동시성(-c)과 요청수(-n) 설정 가이드

목적 -n -c -z 설명
빠른 확인 200 50 - 기본값. 1-2초 완료
기준선 측정 10,000 100 - 통계적으로 유의미한 샘플
시간 기반 - 200 60s 1분간 최대 부하
Rate limit - 50 60s -q 10 추가 시 500 RPS
연결 테스트 1,000 1,000 - 최대 동시 연결 수 테스트

10.2.2 결과 해석

핵심 규칙:
1. Average는 무시하라. p50과 p99를 봐라.
2. p99/p50 비율이 10x 이상이면 bimodal distribution 의심
3. Status code 분포에서 2xx 외의 비율 확인
4. "resp wait" 시간이 전체의 대부분이면 서버 병목
5. "DNS+dialup"이 높으면 연결 설정 병목 (Keep-Alive 확인)

10.2.3 워밍업 처리

# 워밍업: 소량 요청으로 JIT 컴파일, 커넥션 풀 초기화
hey -n 100 -c 10 https://api.example.com/health

# 실제 벤치마크
hey -n 10000 -c 200 https://api.example.com/health

10.2.4 프록시/로드밸런서 주의점

# 로드밸런서 뒤의 서버 테스트 시:
# 1. Keep-Alive가 동일 서버로 고정될 수 있음 → -disable-keepalive
hey -n 10000 -c 100 -disable-keepalive https://api.example.com/health

# 2. CDN 캐시 회피 → URL 파라미터 추가
hey -n 10000 -c 100 "https://api.example.com/health?nocache=$(date +%s)"

# 3. Host 헤더 오버라이드 (직접 서버 IP 지정)
hey -n 10000 -c 100 -host api.example.com http://10.0.1.100:8080/health

10.3 JMeter → k6 마이그레이션

10.3.1 개념 매핑 표

JMeter k6 설명
Test Plan script.js 테스트 전체 구성
Thread Group options.scenarios VU/실행 설정
Thread VU (goroutine) 실행 단위
Sampler http.get(), http.post() HTTP 요청
Assertion check() 응답 검증
Listener Output (--out) 결과 수집/출력
Timer sleep() Think Time
CSV Data Set Config SharedArray + open() 데이터 파라미터화
HTTP Cookie Manager http.cookieJar() 쿠키 관리
HTTP Header Manager params.headers 헤더 설정
Response Assertion check() 응답 검증
Summary Report 터미널 출력 / Grafana 결과 요약
Backend Listener --out influxdb=... 실시간 메트릭 전송
.jmx (XML) .js (JavaScript) 테스트 파일 형식

10.3.2 jmeter-to-k6 변환기

# JMeter 테스트 계획을 k6 스크립트로 자동 변환
npm install -g @grafana/jmeter-to-k6

# 변환 실행
jmeter-to-k6 test-plan.jmx -o k6-script/

# 결과:
# k6-script/
# ├── test.js      # 변환된 k6 스크립트
# ├── libs/        # 헬퍼 라이브러리
# └── data/        # 데이터 파일

10.3.3 단계적 마이그레이션 전략

Phase 1 (1-2주): Smoke Test를 k6로 전환
  → 가장 단순한 테스트부터 시작
  → 팀이 k6 문법에 익숙해지는 기간

Phase 2 (2-4주): Load Test를 k6로 전환
  → JMeter와 k6를 병행 실행, 결과 비교
  → Threshold/Check 설정

Phase 3 (4-8주): Stress/Soak Test 전환
  → 복잡한 시나리오 마이그레이션
  → CI/CD 파이프라인 연동

Phase 4 (8-12주): JMeter 완전 대체
  → JMeter 제거
  → Grafana 대시보드 구축
  → 팀 교육 완료

11. 안티패턴과 함정

11.1 Coordinated Omission 무시

문제: Closed model(constant-vus)만 사용하여 latency를 측정하면, 서버가 느려질 때 요청률도 함께 감소하여 실제보다 낙관적인 결과를 얻는다.

해결: constant-arrival-rate 또는 ramping-arrival-rate executor를 사용하여 서버 응답과 무관하게 일정 비율의 요청을 발생시킨다.

// ✗ 안티패턴: Closed Model만 사용
export const options = {
  vus: 100,
  duration: '10m',
};

// ✓ 베스트 프랙티스: Open Model 사용
export const options = {
  scenarios: {
    open_model: {
      executor: 'constant-arrival-rate',
      rate: 500,
      timeUnit: '1s',
      duration: '10m',
      preAllocatedVUs: 100,
      maxVUs: 500,
    },
  },
};

11.2 클라이언트 병목

문제: 부하 생성 도구 자체가 CPU/메모리/네트워크 병목이 되어, 서버가 아닌 클라이언트의 한계를 측정하게 된다.

징후:

  • 부하 생성 머신의 CPU 사용률이 80% 이상
  • RPS가 특정 값에서 더 이상 증가하지 않음
  • k6의 dropped_iterations 메트릭이 증가

해결:

# 부하 생성 중 클라이언트 머신 모니터링
htop  # CPU/메모리 확인
ss -s  # 소켓 상태 확인

# k6 결과에서 dropped_iterations 확인
# dropped_iterations > 0이면 VU가 부족하여 요청을 발사하지 못한 것

11.3 평균값 함정

문제: 평균(mean) latency만 보고 성능을 판단하면, tail latency의 심각한 문제를 놓칠 수 있다.

예시: 1000개 요청
- 990개: 10ms
- 10개: 5,000ms (GC 일시정지)

평균: 59.9ms   ← "괜찮아 보임"
p50: 10ms
p99: 5,000ms   ← "심각한 문제"

해결: 항상 p50, p95, p99를 기준으로 판단한다. k6 Threshold는 percentile 기반으로 설정한다.

11.4 워밍업 미포함

문제: JVM 기반 서버(Spring Boot, Tomcat 등)는 JIT 컴파일과 클래스 로딩으로 인해 초기 요청이 느리다. 워밍업 없이 테스트하면 초기 latency가 결과를 왜곡한다.

해결:

export const options = {
  stages: [
    { duration: '2m', target: 10 },    // ★ 워밍업: 소량 VU로 서버 예열
    { duration: '1m', target: 100 },   // Ramp-up
    { duration: '10m', target: 100 },  // 실제 측정 구간
    { duration: '2m', target: 0 },     // Ramp-down
  ],
  thresholds: {
    // delayAbortEval로 워밍업 구간 제외
    http_req_duration: [
      { threshold: 'p(95)<500', delayAbortEval: '3m' },
    ],
  },
};

11.5 네트워크 영향 무시

문제: 부하 생성기와 서버가 동일 네트워크(또는 localhost)에 있으면, 실제 사용자의 네트워크 latency를 반영하지 못한다.

해결:

  • 가능하면 실제 사용자와 유사한 네트워크 경로에서 테스트
  • k6 Cloud의 글로벌 PoP 활용
  • tc(Linux Traffic Control)로 네트워크 지연/손실 시뮬레이션
# 네트워크 지연 시뮬레이션 (서버 측)
sudo tc qdisc add dev eth0 root netem delay 50ms 10ms distribution normal
# → 평균 50ms 지연, 표준편차 10ms, 정규분포

11.6 단일 엔드포인트만 테스트

문제: /health만 벤치마크하고 “서버가 10,000 RPS를 처리한다”고 결론짓는 것은 위험하다. 실제 부하는 다양한 엔드포인트에 분산되며, 비용이 큰 API(DB 조회, 외부 API 호출)가 병목이 된다.

해결: 실제 트래픽 분포를 반영한 시나리오를 구성한다.

export default function () {
  // 실제 트래픽 비율 반영
  const rand = Math.random();
  if (rand < 0.5) {
    http.get(`${BASE_URL}/products`);         // 50%: 상품 목록
  } else if (rand < 0.8) {
    http.get(`${BASE_URL}/products/${id}`);    // 30%: 상품 상세
  } else if (rand < 0.95) {
    http.post(`${BASE_URL}/cart`, body);       // 15%: 장바구니
  } else {
    http.post(`${BASE_URL}/checkout`, body);   // 5%: 결제
  }
}

11.7 Think Time 미설정

문제: sleep() 없이 테스트하면 VU가 응답 즉시 다음 요청을 보내며, 실제 사용자 행동과 크게 다른 부하 패턴이 생성된다.

해결:

import { sleep } from 'k6';

export default function () {
  http.get('https://api.example.com/page');
  sleep(Math.random() * 3 + 1);  // 1-4초 사이 랜덤 Think Time
  // 또는 실제 사용자 데이터 기반:
  // sleep(normalDistribution(3, 1));  // 평균 3초, 표준편차 1초
}

11.8 스케일링 미고려

문제: 단일 인스턴스에서만 테스트하고, 오토스케일링이나 로드밸런서 동작을 검증하지 않는다.

해결: Spike Test로 오토스케일링 반응 시간을 측정하고, Scale-out 후의 성능 변화를 확인한다.

// Spike Test: 오토스케일링 검증
export const options = {
  stages: [
    { duration: '1m', target: 50 },    // 기본 부하
    { duration: '10s', target: 500 },  // ★ 급격한 스파이크 (10배)
    { duration: '3m', target: 500 },   // 오토스케일링 대기
    { duration: '10s', target: 50 },   // 원래 수준으로 복귀
    { duration: '3m', target: 50 },    // Scale-down 관찰
  ],
};

11.9 결과 비교 오류

문제: 다른 조건(다른 네트워크, 다른 시간대, 다른 데이터 규모)에서 수행한 테스트 결과를 직접 비교하는 것은 무의미하다.

해결:

  • 동일 환경, 동일 데이터, 동일 시간대에서 비교
  • A/B 테스트처럼 동시에 두 버전을 테스트
  • k6의 태그 시스템으로 구분

11.10 캐시/CDN 영향 무시

문제: CDN이나 서버 캐시가 결과를 왜곡한다. 첫 요청은 캐시 미스(MISS)로 느리고, 이후 요청은 캐시 히트(HIT)로 빠르다. 동일 URL만 반복하면 비현실적으로 좋은 결과를 얻는다.

해결:

// 다양한 리소스를 요청하여 캐시 효과 최소화
import { SharedArray } from 'k6/data';

const productIds = new SharedArray('product-ids', function () {
  return JSON.parse(open('./product-ids.json'));  // 수천 개의 실제 상품 ID
});

export default function () {
  const id = productIds[Math.floor(Math.random() * productIds.length)];
  http.get(`https://api.example.com/products/${id}`);
}

12. 빅테크 실전 사례

12.1 Google: DiRT + Dapper 연계

Google은 DiRT(Disaster Recovery Testing)를 정기적으로 수행하여 전사적 장애 복구 능력을 검증한다:

  • DiRT: 의도적으로 데이터센터를 비활성화하고, 트래픽 전환과 복구 시간을 측정
  • Dapper: 분산 트레이싱 시스템. 부하 테스트 중 서비스 간 지연 전파 경로를 추적
  • Borgmon/Monarch: 내부 모니터링 시스템으로 부하 테스트 중 모든 서비스의 SLI를 실시간 추적
  • 방법론: SRE 팀이 SLO 기반으로 부하 테스트 기준을 설정하고, Error Budget 소진 속도를 측정

12.2 Netflix: Chaos Monkey + FIT

Netflix는 부하 테스트와 Chaos Engineering을 결합한 선구자다:

  • Chaos Monkey: 프로덕션 환경에서 랜덤하게 인스턴스를 종료하여 복원력 검증
  • FIT (Failure Injection Testing): 특정 서비스 간 통신에 인위적 장애(지연, 에러) 주입
  • Chaos Kong: 전체 리전 장애 시뮬레이션
  • 부하 테스트 + Chaos 조합: 부하 상태에서 장애를 주입하여 “고부하 + 장애” 시나리오를 검증
  • EVCache 부하 테스트: 캐시 계층의 성능을 지속적으로 벤치마킹하여 캐시 미스 시 DB 부하 폭증을 사전 감지

12.3 Amazon/AWS: Distributed Load Testing on AWS

AWS는 자체 서비스와 고객을 위한 부하 테스트 솔루션을 제공한다:

  • Distributed Load Testing on AWS: AWS에서 제공하는 오픈소스 솔루션. ECS Fargate에서 부하 생성기 자동 확장
  • GameDay: Amazon 내부의 대규모 장애 시뮬레이션 행사. 부하 테스트 + 장애 주입 + 복구 절차를 통합 검증
  • Prime Day 대비: 연간 최대 트래픽(Prime Day)에 대비하여 수개월 전부터 단계적 부하 테스트 수행. 예상 피크의 2-3배까지 테스트

12.4 Meta: 프로덕션 트래픽 리플레이

Meta(구 Facebook)는 독특한 접근 방식을 사용한다:

  • Shadow Testing / Dark Traffic: 프로덕션 트래픽의 복사본을 새 버전의 서비스로 전송하여 실제 사용자에게 영향 없이 성능을 검증
  • Proxygen: Meta의 커스텀 HTTP 프레임워크로, 트래픽 분할 및 리플레이 기능 내장
  • 프로덕션 트래픽의 장점: 합성(synthetic) 부하와 달리, 실제 트래픽 패턴(요청 분포, 크기, 타이밍)을 정확히 반영
  • Sharding 테스트: 새로운 샤딩 전략 배포 전, 프로덕션 트래픽 리플레이로 핫스팟 여부 사전 검증

12.5 LinkedIn: BOSS + Simoorg

LinkedIn의 성능 엔지니어링 체계:

  • BOSS (Benchmarking on Staging Systems): LinkedIn의 내부 부하 테스트 플랫폼. 모든 서비스의 성능 프로파일을 지속적으로 유지
  • Simoorg: LinkedIn이 오픈소스로 공개한 장애 시뮬레이션 프레임워크. 프로세스 종료, 네트워크 파티션, 디스크 고갈 등 시뮬레이션
  • Performance as a Service: 개발 팀이 셀프 서비스로 부하 테스트를 수행할 수 있는 플랫폼 제공. 중앙 성능 팀은 도구/인프라/컨설팅 역할

12.6 Uber: Ballast + Ceilometer + PerfInsights

Uber의 대규모 마이크로서비스 성능 관리:

  • Ballast: eBPF 기반 부하 테스트 프레임워크. 네트워크 레벨에서 트래픽을 조작하여 극도로 낮은 오버헤드로 부하 생성
  • Ceilometer: Uber의 RPS 기반 용량 계획 시스템. 각 서비스의 최대 처리 용량을 지속적으로 측정하고, 오토스케일링 기준으로 활용
  • PerfInsights: 성능 회귀를 자동 감지하는 시스템. 배포마다 벤치마크를 실행하고, 이전 버전 대비 유의미한 성능 저하 시 자동 알림
  • DORA Metrics 연계: 배포 빈도, 변경 실패율, MTTR을 부하 테스트 결과와 상관분석

12.7 Twitter/X: Iago v2 + Diffy

Twitter의 성능 테스트 도구:

  • Iago: Twitter가 오픈소스로 공개한 Scala 기반 부하 생성 프레임워크. 프로덕션 로그를 리플레이하여 현실적인 부하 패턴 생성
  • Diffy: 두 서비스 버전의 응답을 비교하는 프록시 기반 도구. 부하 테스트와 결합하여 “성능 차이 + 결과 차이”를 동시에 감지
  • Finagle: Twitter의 RPC 프레임워크에 내장된 부하 분산 및 서킷 브레이커. 부하 테스트 결과를 기반으로 timeout/retry 파라미터 튜닝

12.8 Shopify: BFCM 대비

Shopify는 연간 최대 트래픽 이벤트인 BFCM(Black Friday / Cyber Monday)에 대비한 체계적인 부하 테스트를 수행한다:

  • Genghis: Shopify의 내부 부하 테스트 프레임워크. 실제 BFCM 트래픽 패턴을 기반으로 합성 부하 생성
  • 5회 Scale Test: BFCM 전 최소 5차례의 대규모 부하 테스트를 수행. 각 라운드에서 발견된 병목을 수정하고 다음 라운드에서 검증
  • Game Days: 전사적으로 참여하는 장애 시뮬레이션 행사. 부하 테스트 + 인프라 장애 + 의사결정 훈련을 통합
  • 성과: 2023 BFCM에서 초당 $4.2M 매출 처리에 성공. 피크 시 분당 수백만 요청 처리

12.9 Stripe: Mock 기반 + Rate Limiter

Stripe의 성능 엔지니어링 접근법:

  • Mock 기반 테스트: 외부 결제 네트워크(Visa, Mastercard)를 Mock으로 대체하여 독립적인 부하 테스트 수행. 외부 의존성 없이 Stripe 시스템 자체의 한계를 정확히 측정
  • 4가지 Rate Limiter: 요청률, API 키, IP, 사용자별 Rate Limiting을 계층적으로 적용. 부하 테스트에서 각 계층의 한계를 개별적으로 검증
  • Ruby → Go 마이그레이션 검증: 결제 처리 서비스를 Ruby에서 Go로 마이그레이션하면서, 동일 부하 프로파일에서의 성능 비교를 통해 마이그레이션 성공 여부를 판정
  • Idempotency Key 테스트: 재시도 폭풍(retry storm) 시나리오를 부하 테스트로 시뮬레이션하여 멱등성 보장 검증

12.10 k6 채택 현황과 트렌드

k6 성장

  • GitHub Stars: 30,000+ (2025년 기준)
  • k6 v1.0 (2025.02): 안정 API 보장, Sobek JS 런타임
  • 주요 채택 기업: Just Eat Takeaway, DoorDash, N26, GitLab, IKEA, Mulligan’s 등
  • Just Eat Takeaway 사례: JMeter에서 k6로 마이그레이션. CI/CD 파이프라인에 통합하여 매 PR마다 성능 게이트 적용. 테스트 실행 시간 60% 감소, 리소스 사용량 70% 감소

2024-2026 트렌드

트렌드 설명 관련 도구/기술
Shift-Left Performance Testing 개발 초기 단계부터 성능 테스트를 통합. PR마다 자동 실행 k6 + CI/CD
AI/ML 부하 패턴 생성 프로덕션 트래픽 패턴을 ML로 학습하여 현실적인 합성 부하 자동 생성 Grafana AI, 자체 ML
Chaos + Load 융합 부하 테스트와 Chaos Engineering을 동일 프레임워크에서 수행 k6 + xk6-disruptor
서버리스 부하 생성 Lambda/Cloud Functions로 부하 생성기를 서버리스로 실행. 유휴 비용 없음 AWS Lambda + vegeta
분산 트레이싱 통합 부하 테스트 요청에 trace ID를 삽입하여 서비스 간 전파 경로 추적 k6 + Tempo/Jaeger
RUM vs Synthetic 통합 Real User Monitoring과 Synthetic Testing 결과를 단일 대시보드에서 상관분석 Grafana Faro + k6 browser
eBPF 기반 모니터링 커널 레벨에서 부하 테스트 중 시스템 동작을 관찰. 오버헤드 극소화 bpftrace, Pixie

13. 참고 자료

13.1 공식 문서

주제 URL
k6 공식 문서 https://grafana.com/docs/k6/latest/
k6 GitHub 리포지토리 https://github.com/grafana/k6
k6 v1.0 릴리스 노트 https://grafana.com/blog/2025/02/25/grafana-k6-v1.0-is-here/
k6 Executors 문서 https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/
k6 Thresholds 문서 https://grafana.com/docs/k6/latest/using-k6/thresholds/
k6 browser 문서 https://grafana.com/docs/k6/latest/using-k6-browser/
k6 Extensions (xk6) https://grafana.com/docs/k6/latest/extensions/
k6 Cloud 문서 https://grafana.com/docs/grafana-cloud/testing/k6/
hey GitHub 리포지토리 https://github.com/rakyll/hey
Grafana Labs 블로그 https://grafana.com/blog/

13.2 학술 논문 및 발표

논문/발표 저자 연도 URL
How NOT to Measure Latency Gil Tene 2013 https://www.youtube.com/watch?v=lJ8ydIuPFeU
HDR Histogram Gil Tene 2013 https://github.com/HdrHistogram/HdrHistogram
Open Versus Closed: A Cautionary Tale Schroeder, Wierman, Harchol-Balter 2006 https://www.usenix.org/conference/nsdi-06/open-versus-closed-cautionary-tale
Little’s Law (original) John Little 1961 https://en.wikipedia.org/wiki/Little%27s_law
A Proof for the Queuing Formula: L = λW John Little 1961 https://doi.org/10.1287/opre.9.3.383
USE Method Brendan Gregg 2012 https://www.brendangregg.com/usemethod.html
Flame Graphs Brendan Gregg 2011 https://www.brendangregg.com/flamegraphs.html
Dapper (Google) Sigelman et al. 2010 https://research.google/pubs/pub36356/

13.3 부하 테스트 도구

도구 URL
wrk https://github.com/wg/wrk
wrk2 https://github.com/giltene/wrk2
vegeta https://github.com/tsenart/vegeta
oha https://github.com/hatoo/oha
bombardier https://github.com/codesenberg/bombardier
Apache JMeter https://jmeter.apache.org/
Gatling https://gatling.io/
Locust https://locust.io/
Artillery https://www.artillery.io/
Siege https://github.com/JoeDog/siege
Apache Bench (ab) https://httpd.apache.org/docs/2.4/programs/ab.html
Tsung http://tsung.erlang-projects.org/
nGrinder https://naver.github.io/ngrinder/

13.4 모니터링/관측성

주제 URL
Grafana https://grafana.com/
Grafana + k6 대시보드 https://grafana.com/grafana/dashboards/2587-k6-load-testing-results/
InfluxDB https://www.influxdata.com/
Prometheus https://prometheus.io/
Grafana Tempo (분산 트레이싱) https://grafana.com/oss/tempo/
Grafana Faro (RUM) https://grafana.com/oss/faro/
Pyroscope (Continuous Profiling) https://grafana.com/oss/pyroscope/

13.5 Chaos Engineering

주제 URL
xk6-disruptor https://grafana.com/docs/k6/latest/testing-guides/injecting-faults-with-xk6-disruptor/
Chaos Monkey (Netflix) https://netflix.github.io/chaosmonkey/
Litmus (CNCF) https://litmuschaos.io/
Chaos Mesh https://chaos-mesh.org/

13.6 빅테크 사례 및 블로그

주제 URL
Google DiRT https://cloud.google.com/blog/products/management-tools/shrinking-the-time-to-mitigate-production-incidents
Netflix Chaos Engineering https://netflixtechblog.com/tagged/chaos-engineering
Netflix FIT https://netflixtechblog.com/fit-failure-injection-testing-35d8e2a9bb2
AWS Distributed Load Testing https://aws.amazon.com/solutions/implementations/distributed-load-testing-on-aws/
Shopify BFCM 대비 https://shopify.engineering/shopify-bfcm-performance
Uber Ballast https://www.uber.com/blog/engineering/
LinkedIn BOSS https://engineering.linkedin.com/blog
Stripe 성능 엔지니어링 https://stripe.com/blog/engineering
Just Eat Takeaway k6 채택 https://grafana.com/blog/2024/01/30/how-just-eat-takeaway-uses-k6-and-grafana-cloud-for-performance-testing/

13.7 서적

서적 저자 설명
Performance Testing with k6 Grafana Labs k6 공식 가이드북
The Art of Capacity Planning John Allspaw 웹 서비스 용량 계획의 고전
Systems Performance Brendan Gregg 시스템 성능 분석 바이블. USE Method 포함
Web Performance in Action Jeremy Wagner 웹 성능 최적화
Site Reliability Engineering Beyer, Jones, Petoff, Murphy (Google) SRE/SLO 기반 성능 관리의 기준서
Chaos Engineering Casey Rosenthal, Nora Jones Chaos Engineering과 부하 테스트의 결합

13.8 커뮤니티

주제 URL
k6 Community Forum https://community.grafana.com/c/grafana-k6/
k6 Discord https://discord.gg/k6
k6 Office Hours (YouTube) https://www.youtube.com/c/k6test
Load Testing 서브레딧 https://www.reddit.com/r/loadtesting/