2단계. v1의 부하테스트를 통한 한계점 파악 - 100-hours-a-week/KTB4-17th-wiki GitHub Wiki

1. 부하테스트 도입

1-1. 무엇을 테스트할 것인가

결론: 사용자 시나리오 중심 테스트

부하테스트에는 DB의 연결과 CUD에 대한 검증이나 외부 서비스에 대한 연동, DB 연결과 조회에 대한 검증 등 여러 목적의 테스트가 존재합니다. 이번 v1배포에서는 개발 기간에 비해 볼륨이 전체의 70%로 api의 종류가 많기 때문에 세부적인 성능보다는 일단 사용자의 시나리오대로 잘 동작하는 것이 가장 중요하다고 판단했습니다. 따라서 설계에서 세웠던 목표에 따라서 사용자 시나리오를 작성해 사용자가 사용할때 성능이 잘 나오는지 판단했습니다.

1-2 도구와 환경 선택

  • 부하 도구: k6
  • 모니터링 도구: granfana, Prometheus, Loki
  • 테스트용 별도의 서버 구성하고 dev.star23sharp.com으로 연결
  • 테스트용 DB에 테스트 데이터 구성 : 1775명

도구는 k6를 선택했습니다. 시나리오를 JS로 작성할 수 있어서 비교적 직관적인 문법을 가지고 있었습니다. python이나 java에 특화된 도구들도 있지만 사용성을 먼저 생각했습니다. 무엇보다 초당 몇 건처럼 목표 RPS를 직접 지정하는 실행 방식을 지원해서 설계 RPS를 그대로 테스트할 수 있다는 점이 좋았습니다. 다만 k6만으로는 결과를 그래프로 보기가 불편해 설계에는 없었지만 시각화를 위해 Grafana로 모니터링을 추가했습니다.

1-3 부하테스트를 진행시 추가 비용 발생 여부

서비스에 따라서 요청마다 비용이 부과되기도 하기 때문에 비용에 대해서 추가적으로 고려했습니다.

항목 무료 범위 과금 단위
CloudFront 요청 월 1000만 건 요청 수만큼 과금
CloudFront 전송 월 1TB GB당 과금
EC2 에서 인터넷으로 나가는 비용 모든 서비스 합산 월 100GB GB당 과금
EC2 들어오는 요청(인바운드) X X

3-7. 결론

평균 운영 부하와 API별 피크 부하에서는 목표를 달성했습니다. 에러 없이 빠르게 응답했고, 서버 자원도 여유가 충분했습니다.

다만 피크(33 RPS) 혼합 부하는 1차 실행이 중단됐고 2차 결과도 다시 확인해야 해서, 최대 처리량은 아직 확정하지 못했습니다. 다음 단계는 Stress 중단 원인 확인과 결과 재정리입니다.

# 1. 부하테스트 도입

1-1. 무엇을 테스트할 것인가

결론: 사용자 시나리오 중심 테스트

부하테스트에는 DB의 연결과 CUD에 대한 검증이나 외부 서비스에 대한 연동, DB 연결과 조회에 대한 검증 등 여러 목적의 테스트가 존재합니다. 이번 v1배포에서는 개발 기간에 비해 볼륨이 전체의 70%로 api의 종류가 많기 때문에 세부적인 성능보다는 일단 사용자의 시나리오대로 잘 동작하는 것이 가장 중요하다고 판단했습니다. 따라서 설계에서 세웠던 목표에 따라서 사용자 시나리오를 작성해 사용자가 사용할때 성능이 잘 나오는지 판단했습니다.

1-2 도구와 환경 선택

  • 부하 도구: k6
  • 모니터링 도구: granfana, Prometheus, Loki
  • 테스트용 별도의 서버 구성하고 dev.star23sharp.com으로 연결
  • 테스트용 DB에 테스트 데이터 구성 : 1775명

도구는 k6를 선택했습니다. 시나리오를 JS로 작성할 수 있어서 비교적 직관적인 문법을 가지고 있었습니다. python이나 java에 특화된 도구들도 있지만 사용성을 먼저 생각했습니다. 무엇보다 초당 몇 건처럼 목표 RPS를 직접 지정하는 실행 방식을 지원해서 설계 RPS를 그대로 테스트할 수 있다는 점이 좋았습니다. 다만 k6만으로는 결과를 그래프로 보기가 불편해 설계에는 없었지만 시각화를 위해 Grafana로 모니터링을 추가했습니다.

1-3 부하테스트를 진행시 추가 비용 발생 여부

