5 분 소요

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

Summary

앞의 두 편은 “밖에서” 홈페이지를 눌렀어요. 2편은 서버에 부하를, 3편은 브라우저 렌더링을 쟀죠. 그런데 부하를 걸었을 때 “느려졌다”까지는 알아도, 느려졌는지 — 어느 쿼리가, 어느 자원이 병목인지 — 는 서버 바깥에서 안 보여요. 이번 편은 백엔드 안쪽으로 들어갑니다.

부하 테스트와 백엔드 모니터링은 한 쌍이에요. k6 로 부하를 거는 동안 서버 내부 지표를 같이 봐야, “동시 접속 500 에서 DB 커넥션이 고갈되면서 응답이 튀었다”처럼 원인까지 짚을 수 있어요. 이번 편은 그 내부 지표를 어떻게 보고, 가장 흔한 병목 세 가지(느린 쿼리·N+1·커넥션 풀)를 어떻게 잡는지 다룹니다.

💡 이 글에서 다루는 것

  • 부하를 걸며 동시에 서버 내부를 보는 이유
  • 네 골든 시그널과 USE 방법론으로 병목 좁히기
  • DB 병목 — 느린 쿼리, N+1, 인덱스 부재
  • 커넥션 풀 고갈 — 소크 테스트에서 자주 터지는 문제
  • APM 으로 요청 하나의 시간 분해하기



1. 부하와 모니터링은 한 쌍

핵심 원칙부터 짚을게요. 부하 테스트는 반드시 서버 내부 모니터링과 동시에 돌려야 합니다. k6 결과만 보면 “p95 가 갑자기 튀었다”는 증상만 알 수 있어요. 그 순간 서버의 CPU·메모리·DB 커넥션 그래프를 겹쳐 봐야 원인이 보입니다.

예를 들어 이런 식으로 원인이 드러나요.

  • 응답 시간이 튄 순간 CPU 가 100% → 연산이 병목. 코드 최적화나 스케일아웃 필요.
  • CPU 는 여유로운데 DB 커넥션이 상한에 붙음 → 커넥션 풀이 병목. 응답을 기다리며 줄 서는 중.
  • CPU·DB 다 여유로운데 느림 → 외부 API 호출이나 잠금(lock) 대기 의심.

즉 부하 도구는 “증상”을, 서버 모니터링은 “원인”을 알려줍니다. 둘을 같은 시간축에 겹쳐 보는 게 병목 찾기의 출발점이에요.



2. 무엇을 볼까 — 네 신호와 USE

1편에서 말한 네 골든 시그널(지연·트래픽·에러·포화도)을 서버 자원에 적용하면, 각 자원마다 무엇을 볼지가 정해져요. 이걸 체계화한 게 USE 방법론입니다. 자원마다 사용률(Utilization)·포화도(Saturation)·에러(Errors) 세 가지를 보라는 거예요.

자원 사용률 포화도(대기) 에러
CPU 사용 % 실행 큐 길이
(load average)
메모리 사용 % 스와핑 발생 OOM 킬
디스크 I/O 사용 % I/O 대기 큐 읽기/쓰기 실패
네트워크 대역폭 % 재전송 드롭된 패킷
DB 커넥션 사용 중 커넥션 % 대기 중인 요청 획득 타임아웃

여기서 초보가 자주 놓치는 게 포화도(대기) 예요. CPU 사용률이 70% 라고 여유로워 보여도, 실행 큐에 작업이 길게 줄 서 있으면 이미 병목입니다. 사용률만 보지 말고 “얼마나 줄 서 있나(대기)” 를 꼭 같이 보세요. 대기가 생기는 순간이 곧 응답 시간이 튀는 순간이에요.

이 지표들은 보통 Prometheus 로 수집해서 Grafana 대시보드로 봅니다. k6 결과까지 같은 Grafana 로 흘려보내면, 부하 곡선과 서버 자원을 한 화면에서 겹쳐 볼 수 있어요. 이게 스트레스 테스트의 이상적인 관측 구성입니다.



3. DB 병목 — 가장 흔한 범인

부하를 걸었을 때 병목의 상당수는 데이터베이스에서 나옵니다. 애플리케이션 서버는 늘리기 쉽지만, DB 는 보통 한 대라서 여기가 먼저 막혀요. 대표적인 세 가지를 정리합니다.

느린 쿼리 (Slow Query)

인덱스가 없어서 테이블을 통째로 훑는 쿼리가 대표적이에요. 데이터가 적을 땐 안 보이다가, 운영 규모 데이터에서 갑자기 몇 초씩 걸립니다. 대부분의 DB 는 느린 쿼리를 자동으로 기록하는 기능이 있어요. MySQL 이면 이렇게 켭니다.

-- 1초 넘게 걸린 쿼리를 로그로 기록
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

그리고 의심되는 쿼리 앞에 EXPLAIN 을 붙이면 DB 가 어떻게 실행하는지 보여줘요.

EXPLAIN SELECT * FROM orders WHERE user_id = 42;
type: ALL      ← 'ALL' 은 풀 테이블 스캔. 인덱스를 안 탐 (위험 신호)
rows: 1840000  ← 184만 행을 전부 훑는 중

type: ALL 과 큰 rows 값이 보이면 인덱스가 필요하다는 뜻이에요. user_id 에 인덱스를 걸면 이 쿼리는 순식간에 빨라집니다.

N+1 쿼리

목록 하나를 가져오려고 쿼리를 수백 번 날리는 문제예요. 게시글 20개를 가져오면서 각 글의 작성자를 따로 조회하면, 1(목록) + 20(작성자) = 21번의 쿼리가 나갑니다. 목록이 100개면 101번이에요. 한 요청에서 나가는 쿼리 개수를 세어보면 금방 드러나요. ORM 을 쓴다면 이 문제가 특히 잘 숨어 있어서, 부하 테스트 중 DB 쿼리 수를 꼭 확인하세요.

