모니터링과 부하테스트

 

 

🚀 모니터링과 부하테스트 입문

부하테스트 모니터링 K6 nGrinder APM Prometheus Grafana

📌 강의 목표

이 특강이 끝나면 여러분은 다음을 할 수 있게 됩니다:

  1. 부하테스트가 왜 필요한지, 어떤 종류가 있는지 설명할 수 있다.
  2. TPS, 응답시간(p95/p99), 에러율 같은 핵심 지표를 읽고 해석할 수 있다.
  3. K6로 간단한 부하테스트 스크립트를 작성하고 실행할 수 있다.
  4. nGrinder가 무엇이고 K6와 어떻게 다른지 비교할 수 있다.
  5. 부하테스트 지표와 시스템 지표(CPU, 메모리, JVM/GC, DB)를 함께 보고 병목을 추론할 수 있다.
핵심 메시지: "숫자 하나(평균 응답시간)만 보지 말고, 여러 지표를 동시에 보고 원인을 추론하는 능력을 기르자."

1. 왜 부하테스트와 모니터링이 필요한가

개발 환경에서 잘 동작하던 API가 운영에서 사용자가 몰리면 느려지거나 죽는 일이 흔합니다. 이유는 단순합니다. 개발 중에는 보통 혼자(동시 사용자 1명) 테스트하기 때문입니다.

실제 서비스는 수백, 수천 명이 동시에 같은 자원(스레드, DB 커넥션, 메모리)을 두고 경쟁합니다.

부하테스트는 "사용자가 몰리는 상황을 인위적으로 재현"해서 시스템이 어디까지 버티는지, 어디서 무너지는지를 미리 확인하는 작업입니다.

모니터링은 그 과정과 운영 중에 "시스템 내부에서 무슨 일이 일어나는지"를 숫자로 보는 작업입니다.

이 둘은 항상 같이 갑니다. 부하를 주는 쪽(부하테스트 도구)에서 나오는 지표와, 부하를 받는 쪽(서버)에서 나오는 지표를 짝지어 봐야 원인을 알 수 있기 때문입니다.

비유: 부하테스트 도구는 "환자에게 운동을 시키는 러닝머신"이고, 모니터링은 "그동안 심박수, 혈압, 호흡을 재는 센서"입니다. 러닝머신 속도만 봐서는 환자 상태를 알 수 없습니다.

2. 핵심 개념과 용어

부하테스트 결과를 읽으려면 먼저 용어를 정확히 알아야 합니다. 입문자가 가장 자주 헷갈리는 것들입니다.

2.1 부하를 표현하는 용어

VU (Virtual User, 가상 사용자): 동시에 요청을 보내는 가상의 사용자 수. K6에서는 VUs, nGrinder에서는 Vuser라고 부릅니다. VU 1명은 "요청 보내고 응답 받으면 다음 요청을 보내는" 행동을 반복합니다.

동시 사용자(Concurrency)와 초당 요청 수(RPS)는 다릅니다. VU 100명이라고 해서 초당 100건이 아닙니다. 한 요청이 0.2초 걸리면 VU 1명은 초당 약 5건을 보낼 수 있으므로, VU 100명은 초당 약 500건을 만듭니다.

RPS = VU 수 × (1 / 응답시간)
예: VU 50개, 평균 응답시간 100ms → RPS = 50 × (1 / 0.1) = 500 RPS

2.2 처리량을 표현하는 용어

  • Throughput(처리량): 단위 시간당 처리한 요청 수
  • TPS (Transactions Per Second): 초당 처리한 트랜잭션(요청) 수. 시스템의 "처리 능력"을 나타내는 가장 중요한 지표. 높을수록 좋다.
  • RPS (Requests Per Second): 초당 요청 수. TPS와 거의 같은 의미로 쓰이지만, 한 트랜잭션이 여러 HTTP 요청으로 구성되면 달라질 수 있다.

2.3 응답시간(Latency)을 표현하는 용어

Latency / Response Time(응답시간): 요청을 보내고 응답을 받기까지 걸린 시간. 낮을수록 좋다.

가장 중요한 입문 포인트: 평균(average)만 보면 안 됩니다.