서비스에 따라서 요청마다 비용이 부과되기도 하기 때문에 비용에 대해서 추가적으로 고려했습니다.

항목 무료 범위 과금 단위
CloudFront 요청 월 1000만 건 요청 수만큼 과금
CloudFront 전송 월 1TB GB당 과금
EC2 에서 인터넷으로 나가는 비용 모든 서비스 합산 월 100GB GB당 과금
EC2 들어오는 요청(인바운드) X X

1-4. AI API 호출 우회 방법

AI 기능은 외부 LLM을 호출하기 때문에 부하테스트 중에 실제로 호출하면 비용이 발생해 우회 방법을 고려했습니다.

  • Mock 서버: AI 서버가 호출하는 LLM 주소를 Mock 서버(로컬 :8090)로 바꾸는 방법입니다. Mock은 실제와 비슷하게 3~6초 뒤에 응답하고 스트리밍도 흉내 냅니다. 응답을 기다리는 동안 연결, 스레드, 메모리를 붙잡고 있는 모습까지 재현되기 때문에 부하 측정에는 이 방법이 맞다고 판단했습니다.
  • 환경변수로 고정 응답: LLM을 호출하지 않고 즉시 정해진 응답을 돌려주는 방법입니다. 기능 흐름을 확인하기에는 편하지만 기다리는 시간이 없어서 실제보다 훨씬 가볍게 측정됩니다. 그래서 기능 확인용으로만 쓰기로 했습니다.

1-5 로그인 우회 방법

  • 브라우저에서 토큰 복사하기 (개발자 도구에서 직접하는 방법)
    • 구현이 간단하고 실제 로그인 과정을 통해 발급된 토큰을 사용할 수 있다는 장점이 있지만 토큰이 만료될 때마다 사람이 다시 로그인하여 토큰을 복사해야 한다. VUser를 1명으로 놓고 api를 여러번 호출하는 방식은 문제가 없겠지만 여러 사용자를 가정하는 경우 VUser별로 토큰을 준비해야 하므로 VUser 수가 많아질수록 계정 및 토큰 관리가 어려워져서 해당 방법은 사용하지 않았다
  • JWT secret으로 토큰에 직접 서명. (채택)
    • 테스트 회원이 이미 들어가 있기 때문에 id 구간만 지정하면 수만 개의 토큰을 한 번에 만들 수 있어 VUser 수가 많아져도 테스트가 가능하다. 백엔드에 테스트용 발급 API를 추가하거나 재배포할 필요가 없어서 로컬에서 바로 실행할 수 있다.
  • 실행 방식 테스트
    1. 세션에 jwt토큰 등록: export jwt = 입력
    2. 토큰 생성 스크립트 실행: user id n개 생성 (테스트 회원과 동일해야함)
    3. k6로 스트립트 실행: 스크립트 내에서 vuser에게 토큰 분배하고 테스트 진행함.

실제 로그인 우회 방식이 잘 동작하는지 확인하기 위한 세미테스트 진행.

연결 테스트: vuser 10명 30초동안 onboarding에 특정api 대해서만 테스트함.


2. 부하테스트 진행

2-1. Load 테스트

목적: 평균 운영 부하에서 응답시간과 실패율이 존재하는지.

  • 목표 RPS: 6.2
  • 시간: 30분

고정되는 값

  1. 사용자의 하루 평균 사용 시간이 10분 (600초)
  2. 부하테스트 실행시 api 호출 비율
    1. 유저 조회 2회
    2. 유저 프로필 상세 조회 15회
    3. 좋아요 12회
    4. 채팅방 생성 4회
    5. 채팅 api 15회
  3. 사용자 한명이 10분동안 호출하는 api갯수 = 44회

변경되는 값

2-2. Stress 테스트

목적: 어느 단계부터 성능이 저하되었다 회복되는지 (최대 처리량)

  • 목표 RPS: 33
  • 시간: 30분

고정되는 값

  1. 사용자의 하루 평균 사용 시간이 10분 (600초)
  2. 부하테스트 실행시 api 호출 비율
    1. 유저 조회 2회
    2. 유저 프로필 상세 조회 15회
    3. 좋아요 12회
    4. 채팅방 생성 4회
    5. 채팅 api 15회
  3. 사용자 한명이 10분동안 호출하는 api갯수 = 44회

변경되는 값

  1. avg_user = 225

원하는 출력 최고 부하일때 피크 상태는 33 RPS인데 해당 값을 만족하려면 avg_user = 225가 되어야 합니다.