잠금 경합 (Lock Contention)

같은 행을 여러 요청이 동시에 수정하려 하면 줄을 서서 기다립니다. 재고 차감, 좋아요 수 증가처럼 인기 있는 한 행에 쓰기가 몰릴 때 터져요. 부하를 걸었을 때 특정 요청만 유독 느리고 DB 에 잠금 대기가 쌓인다면 이걸 의심합니다.

⚠️ DB 병목은 운영 규모의 데이터에서만 드러납니다. 1편에서 “텅 빈 DB 는 무조건 빠르다”고 한 이유가 이거예요. 테스트 DB 에 운영과 비슷한 양의 데이터를 채워두지 않으면, 느린 쿼리도 N+1 도 인덱스 부재도 전혀 안 보이다가 오픈 후에 터집니다.



4. 커넥션 풀 고갈

이건 소크 테스트(2편)에서 특히 잘 터지는 문제라 따로 짚을게요. 애플리케이션은 DB 커넥션을 매번 새로 만들지 않고 풀(pool) 에 미리 만들어두고 빌려 씁니다. 문제는 이 풀의 크기가 정해져 있다는 거예요.

풀이 20개인데 동시에 30개의 요청이 DB 를 쓰려 하면, 10개는 커넥션이 반납되기를 기다립니다. 이때 나타나는 증상이 특이해요. CPU·메모리는 멀쩡한데 응답 시간만 치솟습니다. 자원은 놀고 있는데 다들 커넥션을 기다리며 줄 서 있는 거예요.

체크할 지점은 이렇습니다.

  • 풀 크기가 적정한가 — 너무 작으면 대기가 생기고, 너무 크면 DB 가 감당을 못 해요. DB 의 최대 커넥션 수와 함께 봐야 합니다.
  • 커넥션을 제때 반납하나 — 쿼리 후 커넥션을 안 돌려주는 버그가 있으면, 부하가 일정해도 풀이 서서히 고갈돼요. 소크 테스트에서 “사용 중 커넥션”이 우상향하면 이걸 의심합니다.
  • 획득 타임아웃이 걸려 있나 — 커넥션을 무한정 기다리면 요청이 쌓여 서버가 통째로 멈춰요. “N초 기다려도 못 얻으면 실패” 설정이 있어야 합니다.

🚨 커넥션 풀 고갈은 오픈 직후엔 멀쩡하다가 트래픽이 조금만 늘면 갑자기 전체가 먹통이 되는, 아주 흔하고 무서운 사고예요. 부하 테스트를 걸며 “사용 중 커넥션 수”를 반드시 그래프로 지켜보세요.



5. APM — 요청 하나를 시간으로 분해

지금까지가 자원별로 보는 방식이라면, APM(Application Performance Monitoring) 은 반대로 요청 하나가 어디서 시간을 썼는지를 분해해서 보여줘요. 느린 요청 하나를 클릭하면 이런 식으로 쪼개집니다.

요청: GET /products/42          총 1,240 ms
├─ 라우팅·미들웨어                    30 ms
├─ DB 쿼리 (상품 조회)               180 ms
├─ DB 쿼리 (리뷰 목록 N+1 ×50)       820 ms   ← 여기가 범인
├─ 외부 API (배송비 계산)             160 ms
└─ 템플릿 렌더링                      50 ms

이 그림 한 장이면 “1.2초 중 0.8초가 리뷰 조회의 N+1 때문”이라는 게 바로 보여요. 자원 그래프로 “DB 가 병목”까지 좁혔다면, APM 으로 “어느 요청의 어느 쿼리”까지 정확히 짚는 거예요. Datadog·New Relic 같은 상용 도구나, OpenTelemetry 로 직접 구축하는 방법이 있습니다.

💡 병목을 좁혀가는 순서를 정리하면 이래요. 부하 도구(어디서 느려지나) → 자원 모니터링(어느 자원이 병목) → APM(어느 요청·쿼리) → EXPLAIN(왜 그 쿼리가 느린가). 위에서 아래로 내려가며 범위를 좁히면 헤매지 않아요.



6. 정리

  • 부하 테스트는 서버 내부 모니터링과 동시에 — 증상과 원인을 겹쳐 본다
  • 자원마다 사용률·포화도(대기)·에러(USE) — 특히 대기를 놓치지 말 것
  • DB 병목 3종 — 느린 쿼리(EXPLAIN)·N+1·잠금 경합, 운영 규모 데이터에서만 드러남
  • 커넥션 풀 고갈 — CPU 는 멀쩡한데 응답만 튀면 이걸 의심
  • APM 으로 요청 하나를 시간 분해해 정확한 범인을 짚는다

여기까지가 “정상 부하에서 어디가 느린가”를 찾는 과정이었어요. 그런데 진짜 무서운 건 뭔가 고장 났을 때 입니다. DB 가 잠깐 죽거나, 외부 API 가 느려지거나, 서버 한 대가 빠졌을 때 홈페이지가 통째로 무너지지 않고 버티는지 — 다음 편에서는 일부러 장애를 주입하는 카오스 엔지니어링으로 복원력을 시험할게요.

일단 오늘은 여기까지….. 다음 글에서는 카오스 엔지니어링으로 복원력을 시험하는 법을 정리해볼게요.


← 이전 글: (3/7) 홈페이지 스트레스 테스트 — 프론트엔드 성능과 코어 웹 바이탈다음 글 →: (5/7) 홈페이지 스트레스 테스트 — 복원력과 카오스 엔지니어링