(7/7) 홈페이지 스트레스 테스트 — 샘플 리포트로 읽는 결과 해석
Summary
여섯 편에 걸쳐 부하(2편)·프론트엔드(3편)·백엔드(4편)·복원력(5편)·보안(6편)을 각각 어떻게 테스트하는지 배웠어요. 그런데 실전에서 진짜 어려운 건 테스트를 돌리는 것보다 나온 숫자를 해석하는 것입니다. p95 가 780ms 인데 이게 통과인가? 메모리가 조금 늘었는데 이게 누수인가 정상인가? 어느 문제부터 고쳐야 하나?
이번 마지막 편은 그 해석을 연습합니다. 가상의 쇼핑몰 홈페이지 shop.example.com 에 지금까지 배운 테스트를 전부 돌렸다고 가정하고, 그 결과를 담은 샘플 스트레스 테스트 리포트를 처음부터 끝까지 함께 읽어요. 숫자가 어떻게 나왔고, 무엇을 통과/실패로 판정했고, 발견한 문제를 어떤 우선순위로 액션으로 바꿨는지까지. 실제 리포트를 쓸 때 그대로 참고할 수 있는 한 장 템플릿으로 마무리합니다.
💡 이 글에서 다루는 것
- 스트레스 테스트 리포트의 표준 구조
- 테스트 대상·조건과 판정 기준(SLO) 명시
- 시나리오별 결과 읽기 — 부하·스파이크·소크·프론트·백엔드
- 발견 문제를 심각도로 분류하고 우선순위 액션으로
- 리포트 한 장 요약 템플릿
1. 리포트의 구조
숫자를 잔뜩 던져놓은 문서는 리포트가 아니에요. 좋은 스트레스 테스트 리포트는 읽는 사람이 5분 안에 “오픈해도 되나?”에 답할 수 있게 짜여 있어야 합니다. 표준 구조는 이래요.
| 섹션 | 담는 내용 | 읽는 사람 |
|---|---|---|
| 총평 (Executive Summary) | 한 문단 결론 + 오픈 가부 |
모두 |
| 테스트 조건 | 대상·환경·시나리오·기준 | 재현하려는 사람 |
| 시나리오별 결과 | 지표 표 + 판정 | 엔지니어 |
| 발견 사항 | 문제 + 원인 + 근거 | 엔지니어 |
| 권고·액션 | 우선순위별 할 일 | 의사결정자 |
핵심은 결론을 맨 위에 두는 거예요. 바쁜 사람은 총평만 읽고, 궁금한 사람만 아래로 내려갑니다. 그럼 이 구조 그대로 샘플 리포트를 채워볼게요.
2. 총평 — 리포트 맨 위
리포트의 첫 화면입니다. 여기만 읽어도 상황이 잡혀야 해요.
📋 총평.
shop.example.com오픈 전 스트레스 테스트를 수행했다. 결론: 조건부 오픈 가능. 예상 피크(동시 500명)의 부하는 목표 지표를 만족하며 견딘다. 다만 소크 테스트에서 DB 커넥션 누수가, 백엔드에서 상품 리뷰의 N+1 쿼리가 발견됐다. 둘 다 오픈 후 트래픽이 누적되면 장애로 이어질 수 있어 오픈 전 수정을 권고한다. 프론트엔드 LCP 는 기준을 소폭 초과(2.8s)하나 심각하지 않아 오픈 후 개선 대상으로 둔다.
이 한 문단에 세 가지가 다 들어 있어요. 결론(조건부 오픈), 잘 된 것(예상 부하는 견딤), 막는 것(커넥션 누수·N+1 은 오픈 전 수정). 나머지 섹션은 이 문단의 근거일 뿐입니다.
3. 테스트 조건 — 무엇을, 어떤 기준으로
결과를 믿으려면 조건을 알아야 해요. 1편에서 강조한 대로, 판정 기준(SLO)을 먼저 못 박아둡니다.
- 대상:
shop.example.com(스테이징, 운영과 동일 스펙 · 운영 규모 데이터 복제) - 환경: 앱 서버 2대(4vCPU/8GB), DB 1대(8vCPU/32GB), 앞단 CDN
- 부하 생성: k6, 별도 리전에서 실행
- 시나리오: 부하 / 스파이크 / 소크 / 브레이크포인트 + 프론트엔드 + 백엔드 모니터링
판정 기준은 이렇게 표로 못 박았어요. 이게 있어야 결과에 초록/빨강을 매길 수 있습니다.
| 지표 | 목표(SLO) |
|---|---|
| 동시 접속 | 500 VU 에서 안정 |
| p95 응답 시간 | < 800 ms |
| 에러율 | < 0.1 % |
| 서버 CPU | < 80 % 유지 |
| 소크(3h) 자원 | 우상향 없음 |
4. 부하 테스트 결과 — 예상 부하는 견디나
먼저 예상 피크(500 VU)까지 서서히 올리는 부하 테스트. k6 요약을 발췌하면 이래요.
http_reqs......................: 1,284,300 713.5/s
http_req_duration..............: avg=190ms p(90)=410ms p(95)=680ms p(99)=1.2s
http_req_failed................: 0.04% ✓ 514 ✗ 1,283,786
vus............................: 500 max=500
✓ http_req_duration..........: p(95)=680ms (기준 p95<800 통과)
✓ http_req_failed............: rate=0.0004 (기준 rate<0.001 통과)
이걸 2편에서 배운 순서(에러율 → p95 → 처리량)로 읽어볼게요.
| 지표 | 결과 | 기준 | 판정 |
|---|---|---|---|
| 에러율 | 0.04 % | < 0.1 % | ✅ 통과 |
| p95 | 680 ms | < 800 ms | ✅ 통과 |
| p99 | 1.2 s | (참고) | ⚠️ 주시 |
| 처리량 | 713 RPS | — | ✅ 목표 충족 |
| CPU(피크) | 71 % | < 80 % | ✅ 통과 |
에러율·p95 모두 기준 안이라 부하 테스트는 통과예요. 한 가지 눈여겨볼 건 p99 가 1.2초로 튄다는 점입니다. 평균과 p95 는 좋은데 가장 느린 1%가 1.2초라는 건, 대부분은 빠르지만 일부 요청에서 뭔가 오래 걸린다는 신호예요. 이건 뒤의 백엔드 분석에서 N+1 의 꼬리로 다시 등장합니다.
✅ 여기서 배울 점: 평균이 좋다고 끝이 아니에요. 꼬리(p99) 를 봐야 숨은 문제의 실마리가 보입니다. p95 는 통과라도 p99 가 튀면 “왜 저 1% 만 느리지?”를 파고들 가치가 있어요.
5. 스파이크·소크 결과 — 시간이 드러낸 것
스파이크 (10초 만에 1500 VU)
이벤트 오픈 순간을 흉내 낸 스파이크 테스트예요.
- 폭증 직후 30초간 에러율이 2.1%까지 상승했다가, 오토스케일이 3번째 서버를 띄운 뒤 정상화.
- 부하가 빠진 뒤 90초 만에 p95 가 평상시(680ms)로 복귀 → 회복도 정상이에요.
판정은 조건부 통과예요. 폭증 순간의 짧은 에러(2.1%)는 오토스케일이 뜨기 전의 1~2분이라 불가피한 면이 있지만, 이벤트가 예정돼 있다면 미리 서버를 늘려두는(프리워밍) 걸 권고 사항에 넣습니다.
소크 (300 VU, 3시간) — 문제 발견
부하는 3시간 내내 일정했는데, 서버 자원 그래프가 이렇게 나왔어요.
[소크 3시간 — 부하 일정(300 VU)]
메모리 사용률 : 42% → 44% → 43% (안정, 정상)
DB 커넥션(사용중) : 18 → 27 → 39 → 48 (계속 우상향 ⚠️)
p95 응답 시간 : 620ms → 700ms → 890ms (서서히 악화 ⚠️)
메모리는 안정적인데 DB 커넥션이 3시간 내내 우상향했고, 그에 따라 p95 도 서서히 나빠졌어요. 4편에서 다룬 전형적인 커넥션 누수 패턴입니다. 부하가 일정한데 사용 중 커넥션이 계속 는다는 건, 요청 처리 후 커넥션을 반납하지 않는 코드가 있다는 뜻이에요.
🚨 이게 이번 테스트의 가장 중요한 발견입니다. 짧은 부하 테스트(4번 결과)에서는 전혀 안 보였어요. 소크 테스트가 아니었으면 오픈 후 며칠 뒤 새벽에 커넥션 풀이 고갈되며 서버가 통째로 멈췄을 문제입니다. 이래서 소크 테스트를 하는 거예요.
6. 프론트엔드 결과 — 코어 웹 바이탈
3편의 Lighthouse 를 모바일·느린 네트워크 조건으로 3회 측정한 중앙값이에요.
| 지표 | 결과 | 기준 | 판정 |
|---|---|---|---|
| LCP | 2.8 s | < 2.5 s | ⚠️ 초과 |
| INP | 140 ms | < 200 ms | ✅ 통과 |
| CLS | 0.03 | < 0.1 | ✅ 통과 |
INP·CLS 는 통과인데 LCP 가 2.8초로 기준(2.5초)을 소폭 초과했어요. Lighthouse 개선 제안을 보니 메인 히어로 이미지가 최적화 안 된 큰 PNG(1.8MB)라, WebP 로 바꾸고 지연 로딩만 걸어도 기준 안에 들 것으로 보입니다. 심각도는 낮음 — 사용성을 크게 해치는 수준은 아니라, 오픈을 막지는 않고 개선 대상으로 둡니다.
7. 백엔드 병목 — p99 꼬리의 정체
4번에서 본 p99 1.2초의 정체를, 4편의 APM 으로 파고들었어요. 가장 느린 요청 하나를 시간 분해하니 이렇게 나왔습니다.
요청: GET /products/{id} 총 1,180 ms
├─ 라우팅·미들웨어 25 ms
├─ DB 쿼리 (상품 조회) 140 ms
├─ DB 쿼리 (리뷰 N+1 ×48) 870 ms ← 범인
├─ 외부 API (배송비) 100 ms
└─ 렌더링 45 ms
상품 상세 페이지가 리뷰를 가져올 때 리뷰 개수만큼 쿼리를 반복하는 N+1 쿼리였어요. 리뷰가 많은 인기 상품일수록 느려지니, 4번의 p99 꼬리(가장 느린 1%)가 바로 리뷰 많은 상품 페이지였던 거예요. 하나의 쿼리로 묶으면(eager loading) 해결됩니다.
💡 리포트의 힘이 여기서 나와요. “부하 테스트에서 p99 가 튄다”(4번)와 “상품 페이지가 느리다”(APM)와 “리뷰 N+1”(원인)이 하나의 이야기로 연결됩니다. 각 층의 결과를 따로 보지 않고 엮어야, 증상이 아니라 원인을 고칠 수 있어요.
8. 브레이크포인트 — 우리 한계는 몇 RPS
마지막으로 죽을 때까지 부하를 올려 최대 용량을 쟀어요.
- ~850 RPS 까지는 SLO 유지.
- 850 RPS 를 넘자 DB CPU 가 95%에 닿으며 p95 가 급격히 악화, 920 RPS 에서 에러율 급증.
- 즉 현재 구성의 안전 한계는 약 850 RPS, 예상 피크(713 RPS)와의 여유는 약 19%.
이 여유가 충분한지는 판단의 영역이에요. 평상시엔 괜찮지만, 마케팅으로 트래픽이 30% 뛰면 한계를 넘습니다. 그래서 권고에 DB 읽기 부하 분산(리드 레플리카) 을 중기 과제로 넣었어요.
9. 종합 판정과 액션 아이템
이제 발견을 모아 심각도로 분류하고 우선순위 액션으로 바꿉니다. 이게 리포트에서 의사결정자가 실제로 보는 부분이에요.
| 발견 | 심각도 | 액션 | 시점 |
|---|---|---|---|
| DB 커넥션 누수 (소크) |
🔴 높음 | 커넥션 반납 누락 수정 |
오픈 전 |
| 리뷰 N+1 쿼리 | 🔴 높음 | eager loading 으로 묶기 |
오픈 전 |
| 스파이크 초기 에러 2.1% |
🟡 중간 | 이벤트 전 서버 프리워밍 |
오픈 전 (이벤트 시) |
| LCP 2.8s | 🟢 낮음 | 히어로 이미지 WebP·지연로딩 |
오픈 후 |
| 한계 여유 19% | 🟡 중간 | 리드 레플리카 도입 |
중기 |
심각도를 나누는 기준은 “오픈 후 장애로 이어지는가” 예요. 커넥션 누수와 N+1 은 시간이 지나면 반드시 터지므로 🔴 높음, LCP 는 불편하지만 장애는 아니라 🟢 낮음. 이렇게 나눠야 “무엇을 오픈 전에 꼭 고치고, 무엇을 나중에 해도 되는가”가 명확해집니다.
✅ 좋은 리포트는 문제를 나열하는 데서 끝나지 않아요. 각 문제에 심각도·액션·시점을 붙여 받는 사람이 바로 우선순위를 잡을 수 있게 합니다. “느립니다”가 아니라 “리뷰 N+1 을 오픈 전에 eager loading 으로 고치세요”까지 써야 리포트예요.
10. 리포트 한 장 템플릿
지금까지의 구조를 그대로 복사해 쓸 수 있게 템플릿으로 정리했어요. 새 홈페이지를 테스트할 때 이 틀에 숫자만 채우면 됩니다.
# [사이트명] 스트레스 테스트 리포트 (YYYY-MM-DD)
## 총평
- 결론: [오픈 가능 / 조건부 / 불가]
- 견딘 것: [한 줄]
- 막는 것: [오픈 전 필수 수정 항목]
## 테스트 조건
- 대상 / 환경 / 데이터:
- 시나리오: 부하 / 스파이크 / 소크 / 브레이크포인트
- 판정 기준(SLO): p95 < __ms, 에러율 < __%, CPU < __%
## 시나리오별 결과 (지표 | 결과 | 기준 | 판정)
- 부하 / 스파이크 / 소크 / 프론트(CWV) / 백엔드
- 최대 용량(브레이크포인트): __ RPS, 피크 대비 여유 __%
## 발견 사항 (발견 | 원인 | 근거)
## 권고·액션 (발견 | 심각도 | 액션 | 시점)
11. 정리 — 시리즈를 마치며
- 리포트는 결론을 맨 위에 — 총평만 읽어도 오픈 가부가 잡히게
- 판정 기준(SLO)을 먼저 못 박아야 결과에 초록/빨강을 매긴다
- 각 층 결과를 하나의 이야기로 엮기 — p99 꼬리 → 상품 페이지 → 리뷰 N+1
- 소크 테스트가 짧은 부하에선 안 보이는 커넥션 누수를 잡았다
- 발견을 심각도·액션·시점으로 바꿔야 비로소 리포트가 된다
일곱 편에 걸쳐 홈페이지를 계획하고, 부수고, 들여다보고, 흔들고, 방어를 시험하고, 마지막으로 그 결과를 리포트로 해석하는 데까지 왔어요. 스트레스 테스트의 진짜 결과물은 예쁜 그래프가 아니라 “이건 오픈 전에 고치고, 저건 나중에 해도 된다”는 명확한 판단이에요. 그 판단을 숫자로 뒷받침할 수 있게 되었다면, 이 시리즈는 제 몫을 다한 겁니다.
일단 오늘은 여기까지….. 긴 시리즈 따라와 주셔서 고맙습니다. 이제 자신 있게, 단단한 홈페이지를 오픈하세요.