🎯 k6란 무엇인가?: 현대적 부하 테스트의 새로운 기준
2016년 Load Impact라는 스웨덴 스타트업에서 시작된 k6는 2021년 Grafana Labs에 인수되면서 오픈소스 부하 테스트 도구의 대표주자로 급부상했습니다. 단순한 도구를 넘어 "개발자 중심의 성능 테스트"라는 새로운 패러다임을 제시했죠.
핵심 철학: Developer-First Approach
k6가 기존 도구들과 가장 크게 차별화되는 점은 "개발자를 위한 도구"라는 철학입니다. JMeter의 GUI 기반 복잡한 설정 대신, 개발자들이 익숙한 JavaScript로 테스트를 작성합니다. 이는 다음과 같은 혜택을 가져옵니다:
Go로 구현된 고성능 엔진
Java 기반 JMeter의 무거운 리소스 소비와 달리, Go로 작성된 k6는 단일 머신에서 수천 개의 가상 사용자(VU)를 생성하면서도 가벼운 메모리 사용량을 자랑합니다.
JavaScript/TypeScript 지원
별도의 학습 없이 바로 시작할 수 있는 친숙한 문법. 모듈 시스템을 활용한 코드 재사용과 버전 관리가 용이합니다.
CLI 중심 워크플로우
터미널에서 바로 실행 가능한 경량 도구. CI/CD 파이프라인과의 통합이 자연스럽습니다.
Grafana 네이티브 통합
테스트 결과를 Prometheus, InfluxDB, Grafana로 실시간 시각화. 모니터링과 테스트의 완벽한 연결고리를 제공합니다.
지원 프로토콜과 테스트 유형
k6는 HTTP/HTTPS, WebSocket을 기본 지원하며, 확장을 통해 gRPC, MQTT 등 다양한 프로토콜을 커버합니다. 특히 2023년에 공개된 xk6-browser는 브라우저 자동화 기능을 추가하여 프로토콜 레벨 테스트와 브라우저 레벨 테스트를 하나의 도구에서 수행할 수 있게 했습니다.
⚔️ 왜 k6인가?: JMeter와의 심층 비교 분석
성능 테스트 도구를 선택할 때 가장 많이 비교되는 것이 바로 Apache JMeter와 k6입니다. 20년 이상의 역사를 가진 JMeter는 방대한 프로토콜 지원과 성숙한 생태계를 자랑하지만, k6는 현대적인 개발 워크플로우에 더 적합한 대안으로 떠오르고 있습니다.
상세 기능 비교표
| 기능 | Apache JMeter | k6 |
|---|---|---|
| 스크립팅 언어 | Java/Groovy 기반 | ✅ JavaScript/TypeScript |
| 실행 방식 | GUI 또는 CLI | ✅ CLI 중심 (CI/CD 친화적) |
| 리소스 효율성 | 높은 메모리 사용 | ✅ 가벼운 Go 엔진 |
| 프로토콜 지원 | ✅ 20+ 프로토콜 (FTP, JDBC, JMS 등) | HTTP, WebSocket, gRPC (확장) |
| 학습 곡선 | GUI 복잡, 높은 진입장벽 | ✅ 코드 중심, 낮은 진입장벽 |
| 버전 관리 | XML 기반 (.jmx 파일) | ✅ Git 친화적 JS 파일 |
| 실시간 모니터링 | 별도 플러그인 필요 | ✅ Grafana 네이티브 통합 |
| 브라우저 테스트 | WebDriver 플러그인 | ✅ xk6-browser 공식 지원 |
| 커뮤니티/생태계 | ✅ 20년+ 역사, 방대한 자료 | 빠르게 성장 중 (27K+ GitHub stars) |
Reddit 커뮤니티의 실제 사용자 반응
"JMeter로 5년간 테스트를 해왔는데, k6로 전환한 후 테스트 작성 시간이 70% 줄었습니다. Git으로 버전 관리가 된다는 것만으로도 충분한 이유가 되었죠."
— Reddit r/performanceengineering, Senior QA Engineer
"마이크로서비스 환경에서 k6는 그저 완벽합니다. Docker 컨테이너로 쉽게 배포되고, Kubernetes에서 분산 실행도 간단해요. JMeter는 JVM 의존성 때문에 컨테이너 이미지가 너무 무거웠어요."
— Reddit r/devops, Platform Engineer
🚀 설치부터 첫 테스트까지: 5분 완성 가이드
k6의 가장 큰 매력 중 하나는 설치의 용이성입니다. 운영체제별로 패키지 매니저를 통해 간단히 설치할 수 있으며, Docker 이미지도 공식 제공됩니다.
운영체제별 설치 방법
# macOS (Homebrew)
brew install k6
# Windows (Chocolatey)
choco install k6
# Windows (Winget)
winget install k6
# Linux (Debian/Ubuntu)
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
# Docker
docker pull grafana/k6
첫 번째 테스트 스크립트 작성하기
설치가 완료되었다면, 간단한 HTTP GET 테스트를 작성해 보겠습니다. first-test.js 파일을 생성하고 다음 코드를 입력하세요:
import http from 'k6/http';
import { check, sleep } from 'k6';
// 테스트 옵션 설정
export const options = {
vus: 10, // 10개의 가상 사용자
duration: '30s', // 30초간 실행
};
// 기본 테스트 함수
export default function () {
const response = http.get('https://test-api.example.com/users');
// 응답 검증
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1); // 1초 대기
}
이제 터미널에서 다음 명령어로 테스트를 실행합니다:
k6 run first-test.js
테스트 결과 해석하기
k6는 테스트 완료 후 터미널에 다음과 같은 핵심 메트릭을 출력합니다:
- checks: 통과/실패한 검증 개수
- data_received: 수신된 총 데이터량
- data_sent: 전송된 총 데이터량
- http_req_duration: HTTP 요청 소요 시간 (avg, min, max, p90, p95)
- http_req_failed: 실패한 요청 비율
- iterations: 완료된 반복 횟수
- vus: 가상 사용자 수
http_req_duration의 p95(95th percentile) 값이 가장 중요합니다. 평균값은 이상치에 민감하지만, p95는 대부분의 사용자 경험을 대표하는 지표입니다.
💻 JavaScript로 만드는 강력한 테스트 시나리오
k6의 진정한 강력함은 단순한 HTTP 요청을 넘어, 복잡한 실제 사용자 행동을 시뮬레이션하는 데 있습니다. JavaScript의 모든 기능을 활용할 수 있어 동적이고 지능적인 테스트를 작성할 수 있습니다.
1. 스테이지(Stages)를 활용한 부하 패턴 설계
실제 트래픽은 일정하지 않습니다. 출시 이벤트, 마케팅 캠페인, 일일 사용 패턴 등 다양한 부하 패턴을 시뮬레이션해야 합니다:
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp-up: 2분 동안 100 VU까지 증가
{ duration: '5m', target: 100 }, // Steady state: 5분간 100 VU 유지
{ duration: '2m', target: 400 }, // Spike: 2분 동안 400 VU로 급증
{ duration: '5m', target: 400 }, // Spike sustain: 5분간 400 VU 유지
{ duration: '3m', target: 0 }, // Ramp-down: 3분 동안 종료
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% 요청이 500ms 미만
http_req_failed: ['rate<0.1'], // 실패율 0.1% 미만
},
};
2. 시나리오(Scenarios)로 복잡한 사용자 흐름 구현
여러 유형의 사용자 행동을 동시에 시뮬레이션하려면 시나리오를 사용합니다:
export const options = {
scenarios: {
// 시나리오 1: 일반 브라우저 사용자
browsing: {
executor: 'constant-vus',
vus: 50,
duration: '10m',
exec: 'browseProducts',
},
// 시나리오 2: 구매 전환 사용자 (더 높은 우선순위)
purchasing: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 10 },
{ duration: '8m', target: 10 },
],
exec: 'completePurchase',
gracefulRampDown: '30s',
},
// 시나리오 3: API 클라이언트 (도착률 기반)
api_calls: {
executor: 'constant-arrival-rate',
rate: 1000,
timeUnit: '1m', // 분당 1000 요청
duration: '10m',
preAllocatedVUs: 20,
maxVUs: 100,
exec: 'apiRequests',
},
},
};
export function browseProducts() {
http.get('https://shop.example.com/products');
sleep(Math.random() * 3 + 1); // 1-4초 랜덤 대기
}
export function completePurchase() {
const payload = JSON.stringify({ product: 'laptop', qty: 1 });
http.post('https://shop.example.com/cart', payload, {
headers: { 'Content-Type': 'application/json' },
});
sleep(2);
}
export function apiRequests() {
http.get('https://api.example.com/v1/data');
}
3. 파라미터화와 데이터 활용
실제 테스트에서는 다양한 입력 데이터가 필요합니다. k6는 CSV, JSON 파일을 읽거나 외부 데이터 소스를 활용할 수 있습니다:
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
// CSV 파일 로드
const csvData = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), { header: true }).data;
});
export default function () {
const user = csvData[Math.floor(Math.random() * csvData.length)];
const payload = JSON.stringify({
username: user.username,
password: user.password,
});
const response = http.post('https://api.example.com/login', payload, {
headers: { 'Content-Type': 'application/json' },
});
check(response, {
'login successful': (r) => r.status === 200 && r.json('token') !== '',
});
}
4. 커스텀 메트릭 수집
기본 메트릭 외에 비즈니스 특화 메트릭을 수집할 수 있습니다:
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
// 커스텀 메트릭 정의
const loginDuration = new Trend('login_duration');
const checkoutErrorRate = new Rate('checkout_errors');
const itemsAdded = new Counter('items_added_to_cart');
const activeUsers = new Gauge('concurrent_shoppers');
export default function () {
const start = Date.now();
const loginRes = http.post('https://shop.example.com/login', credentials);
loginDuration.add(Date.now() - start);
if (loginRes.status !== 200) {
checkoutErrorRate.add(1);
}
// 장바구니에 아이템 추가
const cartRes = http.post('https://shop.example.com/cart/add', item);
if (cartRes.status === 200) {
itemsAdded.add(1);
}
activeUsers.add(1);
sleep(5);
activeUsers.add(-1);
}
🔌 xk6 확장 생태계: 브라우저 테스트부터 gRPC까지
k6의 핵심은 가볍고 빠른 HTTP 테스트에 있지만, xk6 확장 시스템을 통해 기능을 무한히 확장할 수 있습니다. Go로 작성된 확장을 빌드하여 k6에 통합하는 방식입니다.
핵심 xk6 확장 모듈
Chromium 기반 브라우저 자동화로 Core Web Vitals를 측정합니다. 페이지 로드, 클릭, 폼 입력 등 실제 사용자 행동을 시뮬레이션합니다.
import { browser } from 'k6/experimental/browser';
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: {
browser: {
type: 'chromium',
},
},
},
},
};
export default async function () {
const page = browser.newPage();
try {
await page.goto('https://shop.example.com');
// 제품 검색
await page.locator('input[name="search"]').type('laptop');
await page.locator('button[type="submit"]').click();
// 성능 메트릭 수집
const metrics = await page.evaluate(() => {
return JSON.stringify(performance.getEntriesByType('navigation'));
});
console.log(metrics);
} finally {
await page.close();
}
}
마이크로서비스 환경에서 gRPC 프로토콜을 테스트합니다. Protocol Buffers를 지원합니다.
import grpc from 'k6/net/grpc';
import { check } from 'k6';
const client = new grpc.Client();
client.load(['definitions'], 'service.proto');
export default () => {
client.connect('grpc.example.com:443', {
plaintext: false,
});
const response = client.invoke('service.Method', {
key: 'value',
});
check(response, {
'status is OK': (r) => r && r.status === grpc.StatusOK,
});
client.close();
};
MySQL, PostgreSQL, SQLite, SQL Server 등 다양한 데이터베이스에 직접 쿼리를 실행하여 테스트합니다.
실제와 유사한 가짜 데이터(이름, 주소, 이메일 등)를 생성하여 더 현실적인 테스트를 만듭니다.
확장 설치 및 사용
xk6 빌드 도구를 사용하여 커스텀 k6 바이너리를 생성합니다:
# xk6 설치
go install go.k6.io/xk6/cmd/xk6@latest
# 브라우저 확장 포함된 k6 빌드
xk6 build --with github.com/grafana/xk6-browser
# 여러 확장 동시 포함
xk6 build \
--with github.com/grafana/xk6-browser \
--with github.com/grafana/xk6-sql \
--with github.com/grafana/xk6-faker
🔄 CI/CD 통합: DevOps 파이프라인에 성능 테스트 심기
현대적인 소프트웨어 개발에서 성능 테스트는 프로덕션 배포 직전이 아닌, 개발 초기부터 지속적으로 수행되어야 합니다. k6는 이 "Shift-Left" 철학을 완벽하게 지원합니다.
GitHub Actions 통합
Pull Request마다 자동으로 스모크 테스트를 실행하는 예시:
# .github/workflows/performance.yml
name: Performance Tests
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup k6
uses: grafana/setup-k6-action@v1
- name: Run smoke tests
uses: grafana/run-k6-action@v1
with:
path: ./tests/smoke.js
flags: --env ENV=staging
env:
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: k6-report
path: ./summary.html
GitLab CI 통합
# .gitlab-ci.yml
performance_test:
image: grafana/k6:latest
stage: test
script:
- k6 run --out json=results.json tests/load.js
artifacts:
paths:
- results.json
expire_in: 1 week
only:
- merge_requests
- main
Jenkins 파이프라인
// Jenkinsfile
pipeline {
agent any
environment {
K6_TOKEN = credentials('k6-cloud-token')
}
stages {
stage('Performance Test') {
steps {
sh '''
docker run --rm -v $(pwd):/tests \
-e K6_CLOUD_TOKEN=$K6_TOKEN \
grafana/k6:latest cloud /tests/load.js
'''
}
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'results',
reportFiles: 'index.html',
reportName: 'k6 Performance Report'
])
}
}
}
성능 회귀 방지: Threshold 기반 게이트
CI/CD에서 가장 중요한 것은 성능 기준을 명확히 설정하는 것입니다. k6의 threshold를 활용하면 자동으로 배포를 차단할 수 있습니다:
export const options = {
thresholds: {
// HTTP 요청 기준
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.05'],
// 비즈니스 로직 기준
checks: ['rate>=0.95'],
// 커스텀 메트릭 기준
login_duration: ['avg<300', 'p(95)<600'],
checkout_errors: ['rate<0.01'],
},
// Threshold 실패 시 테스트 중단
abortOnFail: true,
};
abortOnFail: true 옵션은 임계값 실패 시 즉시 테스트를 중단합니다. 프로덕션 환경을 보호하기 위해 권장되지만, 초기 설정 시에는 false로 시작하여 기준선을 파악하는 것이 좋습니다.
☁️ Grafana Cloud k6: 클라우드 기반 분산 테스트
로컬 머신의 한계를 넘어 전 세계 21개 로드 존에서 동시에 테스트를 실행하고 싶다면, Grafana Cloud k6를 활용하세요. 2025년 기준으로 많은 새로운 기능이 추가되었습니다.
주요 기능
글로벌 로드 존
미국, 유럽, 아시아, 남미 등 전 세계 21개 지역에서 동시에 테스트 실행. 실제 글로벌 사용자 경험을 측정합니다.
실시간 결과 분석
테스트 실행 중에도 실시간으로 메트릭을 확인하고, 문제 발생 시 즉시 중단할 수 있습니다.
GitHub Actions 통합
Pull Request에 자동으로 테스트 요약을 코멘트로 추가. 팀원들이 즉시 성능 변화를 파악할 수 있습니다.
확장 모듈 지원
클라우드에서도 xk6 확장을 사용할 수 있어, 복잡한 프로토콜 테스트도 가능합니다.
클라우드 테스트 실행
CLI에서 바로 클라우드 테스트를 실행합니다:
# 로그인
k6 cloud login --token YOUR_TOKEN
# 클라우드에서 테스트 실행
k6 cloud script.js
# 특정 로드 존에서 실행
k6 cloud --ext-load-impact distribution='{"amazon:us:ashburn": 0.5, "amazon:de:frankfurt": 0.5}' script.js
가격 정책 (2026년 기준)
| 플랜 | VU(가상 사용자) | 테스트 실행 시간 | 주요 기능 |
|---|---|---|---|
| Free | 최대 50 VU | 월 500 VUh | 기본 메트릭, 30일 데이터 보관 |
| Pro | 최대 5,000 VU | 유연한 크레딧 | 확장 모듈, 1년 데이터 보관, SLA |
| Enterprise | 무제한 | 맞춤형 | 온프레미스 옵션, 전담 지원, SSO |
💡 실전 팁과 커뮤니티 인사이트
k6를 실제 프로덕션 환경에서 사용하는 개발자들의 경험에서 나온 팁들을 모았습니다.
1. 메모리 누수 방지
JavaScript의 특성상 전역 변수에 대량의 데이터를 누적하면 메모리 사용량이 급증합니다:
// ❌ 잘못된 예: 메모리 누수 발생
const allResponses = [];
export default function () {
const res = http.get('https://api.example.com/data');
allResponses.push(res); // VU당 메모리 계속 증가
}
// ✅ 올바른 예: 필요한 데이터만 추출
export default function () {
const res = http.get('https://api.example.com/data');
// 필요한 값만 추출하고 바로 검증
check(res, {
'has valid data': (r) => r.json('status') === 'ok',
});
// 글로벌 메트릭에만 기록
myMetric.add(res.timings.duration);
}
2. DNS 캐싱 최적화
높은 부하 테스트에서 DNS 조회가 병목이 될 수 있습니다:
export const options = {
// DNS 캐싱 활성화
dns: {
ttl: '5m', // 5분간 캐싱
select: 'first', // 첫 번째 IP만 사용
policy: 'preferIPv4',
},
};
3. TLS 핸드쉐이크 최소화
연결 재사용으로 TLS 오버헤드를 줄입니다:
export const options = {
// 연결 유지 설정
batch: 20, // 배치 요청
batchPerHost: 6, // 호스트당 최대 동시 연결
noConnectionReuse: false, // 연결 재사용 활성화
tlsAuth: null, // TLS 상호 인증 비활성화 (필요시)
};
4. 커뮤니티에서 자주 언급되는 함정들
sleep() 함수 누락
요청 사이에 sleep을 넣지 않으면 비현실적인 부하가 발생합니다. 실제 사용자는 페이지 로드 후 생각하고 클릭하는 시간이 필요합니다.
환경 변수 하드코딩
URL, 인증 정보를 코드에 직접 작성하지 말고, __ENV 객체를 활용하여 외부에서 주입하세요.
단일 JSON 파싱 반복
동일한 JSON 응답을 여러 번 파싱하면 CPU 사용량이 증가합니다. 한 번 파싱한 결과를 변수에 저장하세요.
SharedArray 오용
대용량 CSV 파일을 SharedArray로 로드하면 모든 VU가 메모리를 공유하여 OOM이 발생할 수 있습니다. 데이터를 샤딩하여 사용하세요.
5. 디버깅 팁
# 상세 로그 출력
k6 run --verbose script.js
# HTTP 디버깅
k6 run --http-debug="full" script.js
# 특정 VU만 실행 (디버깅용)
k6 run --vus 1 --iterations 1 script.js
# 결과를 JSON으로 내보내기
k6 run --out json=results.json script.js
🔮 2026년 전망과 마무리
k6는 2025년 5월, k6 1.0 릴리즈를 통해 "신뢰할 수 있고 예측 가능하며 확장 가능한 성능 테스트"라는 비전을 재확인했습니다. Grafana Labs의 지원 아래 다음과 같은 방향으로 발전하고 있습니다:
로드맵과 미래 기능
- AI 기반 성능 분석: 테스트 결과의 이상 징후를 자동으로 감지하고 원인을 제안
- 향상된 브라우저 테스트: Web Vitals의 완벽한 지원과 시각적 회귀 테스트 통합
- 카오스 엔지니어링: k6와 LitmusChaos의 통합으로 장애 주입 테스트
- WebSocket 개선: 더 복잡한 실시간 시나리오 지원
핵심 요약
k6는 현대적인 개발 워크플로우에 최적화된 부하 테스트 도구입니다. JavaScript의 친숙함, Go의 성능, Grafana의 시각화가 결합되어 있어 마이크로서비스와 클라우드 네이티브 애플리케이션 테스트에 탁월한 선택입니다. JMeter의 방대한 프로토콜 지원이 필요하지 않다면, k6로의 전환을 고려해볼 만합니다.
시작하기: brew install k6 또는 docker pull grafana/k6로 지금 바로 시작하세요!
🚀 성능 테스트는 두려움이 아닌 자신감을 주어야 합니다. k6와 함께라면 프로덕션 배포도 안심하세요.