예를 들어 응답시간이 [100ms, 100ms, 100ms, 100ms, 5000ms]라면 평균은 1080ms입니다. 평균만 보면 "1초쯤 걸리네"라고 생각하지만, 실제로는 5명 중 1명이 5초를 기다린 것입니다. 이 1명이 가장 화가 난 사용자이고, 이런 사용자가 이탈합니다.

그래서 백분위수(Percentile)를 봅니다.

백분위수 의미 실무 활용
p50 (median) 절반의 요청이 이 시간 안에 처리됨 중앙값 확인용
p95 95%의 요청이 이 시간 안에 처리됨 느린 5%의 경계 — SLO 기준
p99 99%의 요청이 이 시간 안에 처리됨 가장 느린 1%의 경계

실무에서는 보통 p95, p99를 SLO(Service Level Objectives / 서비스 수준 목표)의 기준으로 삼습니다.

예시: "p95 응답시간 200ms 이하"

2.4 안정성을 표현하는 용어

Error Rate(에러율): 전체 요청 중 실패한 요청의 비율. HTTP 5xx, 타임아웃, 커넥션 거부 등이 실패로 잡힙니다. 부하가 올라가면서 에러율이 치솟는 지점이 바로 시스템의 한계점입니다.

2.5 용어 요약

용어 의미 좋은 방향
VU / Vuser 동시 가상 사용자 수 (부하의 크기, 조절 대상)
TPS / RPS 초당 처리 요청 수 높을수록 좋음
p95 / p99 응답시간 느린 쪽 응답시간 낮을수록 좋음
Error Rate 실패 비율 0%에 가깝게

3. 부하테스트의 종류

같은 도구라도 "어떤 패턴으로 부하를 주는가"에 따라 목적이 다릅니다.

테스트 종류 설명 목적
Smoke Test VU 1~2명으로 아주 가볍게 스크립트 정상 동작 여부 사전 점검
Load Test 예상 피크 트래픽 재현 목표 동시 사용자 수에서 기준 만족 여부 검증
Stress Test 부하를 점점 올려 한계점 탐색 "최대 몇 명까지 버티는가" 확인
Spike Test 짧은 시간에 트래픽 급증 급증/급감 시 시스템 반응 확인
Soak Test 중간 부하를 수 시간 유지 메모리 누수, 커넥션 누수 탐지
입문 단계에서는 Smoke → Load → Stress 순서만 확실히 이해하면 충분합니다.

4. K6 소개

K6는 Grafana Labs가 개발하는 오픈소스 부하테스트 도구입니다. Go로 만들어졌고, 테스트 스크립트는 JavaScript로 작성합니다. 현재도 활발히 개발되고 있습니다.

주요 특징:
  • CLI 기반으로 가볍고 빠름 — k6 run script.js 한 줄로 실행
  • 스크립트가 JavaScript라 Git 버전 관리하기 좋음
  • Prometheus, Grafana, InfluxDB 등으로 결과 내보내기 가능
  • CI/CD 파이프라인 자동화에 적합

4.1 설치

TERMINAL
# macOS
brew install k6

# Windows (Chocolatey)
choco install k6

# Linux (Ubuntu/Debian)
sudo gpg --no-default-keyring \
  --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
  --keyserver hkp://keyserver.ubuntu.com:80 \
  --recv-keys C5AD17C747E3415A3642D57D77C6C491FC6F0692
echo "deb 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 run --rm -i grafana/k6 run - < script.js

4.2 가장 기본적인 스크립트

JAVASCRIPT (K6)
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,          // 동시 가상 사용자 10명
  duration: '30s',  // 30초 동안 실행
};

export default function () {
  const res = http.get('http://localhost:8080/api/products');

  // 응답 검증: 상태코드가 200인가
  check(res, {
    'status is 200': (r) => r.status === 200,
  });

  sleep(1); // 다음 요청까지 1초 대기 (실제 사용자 행동 모사)
}

4.3 단계적으로 부하를 올리는 스크립트 (Stress Test)

JAVASCRIPT (K6)
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },   // 1분에 VU 50명까지 증가
    { duration: '2m', target: 50 },   // 2분간 50명 유지
    { duration: '1m', target: 200 },  // 1분에 200명까지 증가
    { duration: '2m', target: 200 },  // 2분간 200명 유지
    { duration: '1m', target: 0 },    // 1분에 걸쳐 종료
  ],
};

