6 분 소요

🧪 홈페이지 스트레스 테스트 완벽 가이드 7부작 — 새로 만든 홈페이지가 얼마나 단단한지 확인하려면 무엇을, 어떻게, 어떤 지표로 재야 할까요. 목표 지표 설계부터 부하·프론트엔드·백엔드·복원력·보안, 그리고 실제 리포트 해석까지, 홈페이지를 무너뜨려 보며 한계를 재는 테스트를 편마다 하나씩. 전체 7편.
  1. 무엇을·왜·어떤 지표로 재는가 — 테스트 종류와 목표 지표
  2. k6 로 부하·스파이크·소크 돌리기 — 실전 부하 시나리오지금 글
  3. 프론트엔드 성능 — 코어 웹 바이탈과 렌더링 스트레스
  4. 백엔드·DB 병목 찾기 — 모니터링과 프로파일링
  5. 복원력 테스트 — 카오스 엔지니어링과 장애 주입
  6. 보안·가용성 스트레스 — Rate Limit·DDoS·종합 체크리스트
  7. 샘플 리포트로 읽는 결과 해석 — 숫자를 판정과 액션으로

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/7) 홈페이지 스트레스 테스트 — 무엇을·왜·어떤 지표로 재는가다음 글 →: (3/7) 홈페이지 스트레스 테스트 — 프론트엔드 성능과 코어 웹 바이탈