avg_user / 600(초) * 44 * 2 = 33 RPS

시나리오

구간 부하(평균 대비) 목표 RPS 시간
1 50% 8.3 0~6분
2 100% 16.7 6~12분
3 150% 25 12~18분
4 200% 33 18~24분
회복 관찰 50% 8.3 24~28분
램프다운 0 - 28~30분

Stress 테스트 결과

실행중 오류

stress ✗ [=================>--------------------] 024/225 VUs  14m21.9s/30m0s  0.56 iters/s
ERRO[0862] thresholds on metrics 'http_req_failed{kind:write}' were crossed; at least one has abortOnFail enabled, stopping test prematurely

중단 이유는 로그 보고 찾아야함. 변경된 값은 없는데 다시 실행하니까 성공 했음.(찾아보기)

2-3. 주요 API 단독 테스트

목적: 한 번에 API 하나만 정한 RPS로 반복 호출. 각 실행에서 해당 API의 성공률, p95, p99, p99.5 기준시간 이내 성공률을 기록합니다. API별 결과를 비교해 SLO 후보를 정합

  • 시간: 5분

API별 피크 목표

API 묶음 피크 목표(분당 요청 수) 5분 전체 요청 수
추천 조회 합계 (15/44) 689회/분 3,445
메시지 전송 (15/44) 689회/분 3,445
좋아요 (12/44) 551회/분 2,755
채팅방 목록 (4/44) 184회/분 920
온보딩 조회 (2/44) 92회/분 460

공통 실행 조건

export DURATION=5m LATENCY_LIMIT_MS=1000 PRE_VUS=10 MAX_VUS=20 ACCOUNT_INDEX=0

3. 부하테스트 결과

  1. 평균 운영 부하에서는 실패 없이 빠르게 응답했고 서버 자원도 여유가 충분합니다.
  2. Stress 1차 실행이 쓰기 요청 실패로 중단됐고 원인은 아직 확인하지 못했습니다.
  3. 조회 API 3개에서 일부 요청만 느린 현상이 있습니다.

3-2. Load 테스트 결과

설정

항목 설계 실제
VUser 85명 88명
시간 30분 39분 24초
목표 RPS 6.2 최대 6.97

결과

지표 결과
요청 수 10,848건 (설계 11,160건의 97%)
에러율 0%
응답시간 평균 27ms / Median 22ms / p95 41ms / 최대 525ms
p99 순간 최고점 1.6초 (03:09~03:11)
image

서버 자원

자원 사용률 포화(대기) 판정
CPU 최대 8.08% - 여유
DB 커넥션 20개 중 최대 2개 (10%) 대기 0건 여유
image image

해석

  • 설계한 부하가 정상적으로 들어갔고, 에러 없이 빠르게 응답했습니다.
  • CPU와 DB 커넥션 모두 10% 정도만 사용해 여유가 큽니다.
  • 03:09~03:11에 p99가 1.6초까지 잠깐 올랐다가 회복했습니다. 일시적인 현상이지만 원인(GC, 느린 쿼리)은 확인이 필요합니다.

3-3. Stress 테스트 결과

설정

항목 설계
VUser 최대 225명
시간 30분
목표 RPS 8.3 → 16.7 → 25 → 33 → 회복 8.3
지표 결과
요청 수 10,848건
에러율 0%
응답시간 평균 27ms / p95 41ms / p99 140ms / 최대 525ms
p99 순간 최고점 367ms (05:36:45, 같은 시점 p95 33ms)

요청 유형별 응답시간

유형 평균 p95 p99 최대
Read 22ms 31ms 119ms 335ms
Write 28ms 39ms 105ms 352ms
Heavy 82ms 149ms 164ms 525ms
image image

서버 자원

자원 사용률 포화(대기) 판정
CPU 최대 10.3% - 여유
메모리 63.6% - 안정
디스크 69.1% - 주의
DB 커넥션 10개 중 최대 3개 (30%) 대기 0건 여유
image image image image

해석

  • 모든 요청 유형이 기준(p95 500ms)을 통과했습니다.
  • Heavy 요청이 다른 요청보다 3~5배 느립니다. 부하가 커지면 가장 먼저 느려질 요청입니다.
  • 05:36:45에 p99만 크게 튀었습니다. 소수 요청만 느린 꼬리 지연입니다.
  • 목표인 33 RPS에 도달했는지는 결과 파일을 다시 확인한 뒤 판단해야 합니다.

3-4. 주요 API 단독 테스트 결과

설정