export default function () {
  const res = http.get('http://localhost:8080/api/products');
  check(res, { 'status is 200': (r) => r.status === 200 });
}

4.4 기준 자동화 (Thresholds) — 현업 필수!

K6의 강력한 기능 중 하나는 Thresholds입니다. SLO를 코드로 박아두면, 기준 미달 시 테스트가 자동으로 실패(exit code ≠ 0)합니다. CI에서 성능 회귀를 잡는 데 유용합니다.

JAVASCRIPT (K6)
export const options = {
  vus: 50,
  duration: '1m',
  thresholds: {
    // 95%가 200ms 이내여야 통과
    http_req_duration: ['p(95)<200'],
    // 에러율 1% 미만이어야 통과
    http_req_failed: ['rate<0.01'],
  },
};

4.5 현업 고급 패턴 — Arrival Rate (RPS 기반)

현업에서는 VU 기반보다 초당 몇 건을 처리해야 하는가에 집중하는 RPS 기반 테스트가 선호됩니다.

JAVASCRIPT (K6)
import http from 'k6/http';

export const options = {
  scenarios: {
    // 평소 트래픽 (100 RPS)
    baseline: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '2m',
      preAllocatedVUs: 20,
    },
    // 피크 시간 (100 → 500 RPS 점진 증가)
    peak: {
      executor: 'ramping-arrival-rate',
      startTime: '2m',
      startRate: 100,
      timeUnit: '1s',
      stages: [
        { target: 500, duration: '3m' },
        { target: 500, duration: '5m' },
        { target: 0,   duration: '1m' },
      ],
      preAllocatedVUs: 100,
      maxVUs: 200,
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<300', 'p(99)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  http.get('http://test.k6.io/');
}

5. nGrinder 소개

nGrinder는 네이버가 The Grinder를 기반으로 만든 오픈소스 부하테스트 플랫폼입니다. 국내 기업 환경에서 오래 사용되어 한국어 자료가 풍부하고, 웹 GUI 기반이라 비개발 직군도 접근하기 쉽다는 장점이 있습니다.

중요 현황: naver/ngrinder 공식 GitHub 저장소는 2025년 9월 24일 아카이브(read-only) 처리되어 더 이상 활발히 개발되지 않습니다. 최신 버전은 3.5.9-p1입니다. 신규 프로젝트라면 이 점을 고려해야 합니다.

5.1 구조 (K6와 가장 큰 차이)

컴포넌트 역할
Controller 웹 UI 제공. 스크립트 작성, 테스트 설정, 실행, 리포트 담당
Agent 실제로 부하를 생성하는 부분. 큰 부하가 필요하면 여러 대로 분산
Target 부하를 받는 우리 서버 (Spring Boot 애플리케이션)

5.2 스크립트 (Groovy 예시)

GROOVY (nGrinder)
// Groovy 스크립트 예시 (핵심 부분만)
import static net.grinder.script.Grinder.grinder
import HTTPClient.HTTPResponse
import net.grinder.plugin.http.HTTPRequest

class TestRunner {
  public static HTTPRequest request = new HTTPRequest()

  @Test
  public void test() {
    HTTPResponse result = request.GET("http://localhost:8080/api/products")
    assertThat(result.statusCode, is(200))
  }
}

5.3 사용 흐름

  1. 컨트롤러 WAR 실행 → 웹 브라우저로 접속
  2. 에이전트를 다운로드해 부하 생성기로 등록
  3. 웹 UI에서 대상 URL, Vuser 수, 실행 시간 설정
  4. 실행하면 TPS, 응답시간, 에러를 실시간 그래프로 확인
  5. 종료 후 리포트(TPS 추이, 평균/최대 응답시간 등) 확인

6. K6 vs nGrinder 비교

항목 K6 nGrinder
개발/관리 Grafana Labs, 활발히 개발 중 네이버, 2025년 9월 아카이브
인터페이스 CLI 중심 웹 GUI 중심
스크립트 언어 JavaScript Groovy / Jython
설치/구성 단일 바이너리, 가벼움 Controller + Agent 구성 필요
분산 부하 가능 (k6 Cloud 또는 직접 구성) 에이전트 다중화로 쉽게 분산
코드 관리(Git) 매우 적합 상대적으로 불편
CI/CD 연동 매우 적합 상대적으로 불편
시각화 Grafana 연동 강력 자체 리포트 내장
진입 장벽 코드 작성 필요 GUI로 쉽게 시작
한국어 자료 보통 풍부
정리: 코드로 관리하고 CI/CD에 자동화하고 싶다면 K6, 웹 클릭으로 빠르게 시작하고 싶다면 nGrinder. 새로 배우는 입장이라면 K6를 메인으로 익히기를 권합니다.

6.1 도구 선택 가이드 (2024년 동향)

도구 적합한 팀 핵심 장점
K6 개발자 중심, DevOps 팀 CI/CD 친화적, 효율적 리소스 사용
JMeter QA 팀, 레거시 환경 방대한 플러그인, GUI 풍부
Locust Python 팀 Python 스크립트, 분산 테스트

7. 부하테스트 지표 읽기

K6 실행 후 출력되는 요약 결과를 해석하는 방법입니다.

K6 OUTPUT
http_req_duration..: avg=120ms min=15ms med=98ms max=2.1s p(90)=210ms p(95)=350ms
http_req_failed....: 0.50%  ✓ 12   ✗ 2388
http_reqs..........: 2400   80.1/s
vus................: 50     min=0  max=50
지표 의미
avg 120ms 평균은 괜찮아 보이지만...
p(95) 350ms 느린 5%는 350ms 초과 — "p95 200ms" 목표라면 미달!
max 2.1s 가장 느린 요청은 2.1초
http_req_failed 0.50% 약간의 에러 발생 — 원인 확인 필요
http_reqs 80.1/s 사실상 TPS/RPS

해석 순서

  1. 에러율 먼저 — 에러가 많으면 응답시간 숫자는 의미 없음 (실패한 요청은 빨리 끝나서 평균을 왜곡함)
  2. p95/p99 응답시간 — 평균이 아니라 백분위수로 판단
  3. TPS 확인 — VU를 올렸는데 TPS가 그대로면 그 지점이 포화 상태
자주 나오는 패턴: VU를 2배로 올렸는데 TPS는 그대로이고 응답시간만 2배가 됐다면, 시스템은 이미 포화 상태이고 요청들이 큐에서 대기하고 있는 것입니다.

8. APM과 시스템 지표

부하테스트 도구의 숫자는 "밖에서 본 결과"입니다. 왜 느린지 알려면 "서버 안"을 봐야 합니다. Spring Boot에서는 Actuator + Micrometer + Prometheus + Grafana 조합이 표준입니다.

8.1 Spring Boot Actuator 설정

GROOVY (build.gradle)
dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
  implementation 'io.micrometer:micrometer-registry-prometheus'
}
YAML (application.yml)
management:
  endpoints:
    web:
      exposure:
        include: health, info, prometheus, metrics
  metrics:
    tags:
      application: ${spring.application.name}
      environment: ${spring.profiles.active}

