6 분 소요

🧪 홈페이지 스트레스 테스트 완벽 가이드 6부작 — 새로 만든 홈페이지가 얼마나 단단한지 확인하려면 무엇을, 어떻게, 어떤 지표로 재야 할까요. 목표 지표 설계부터 부하·프론트엔드·백엔드·복원력·보안까지, 홈페이지를 무너뜨려 보며 한계를 재는 테스트를 편마다 하나씩. 전체 6편.
  1. 무엇을·왜·어떤 지표로 재는가 — 테스트 종류와 목표 지표
  2. k6 로 부하·스파이크·소크 돌리기 — 실전 부하 시나리오지금 글
  3. 프론트엔드 성능 — 코어 웹 바이탈과 렌더링 스트레스
  4. 백엔드·DB 병목 찾기 — 모니터링과 프로파일링
  5. 복원력 테스트 — 카오스 엔지니어링과 장애 주입
  6. 보안·가용성 스트레스 — 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) 홈페이지 스트레스 테스트 — 프론트엔드 성능과 코어 웹 바이탈