항목 내용
방식 API 1개씩 목표 RPS로 고정 호출
시간 API당 5분
기준시간 1초 (LATENCY_LIMIT_MS=1000)

테스트 대상

기능 Endpoint
추천 활성 배치 조회 GET /api/v1/recommendation-batches/active
온보딩 상태 조회 GET /api/v1/users/me/onboarding
채팅방 목록 조회 GET /api/v1/chat-rooms
좋아요 전송 POST /api/v1/likes
메시지 전송 POST /api/v1/chat-rooms/{chatRoomId}/messages
홈 화면 페이지네이션 (DB 20개 조회) GET /api/v1/recommendation-batches/{batchId}/items

결과: 목표 대비 처리량

기능 실행 일시 목표 RPS 실제 RPS 요청 수 성공률
추천 활성 배치 조회 10-02 13:15~13:20 11.48 11.49 3,446 100%
온보딩 상태 조회 10-02 13:21~13:26 1.53 1.54 461 100%
채팅방 목록 조회 10-02 13:26~13:31 3.07 3.07 921 100%
좋아요 전송 10-03 21:19~21:24 9.20 9.20 2,760 100%
메시지 전송 10-03 21:24~21:29 11.48 11.48 3,445 100%
홈 화면 페이지네이션 10-03 21:13:19~21:18:19 11.48 11.48 3,445 100%

결과: 응답시간

기능 평균 Median p95 p99 p99.5 최대
추천 활성 배치 조회 32.03ms 18.65ms 102.68ms 132.81ms 150.78ms 221.04ms
온보딩 상태 조회 33.48ms 20.04ms 107.91ms 133.87ms 162.99ms 184.97ms
채팅방 목록 조회 39.79ms 26.40ms 109.35ms 129.83ms 150.02ms 248.58ms
좋아요 전송 33.70ms 32.92ms 37.79ms 52.22ms 69.25ms 116.00ms
메시지 전송 21.94ms 21.06ms 27.34ms 38.77ms 50.56ms 247.88ms
홈 화면 페이지네이션 41.84ms 41.05ms 46.74ms 61.48ms 70.23ms 129.63ms

[사진: API별 테스트 결과 그래프]

해석

  • 6개 API 모두 목표 RPS에 도달했고, 실패 0건, Dropped 0건, 1초 이내 응답 비율 100%입니다.
  • 좋아요, 메시지, 홈 화면 페이지네이션은 응답시간이 고르게 빠릅니다.
  • 추천 활성 배치, 온보딩, 채팅방 목록은 대부분 20ms대지만 일부 요청이 100ms 이상 걸립니다. 세 API의 부하량이 7배 넘게 차이 나는데도 같은 패턴이라, 공통 원인(인증, 사용자 조회, 캐시 등)이 있을 가능성이 높습니다.
  • 단독 실행에서는 쓰기 실패가 없었습니다. 그래서 Stress 1차 중단은 여러 API가 동시에 호출될 때 생긴 문제로 볼 수 있습니다.

SLO 후보

측정값에 약 2배 여유를 둔 값입니다.

대상 p95 p99 성공률
추천 활성 배치, 온보딩, 채팅방 목록 200ms 미만 300ms 미만 99.9% 이상
홈 화면 페이지네이션, 좋아요, 메시지 100ms 미만 150ms 미만 99.9% 이상

3-5. 개선 과제

우선순위 과제 할 일
1 Stress 1차 중단 원인 확인 실패한 쓰기 요청의 상태 코드와 에러 로그 확인
2 Stress 결과 재정리 결과 파일에서 구간별 RPS와 응답시간을 다시 뽑아 33 RPS 도달 여부 확인
3 조회 API 꼬리 지연 확인 같은 조건으로 재측정하고 GC와 쿼리 지표를 함께 확인
4 DB 커넥션 설정 통일 Load(20개)와 Stress(10개)의 Pool Size 맞추기
5 후속 테스트 Stress 재측정, Soak(장시간 유지), Spike(급격한 증가)

3-7. 결론

평균 운영 부하와 API별 피크 부하에서는 목표를 달성했습니다. 에러 없이 빠르게 응답했고, 서버 자원도 여유가 충분했습니다.

다만 피크(33 RPS) 혼합 부하는 1차 실행이 중단됐고 2차 결과도 다시 확인해야 해서, 최대 처리량은 아직 확정하지 못했습니다. 다음 단계는 Stress 중단 원인 확인과 결과 재정리입니다.

⚠️ **GitHub.com Fallback** ⚠️