8.2 꼭 확인해야 할 서버 지표

지표 확인 방법 병목 증상
CPU 사용률 node_cpu_usage 100% 유지 → CPU 바운드 연산 과다
JVM Heap jvm_memory_used_bytes 우상향 추세 → 메모리 누수 의심
GC pause jvm_gc_pause_seconds 빈번/장시간 일시 정지 → 응답시간 튀는 원인
스레드 풀 tomcat.threads.busy 모두 사용 중 → 새 요청 대기
HikariCP hikaricp.* pending 증가 → 풀 고갈 또는 쿼리 느림

8.3 HikariCP 커넥션 풀 사이징

핵심 인사이트: 커넥션 풀은 "작을수록 좋은 경우"가 많습니다. Oracle 테스트에서 2048개 → 96개로 줄이니 응답시간이 100ms → 2ms로 개선됐습니다!
FORMULA
# 권장 공식
connections = (core_count * 2) + effective_spindle_count

# 4코어 + SSD
4 * 2 + 0 = 8 connections

# 4코어 + HDD 2개
4 * 2 + 2 = 10 connections

8.4 증상 ↔ 원인 매핑

부하테스트 쪽(밖) 증상 서버 쪽(안) 확인 지표
응답시간이 길어짐 CPU, GC, DB 커넥션 대기, 스레드 풀
에러율 급증(5xx) 예외 로그, 스레드 풀 고갈, 커넥션 타임아웃
TPS가 더 안 오름 CPU 포화 여부, DB가 한계인지
시간이 갈수록 느려짐 Heap 우상향(메모리 누수), 커넥션 누수

