(2/6) 홈페이지 스트레스 테스트 — k6 로 부하·스파이크·소크 돌리기
- 무엇을·왜·어떤 지표로 재는가 — 테스트 종류와 목표 지표
- k6 로 부하·스파이크·소크 돌리기 — 실전 부하 시나리오 ← 지금 글
- 프론트엔드 성능 — 코어 웹 바이탈과 렌더링 스트레스
- 백엔드·DB 병목 찾기 — 모니터링과 프로파일링
- 복원력 테스트 — 카오스 엔지니어링과 장애 주입
- 보안·가용성 스트레스 — Rate Limit·DDoS·종합 체크리스트
Summary
1편에서 무엇을·어떤 지표로 잴지 정했으니, 이제 실제로 서버에 부하를 쏠 차례예요. 이번 편의 주인공은 k6 입니다. 요즘 부하 테스트에서 사실상 표준처럼 쓰이는 도구인데, 테스트 시나리오를 자바스크립트로 짜고 결과를 백분위수까지 깔끔하게 뽑아줘서 처음 시작하기에 딱 좋아요.
1편에서 정리한 부하·스파이크·소크 시나리오를 이번엔 전부 실행되는 k6 스크립트로 옮깁니다. 스크립트를 조금씩 바꿔가며 세 가지 테스트를 어떻게 만드는지, 그리고 나온 결과를 어떻게 읽고 통과/실패를 판정하는지까지 다뤄요.
💡 이 글에서 다루는 것
- 부하 도구 비교 — k6 / Locust / JMeter / Gatling 중 왜 k6
- k6 설치와 가장 작은 첫 스크립트
- VU·stages·thresholds — 부하의 모양을 결정하는 3요소
- 부하 / 스파이크 / 소크 테스트를 각각 스크립트로
- 결과 읽기 — p95·에러율·체크 통과율
1. 도구 선택 — 왜 k6 인가
부하 테스트 도구는 여러 갈래입니다. 대표적인 넷을 비교할게요.
| 도구 | 스크립트 언어 | 장점 | 단점 |
|---|---|---|---|
| k6 | JavaScript | 가볍고 빠름, 결과 지표 깔끔, CI 통합 쉬움 |
브라우저 렌더링은 측정 안 함 |
| Locust | Python | 파이썬으로 유연, 분산 쉬움 |
단일 프로세스 성능 한계 |
| JMeter | GUI/XML | 역사·플러그인 방대 | 무겁고 XML 설정 번거로움 |
| Gatling | Scala/Java | 고성능, 리포트 예쁨 | Scala 진입장벽 |
이 시리즈는 k6 로 갑니다. 이유는 세 가지예요. 첫째, 설치가 바이너리 하나로 끝나고 가볍습니다. 둘째, 시나리오를 자바스크립트로 짜니까 웹 개발자에게 친숙해요. 셋째, p95 < 800ms 같은 통과 기준(thresholds)을 스크립트 안에 직접 박을 수 있어서 1편에서 정한 목표 지표를 그대로 코드로 옮길 수 있습니다.
한 가지만 미리 짚을게요. k6 는 HTTP 요청 수준의 부하를 측정합니다. 즉 서버가 응답을 돌려주는 속도는 재지만, 그 응답을 받은 브라우저가 화면을 그리는 속도(렌더링)는 측정하지 않아요. 프론트엔드 렌더링 성능은 3편에서 Lighthouse 로 따로 잽니다. 이 둘은 다른 층이에요.
2. 설치와 첫 스크립트
설치는 OS 별 패키지 매니저로 한 줄입니다. 맥이면 이렇게 해요.
brew install k6
설치 확인은 버전 출력으로 합니다.
k6 version
k6 v0.52.0 (go1.22.5, darwin/arm64)
가장 작은 스크립트를 하나 만들어볼게요. script.js 로 저장합니다.
import http from 'k6/http';
import { sleep, check } from 'k6';
export default function () {
const res = http.get('https://test.k6.io'); // 테스트 대상 URL
// 응답이 정상인지 검증
check(res, {
'상태 코드 200': (r) => r.status === 200,
'응답 500ms 미만': (r) => r.timings.duration < 500,
});
sleep(1); // 가상 사용자가 1초 쉬고 다음 요청 (실사용자 흉내)
}
여기서 export default function 안에 든 내용이 가상 사용자(VU, Virtual User) 한 명이 반복하는 행동입니다. 페이지를 열고, 응답을 확인하고, 잠깐 쉬고, 다시 반복하는 거예요. 실행은 이렇게 합니다.
k6 run --vus 10 --duration 30s script.js
--vus 10 은 가상 사용자 10명, --duration 30s 는 30초 동안이라는 뜻이에요. 가상 사용자 10명이 30초 동안 계속 페이지를 두드리는 상황을 만든 겁니다.
💡
sleep(1)은 사소해 보이지만 중요해요. 실제 사용자는 페이지를 열자마자 곧바로 다음 페이지를 누르지 않고 잠깐 읽습니다. 이 “생각하는 시간(think time)”을 빼면 비현실적으로 가혹한 부하가 돼서, 실전보다 나쁜 숫자가 나와요.
3. 부하의 모양 — stages 와 thresholds
명령줄 옵션(--vus, --duration)으로도 되지만, 부하의 모양을 제대로 그리려면 스크립트 안에 options 를 씁니다. 여기 두 가지 핵심이 들어가요.
- stages — 시간에 따라 VU 수를 어떻게 올리고 내릴지. 부하의 “모양”을 그리는 부분.
- thresholds — 통과/실패 기준. 1편에서 정한 목표 지표를 그대로 박는 부분.
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // 2분에 걸쳐 0 → 100명으로 증가
{ duration: '5m', target: 100 }, // 100명으로 5분 유지
{ duration: '2m', target: 0 }, // 2분에 걸쳐 0명으로 감소
],
thresholds: {
http_req_duration: ['p(95)<800'], // 95%가 800ms 이내 (실패 시 빨간불)
http_req_failed: ['rate<0.01'], // 에러율 1% 미만
},
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
이게 전형적인 부하 테스트의 모양입니다. 서서히 올려서(ramp-up), 목표 부하로 한동안 유지하고(steady), 서서히 내리는(ramp-down) 사다리꼴이에요. 갑자기 100명을 꽂지 않고 서서히 올리는 이유는, 오토스케일과 캐시 워밍업이 반응할 시간을 주기 위해서입니다. 실제 트래픽도 보통 이렇게 증가해요.
thresholds 에 걸린 기준을 하나라도 넘기면 k6 는 종료 코드를 실패로 반환합니다. 그래서 이 스크립트를 CI 파이프라인에 넣으면 “성능이 기준 아래로 떨어지면 배포 실패” 를 자동화할 수 있어요.
4. 스파이크 테스트 — 급증을 견디나
같은 스크립트에서 stages 만 바꾸면 다른 종류의 테스트가 됩니다. 스파이크 테스트는 트래픽을 순식간에 치솟게 했다가 뚝 떨어뜨려요. 이벤트 오픈, 뉴스 노출, 푸시 알림 발송 같은 순간을 흉내 내는 거예요.
export const options = {
stages: [
{ duration: '30s', target: 50 }, // 평상시 부하
{ duration: '10s', target: 1500 }, // 10초 만에 1500명으로 폭증 ⚡
{ duration: '1m', target: 1500 }, // 잠깐 유지
{ duration: '20s', target: 50 }, // 다시 평상시로
{ duration: '2m', target: 50 }, // 회복 확인
],
thresholds: {
http_req_failed: ['rate<0.05'], // 스파이크 중엔 기준을 살짝 완화
},
};
스파이크 테스트에서 볼 것은 두 가지예요. 첫째, 폭증하는 순간 에러가 얼마나 터지는가. 오토스케일이 새 서버를 띄우기까지 보통 1~2분이 걸리는데, 그 사이의 짧은 순간을 버티는지가 관건입니다. 둘째, 폭증이 지나간 뒤 정상으로 돌아오는가. 어떤 시스템은 스파이크에 한 번 밀리면 큐가 쌓여서 부하가 사라진 뒤에도 한참 회복을 못 해요. 마지막 “회복 확인” 구간에서 응답 시간이 평상시로 돌아오는지를 봅니다.
⚠️ 스파이크 테스트는 오토스케일 설정을 검증하는 가장 좋은 방법이에요. “서버가 몇 % CPU 에서 몇 초 만에 몇 대까지 늘어나는가”를 실제로 눈으로 확인할 수 있습니다. 설정만 해두고 한 번도 안 터뜨려본 오토스케일은, 정작 실전에서 안 도는 경우가 흔해요.
5. 소크 테스트 — 오래 돌 때 새는 것
소크(soak) 테스트는 중간 정도 부하를 아주 오래 유지합니다. 부하의 세기가 아니라 시간이 무기예요. 짧은 테스트로는 절대 안 보이는 문제를 잡습니다.
export const options = {
stages: [
{ duration: '5m', target: 300 }, // 서서히 올려서
{ duration: '3h', target: 300 }, // 3시간 유지 (핵심)
{ duration: '5m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<800'],
},
};
여기서 봐야 할 건 부하 도구의 숫자가 아니라 서버 쪽 자원 그래프예요. 3시간 동안 부하는 일정한데 다음 중 하나라도 우상향한다면 문제입니다.
- 메모리가 계속 증가 → 메모리 누수. 결국 OOM 으로 죽어요.
- DB 커넥션 수가 계속 증가 → 커넥션을 반납 안 하고 있음. 풀이 고갈됩니다.
- 응답 시간이 서서히 증가 → 캐시·로그·임시 데이터가 무한히 쌓이는 중.
- 파일 디스크립터가 계속 증가 → 소켓·파일을 안 닫고 있음.
부하는 일정한데 자원이 우상향한다는 건, 요청을 처리할 때마다 뭔가를 조금씩 안 돌려주고 있다는 뜻이에요. 이건 오픈 직후엔 멀쩡하다가 며칠 뒤 새벽에 서버가 죽는 전형적인 사고의 원인입니다. 그래서 소크 테스트는 자원 모니터링(4편)과 반드시 함께 봐야 해요.
6. 결과 읽기
테스트가 끝나면 k6 가 요약을 출력합니다. 핵심만 발췌하면 이런 모양이에요.
✓ 상태 코드 200
✓ 응답 500ms 미만
checks.........................: 99.87% ✓ 89234 ✗ 116
http_req_duration..............: avg=210ms p(90)=350ms p(95)=620ms
http_req_failed................: 0.13% ✓ 116 ✗ 89234
http_reqs......................: 89350 148.9/s
vus............................: 100 min=0 max=100
✓ http_req_duration..........: p(95)=620ms (기준 p95<800 통과)
✓ http_req_failed............: rate=0.0013 (기준 rate<0.01 통과)
읽는 순서를 정리하면 이래요.
- http_req_failed (에러율) 를 가장 먼저 봅니다. 여기가 높으면 나머지 숫자는 볼 것도 없이 실패예요. 0.13% 면 양호합니다.
- http_req_duration 의 p(95) — 620ms 로 기준 800ms 안에 들었어요. 평균(avg=210ms)이 아니라 p95 를 본다는 걸 잊지 마세요.
- http_reqs 의 초당 값(148.9/s) — 실제로 소화한 처리량입니다. 목표 RPS 와 비교합니다.
- 맨 아래 thresholds 통과 여부 — 초록 체크면 통과, 빨간불이면 종료 코드가 실패로 나와요.
🎉 세 가지 기준(에러율·p95·처리량)이 모두 목표 안에 들면 그 부하 수준은 통과입니다. 그럼 stages 의 target 을 더 올려서 어디서 빨간불이 켜지는지 찾아가면, 그게 곧 1편에서 말한 브레이크포인트 테스트가 돼요.
숫자를 그래프로 더 자세히 보고 싶으면, k6 결과를 Grafana 로 흘려보내 실시간 대시보드로 보는 방법도 있습니다. 이 관측 쪽은 4편에서 서버 지표와 함께 다룰게요.
7. 정리
- HTTP 부하는 k6 로 — 가볍고, 스크립트가 JS 라 친숙하고, 기준을 코드에 박음
- stages 로 부하의 모양을, thresholds 로 통과 기준을 그린다
- stages 만 바꾸면 부하 → 스파이크 → 소크 로 테스트 종류가 바뀐다
- 소크 테스트는 부하 숫자가 아니라 서버 자원 그래프의 우상향을 본다
- 결과는 에러율 → p95 → 처리량 → thresholds 통과 순으로 읽는다
k6 로 “서버가 응답을 얼마나 빨리, 얼마나 많이 돌려주나”는 확인했어요. 그런데 사용자가 실제로 느끼는 속도는 그 응답을 받은 브라우저가 화면을 그리는 속도까지 포함합니다. 다음 편에서는 이 프론트엔드 층을 Lighthouse 와 코어 웹 바이탈로 눌러볼게요.
일단 오늘은 여기까지….. 다음 글에서는 프론트엔드 성능과 코어 웹 바이탈을 정리해볼게요.
← 이전 글: (1/6) 홈페이지 스트레스 테스트 — 무엇을·왜·어떤 지표로 재는가 | 다음 글 →: (3/6) 홈페이지 스트레스 테스트 — 프론트엔드 성능과 코어 웹 바이탈