9. 병목 진단 실습 (케이스 스터디)

케이스 A: 평균은 괜찮은데 p99가 매우 나쁨

CASE A
// 증상
avg 90ms, p99 1800ms. 에러는 거의 없음.

// 관찰
Grafana에서 응답시간이 주기적으로 튄다.
그 시점에 GC pause 그래프도 같이 튄다.

// 추론
GC 멈춤이 일부 요청을 길게 만든다.

// 조치 방향
Heap 크기, GC 알고리즘 점검.
불필요한 객체 생성 줄이기.

케이스 B: VU를 올려도 TPS가 안 오름

CASE B
// 증상
VU 50 → 100으로 올렸는데 TPS는 80 → 82로 그대로.
p95는 2배.

// 관찰
서버 CPU는 40%로 한가함.
HikariCP pending connection이 계속 쌓임.

// 추론
CPU가 한가한데 막힌다 = DB 커넥션 풀 병목.
요청들이 커넥션을 기다리는 중.

// 조치 방향
커넥션 풀 크기 조정, 느린 쿼리 최적화(인덱스), N+1 쿼리 제거.

케이스 C: 부하 시작 직후 에러율 급증

CASE C
// 증상
부하를 주자마자 5xx가 쏟아짐.

// 관찰
톰캣 스레드 풀이 즉시 꽉 참.
로그에 connection timeout.

// 추론
처리 용량 대비 동시 요청이 너무 많거나,
다운스트림(외부 API/DB)이 막혀 스레드가 점유됨.

// 조치 방향
스레드 풀/타임아웃 설정 점검, 다운스트림 응답 확인.

케이스 D: Soak 테스트에서 시간이 갈수록 느려짐

CASE D
// 증상
처음엔 100ms, 2시간 뒤 1500ms. 부하는 일정.

// 관찰
Heap 사용량이 계속 우상향.
GC를 해도 회수 안 됨.

// 추론
메모리 누수 (캐시 무한 증가, 닫지 않은 리소스 등).

// 조치 방향
힙 덤프 분석, 누수 객체 추적, 리소스 close 확인.
진단의 일반 원칙: "밖에서 본 증상(응답시간, 에러, TPS)" 하나를 잡고, "안의 지표(CPU, GC, 스레드, 커넥션, 메모리)" 중 같은 시점에 함께 움직이는 것을 찾는 것이 핵심입니다.

10. 마무리 체크리스트

  • 응답시간은 평균이 아니라 p95/p99로 본다
  • 지표는 항상 에러율 → 응답시간 → TPS 순으로 읽는다
  • 부하테스트 도구(밖)와 서버 지표(안)를 짝지어 원인을 추론한다
  • VU를 올려도 TPS가 안 오르면 그 지점이 시스템의 한계다
  • CPU가 한가한데 느리면 대기(DB 커넥션, 락, I/O)를 의심한다
  • 메모리 누수는 Soak 테스트와 Heap 그래프 우상향으로 잡는다
  • 새로 배운다면 K6를 메인으로, nGrinder는 GUI 대안으로 이해한다

11. 다음 프로젝트에 바로 적용하기 — 현업 실천 가이드

 다음 프로젝트에서 실제로 어떻게 적용할지가 중요하다.
현업에서 자주 쓰이는 실천 방법들을 단계별로 정리했습니다.

11.1 SLO부터 먼저 정하고 시작하기

부하테스트를 돌리기 전에 가장 먼저 해야 할 일은 "얼마나 빨라야 합격인가"를 팀이 합의하는 것입니다. 기준 없이 돌리면 결과를 봐도 "이게 좋은 건지 나쁜 건지" 판단할 수 없습니다.

현업에서 자주 쓰는 첫 번째 SLO 기준점은 "p95 응답시간 300ms 이하, 에러율 1% 미만"입니다. 서비스 특성에 따라 조정하되, 팀원 모두가 동의한 숫자를 코드(K6 Thresholds)로 고정해두면 사람마다 기준이 달라지는 혼선을 막을 수 있습니다.

 

JAVASCRIPT (K6 — SLO를 코드로 고정)
// 팀이 합의한 SLO를 thresholds로 고정
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<300', 'p(99)<500'],
    http_req_failed:   ['rate<0.01'],
  },
};
// 이 기준을 통과하지 못하면 k6가 exit code 1을 반환
// → CI 파이프라인에서 빌드 실패로 잡을 수 있음

11.2 개발 초기부터 Actuator + Prometheus 붙이기

모니터링 스택은 나중에 붙이면 붙일수록 귀찮아집니다. 프로젝트 시작 시점에 의존성 두 줄만 추가해두면 나중에 Grafana 대시보드 연동이 훨씬 수월합니다. 특히 spring.application.name 태깅을 초반에 잡아두지 않으면, 마이크로서비스가 늘어났을 때 어떤 서비스의 지표인지 구분이 안 되는 상황이 생깁니다.

YAML (application.yml — 프로젝트 초기 세팅)
spring:
  application:
    name: my-service   # 반드시 고유한 이름으로

management:
  endpoints:
    web:
      exposure:
        include: health, prometheus, metrics
  metrics:
    tags:
      application: ${spring.application.name}
      env: ${spring.profiles.active:local}

11.3 Smoke Test를 PR 규칙으로 만들기

현업에서 부하테스트가 흐지부지되는 가장 큰 이유는 "배포 직전에 한 번 돌리고 끝"이기 때문입니다. 팀에서 지속적으로 운영하려면 GitHub Actions 같은 CI에 Smoke Test를 연결해두고 PR마다 자동으로 실행되게 만드는 게 효과적입니다. Smoke Test는 VU 1~2명으로 30초면 충분하니 CI 시간도 크게 늘지 않습니다.

YAML (GitHub Actions — PR마다 Smoke Test 자동 실행)
name: Smoke Test

on: [pull_request]

jobs:
  smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start app
        run: ./gradlew bootRun &
        # 앱 기동 대기

      - name: Run K6 Smoke Test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: k6/smoke.js
          # thresholds 미달 시 자동으로 step 실패

11.4 부하테스트 대상 API 우선순위 정하기

모든 API를 다 테스트하려고 하면 오히려 아무것도 제대로 못 합니다. 현업에서는 다음 기준으로 우선순위를 정합니다.

우선순위 대상 이유
🔴 높음 로그인, 메인 피드, 결제, 검색 트래픽이 가장 많이 몰리는 경로
🟡 중간 상품 상세, 주문 목록 조회 DB 쿼리 복잡도가 높아 병목 가능성 있음
🟢 낮음 설정 변경, 단순 CRUD 트래픽 적고 로직 단순

11.5 로컬에서 돌릴 때 주의할 점

로컬 머신에서 K6를 돌리면 테스트 도구 자체가 CPU와 메모리를 잡아먹어서 결과가 왜곡될 수 있습니다. 특히 VU가 50명을 넘어가면 로컬 결과를 그대로 믿으면 안 됩니다. 현업에서는 테스트 서버(또는 CI 에이전트)와 애플리케이션 서버를 물리적으로 분리해서 실행하는 것이 기본입니다.

로컬 테스트의 한계: 테스터 PC가 느리면 테스트 결과도 느리게 나옵니다. "내 컴퓨터에서는 p95 500ms였는데 서버에서 돌리니 100ms"가 되는 경우가 흔합니다. 진지한 테스트는 항상 격리된 환경에서 실행하세요.

11.6 Grafana 대시보드를 미리 구성해두기

부하테스트를 돌리면서 Grafana 없이 터미널 숫자만 보면 놓치는 게 많습니다. 특히 "GC pause가 언제 일어났는가"는 숫자가 아닌 그래프에서만 패턴이 보입니다. Grafana Labs가 공식 제공하는 Spring Boot 대시보드(ID: 11378)와 K6 대시보드(ID: 2587)를 Import해두면 처음부터 쓸만한 대시보드를 빠르게 세팅할 수 있습니다.

TERMINAL (Grafana 공식 대시보드 Import)
# Grafana UI에서 Dashboards → Import → ID 입력

11378  # JVM (Micrometer) — Spring Boot 전용
1860   # Node Exporter Full — 서버 OS 지표
2587   # K6 Load Testing Results
9628   # PostgreSQL Database — DB 사용 시

11.7 부하테스트 결과를 문서로 남기는 습관

한 번 돌리고 끝나는 테스트는 팀에 아무 자산도 남기지 않습니다. 현업에서는 테스트 결과를 간단한 포맷으로 Confluence, Notion, 또는 PR 코멘트에 남깁니다. 다음 릴리스 때 "이번 변경 후 TPS가 얼마나 바뀌었는가"를 비교할 수 있어야 의미가 있습니다.

결과 기록 최소 포맷 (PR 코멘트나 노션에 붙여넣기):

📅 테스트 일시: 2026-06-02
🎯 대상 API: GET /api/products
👥 VU: 50명 / 시간: 3분
📊 TPS: 312 req/s
⏱️ p95: 187ms / p99: 290ms
❌ 에러율: 0.1%
🔍 특이사항: HikariCP pending 일시적 증가 (max 3개), 무해 수준

11.8 운영 전 체크리스트 — "출시 전 반드시 돌릴 것"

기능 개발이 끝나고 배포 전에 팀 내에서 공유할 수 있는 최소한의 체크포인트입니다. 처음부터 거창하게 하려고 하면 실천이 어렵습니다. 이 네 가지만 습관으로 만들어도 운영에서 발생하는 대부분의 성능 이슈를 사전에 잡을 수 있습니다.

  • Smoke Test 통과 확인 (스크립트가 정상 동작하는가)
  • 목표 트래픽 기준 Load Test 실행 후 SLO 만족 여부 확인
  • 부하 중 HikariCP pending, 스레드 풀 포화 여부 Grafana로 확인
  • 테스트 결과 TPS / p95 / 에러율 수치를 PR 또는 노션에 기록

📚 보충 자료

부록 A. 자주 쓰는 K6 명령어

TERMINAL
# 기본 실행
k6 run script.js

# CLI 옵션 직접 지정
k6 run --vus 50 --duration 30s script.js

# 결과를 JSON으로 저장 (사후 분석)
k6 run --out json=result.json script.js

# Prometheus로 전송 (실시간 모니터링)
k6 run --out experimental-prometheus-rw script.js

# Docker로 실행
docker run --rm -i grafana/k6 run \
  -e TARGET_URL=http://localhost:8080 \
  - < script.js

부록 B. K6 주요 내장 지표

지표 의미
http_req_duration 요청 전체 응답시간 (DNS+연결+응답 포함)
http_req_waiting 서버 처리 대기 시간 (TTFB)
http_req_failed 실패한 요청 비율
http_reqs 총 요청 수 + 초당 요청 수
vus / vus_max 현재/최대 가상 사용자 수

부록 C. SLO/SLI/SLA 개념

SLI: 실제로 측정한 값 (예: 현재 p95 응답시간)
SLO: 달성하려는 목표 (예: p95 < 200ms)
SLA: 고객과 약속한 수준 (SLO 위반 시 패널티 발생)

부록 D. JVM GC 종류

GC 특징 적합한 상황
Serial GC 단일 스레드, 간단 Heap < 100MB 소규모
Parallel GC 멀티 스레드, 처리량 중심 배치 처리, 백그라운드 jobs
G1 GC 균형 잡힌 지연/처리량 Heap ≥ 6GB, latency-sensitive
ZGC / Shenandoah 초저지연 (< 10ms) 대형 Heap (≥ 100GB)

부록 E. 현업 통합 파이프라인 (2024년 동향)

  1. K6로 부하테스트 실행
  2. Prometheus가 K6 메트릭 + 서버 메트릭 통합 수집
  3. Grafana에서 실시간 대시보드 (테스트 결과 + APM)
  4. SLO 기반 Alertmanager로 Slack/PagerDuty 알림
  5. OpenTelemetry로 마이크로서비스 간 추적

부록 F. 참고 링크

 

'스파르타 심화 과정' 카테고리의 다른 글

RAG 이해하기  (0) 2026.06.09
CQRS  (0) 2026.05.26
spring 과제  (0) 2026.04.21
4/20 Spring 기초(2) 특강  (0) 2026.04.20
spring 특강 1  (0) 2026.04.20