M8 ‐ 부하 테스트 시나리오 설계 및 구현 - 100-hours-a-week/KTB4-4th-wiki GitHub Wiki

1. 테스트 목적

Need U V1은 단일 t3.medium EC2에서 Frontend, Backend, AI Server, Redis를 Docker Compose로 운영하고 있으며, MySQL과 PostgreSQL도 동일 EC2에서 사용한다.

현재 예상 사용 규모는 다음과 같다.

지표 예상 규모
MAU 약 1,000명
WAU 약 400~500명
DAU 약 80~140명
예상 Peak Traffic 약 0.16 HTTP RPS

이번 부하 테스트에서는 현재 V1이 예상 트래픽을 안정적으로 처리할 수 있는지 검증하고, 예상보다 트래픽이 증가했을 때 어느 구간부터 성능 저하가 나타나는지 확인한다.

구체적으로 다음을 확인한다.

  • 예상 Peak Traffic에서 Backend API가 목표 성능을 유지하는지
  • 예상 Peak보다 높은 트래픽에서도 어느 정도의 처리 여유가 있는지
  • API 종류에 따라 응답시간 차이가 발생하는지
  • 부하 증가에 따라 Backend, MySQL, EC2의 상태가 어떻게 변화하는지
  • 성능 저하가 발생할 경우 어떤 구성요소가 병목 후보인지

테스트 결과는 V1의 안정 처리 범위를 확인하고 향후 V2의 Scale-out, DB 분리, Instance 변경 등의 확장 여부를 판단하기 위한 근거로 활용한다.


2. SLI/SLO와 부하 테스트

V1에서는 일반 Backend API에 대해 다음 SLI/SLO를 정의하였다.

SLI SLO
Backend API 요청 성공률 ≥ 99.0%
Backend API 응답시간 p95 ≤ 300ms
Backend API 응답시간 p99 ≤ 1,000ms

이번 부하 테스트에서도 동일하게 성공률, p95, p99를 핵심 성능 지표로 사용한다.

각 부하 단계에서 기존 SLO 수준의 성능을 유지할 수 있는지를 확인한다.

단, SLO는 28일 Rolling Window의 장기적인 서비스 목표이고 부하 테스트는 짧은 시간 동안 특정 부하를 발생시키는 실험이므로, 테스트 결과 자체를 28일 SLO 달성 여부로 해석하지 않는다.

현재 V1에 특정 수준의 트래픽이 발생했을 때 Backend API가 정의한 SLO 수준의 성능을 유지할 수 있는가?


3. 테스트 범위

이번 부하 테스트는 개발자 PC에서 k6를 실행하여 실제 운영 서버에 부하를 발생시키는 방식으로 진행한다.

3.1 테스트 실행 구조

k6에서 실제 운영 URL인 https://needu.gift로 요청을 발생시킨다.

개발자 PC
   │
   │ k6
   │ HTTPS Request
   ▼
https://needu.gift
   │
   ▼
Nginx :443
   │
   ▼
Spring Boot :8080
   │
   ▼
MySQL :3306

Backend 컨테이너를 직접 호출하지 않고 실제 서비스 URL을 사용하므로, 실제 사용자 요청과 동일하게 HTTPS와 Nginx를 거쳐 Backend에 도달한다.

3.2 Backend + MySQL을 테스트하는 이유

AI 기능은 다음과 같이 여러 구성요소에 의존한다.

Spring Boot
 ↓
FastAPI
 ↓
PostgreSQL / Redis
 ↓
External AI API

AI API를 부하 테스트하면 External AI API의 응답시간, Rate Limit, 비용 등의 영향을 함께 받게 되어 내부 Backend 성능과 외부 요인의 영향을 분리하기 어렵다.

따라서 V1에서는 외부 시스템의 영향을 배제하고 독립적으로 부하를 발생시킬 수 있는 Spring Boot + MySQL 조회 경로를 검증한다.

Frontend, FastAPI, PostgreSQL, Redis, External AI API에는 별도의 부하를 발생시키지 않는다.


4. 테스트 API 선정

사용자가 실제로 사용하는 친구 조회와 추천 결과 조회 기능을 중심으로 테스트한다.

서비스 기능 API 선정 이유
친구 목록 확인 GET /api/v1/friends?size=20 친구를 선택하기 전에 호출되는 기본 조회 기능
친구 정보 확인 GET /api/v1/friends/{userId} 특정 친구의 정보를 확인할 때 호출
나를 위한 추천 확인 GET /api/v1/users/me/personal-recommendations?size=20 개인화 상품 추천 결과 조회
친구 선물 추천 확인 GET /api/v1/users/{userId}/gift-recommendations?size=20 친구에게 줄 선물을 탐색하는 핵심 서비스 흐름

4.1 친구 관련 API

친구에게 줄 선물을 확인하기 위한 기본 흐름이다.

친구 목록 확인
      ↓
친구 선택
      ↓
친구 정보 확인

GET /api/v1/friends는 친구 목록을 조회하고, GET /api/v1/friends/{userId}는 선택한 친구의 정보를 조회한다.

4.2 개인 추천 API

GET /api/v1/users/me/personal-recommendations는 사용자를 위해 미리 생성된 개인화 상품 추천 결과를 조회한다.

테스트 계정마다 60개의 Personal Recommendation을 준비하여 빈 응답만 빠르게 반환하는 테스트가 되지 않도록 한다.

4.3 친구 선물 추천 API

GET /api/v1/users/{userId}/gift-recommendations는 친구에게 적합한 선물 추천 결과를 조회한다.

친구 목록
   ↓
친구 선택
   ↓
친구 상세
   ↓
친구 선물 추천

테스트 계정마다 19명의 친구와 60개의 Gift Recommendation을 준비한다.

4.4 AI 대화 API 제외

AI 대화 기능은 Backend뿐 아니라 FastAPI, PostgreSQL, Redis 및 External AI API와 연결된다.

따라서 이번 테스트에서는 이미 생성된 추천 결과를 조회하는 서비스 흐름만 대상으로 하고 AI 생성 과정 자체는 제외한다.


5. 테스트 도구 선정

부하 테스트 도구로 Grafana k6를 사용한다.

5.1 k6 선정 이유

이번 테스트에서는 다음과 같이 목표 요청량을 단계적으로 증가시킨다.

0.16 → 0.32 → 0.80 → 1.60 → 3.20 HTTP RPS

k6의 constant-arrival-rate Executor를 사용하면 목표 실행률을 기준으로 일정한 부하를 발생시킬 수 있다.

또한 다음 기능을 사용할 수 있다.

  • HTTP API 기반 부하 테스트
  • p50 / p95 / p99 측정
  • HTTP 요청 실패율 측정
  • Threshold를 이용한 SLO 기준 검증
  • Tag를 이용한 API별 성능 측정
  • JavaScript 기반 시나리오 관리
  • Git을 통한 테스트 코드 관리
  • 향후 CI/CD 연동 가능

특히 V1 SLI/SLO에서 사용하는 성공률, p95, p99를 동일하게 측정할 수 있다는 점을 기준으로 k6를 사용한다.


6. 테스트 데이터 구성

실제 사용자 데이터에 부하가 집중되지 않도록 20개의 테스트 전용 계정을 사용한다.

각 계정에는 다음 데이터를 준비한다.

  • 다른 테스트 계정 19명과 친구 관계
  • Personal Recommendation 60개
  • Gift Recommendation 60개
  • 추천 결과와 연결된 실제 ACTIVE 상품

각 테스트 계정에는 별도의 Access Token을 발급한다.

Backend는 다음 Cookie에서 Access Token을 읽는다.

NEEDU_ACCESS_TOKEN=<access-token>

tokens.json에는 실제 인증 가능한 Token이 포함되므로 Git에는 포함하지 않는다.


7. 테스트 시나리오

7.1 Smoke Test

부하 테스트 전에 테스트 환경이 정상인지 확인한다.

확인 대상:

  • 친구 목록 조회
  • 친구 상세 조회
  • 개인 추천 결과 조회
  • 친구 선물 추천 결과 조회
  • Token 인증
  • 테스트 데이터 존재 여부
  • 401 / 403 / 404 / 429 발생 여부

Smoke Test는 성능 측정이 아니라 테스트 환경이 정상적으로 준비되었는지 확인하는 목적으로 수행한다.

7.2 API별 Load Test

다음 API를 각각 독립적으로 테스트한다.

GET /api/v1/friends?size=20

GET /api/v1/friends/{userId}

GET /api/v1/users/me/personal-recommendations?size=20

GET /api/v1/users/{userId}/gift-recommendations?size=20

동일한 HTTP RPS에서 API별 성공률과 응답시간을 비교하여 특정 기능이 상대적으로 먼저 느려지는지 확인한다.

7.3 User Flow Load Test

실제 서비스의 선물 추천 흐름을 테스트한다.

친구 목록 조회
      ↓
친구 선택
      ↓
친구 상세 조회
      ↓
친구 선물 추천 결과 조회

userFlow 1회 실행에서는 총 3개의 HTTP 요청이 발생한다.

개인 추천은 친구 선택과 무관한 독립 기능이므로 API별 테스트에서 별도로 측정한다.

7.4 User Flow RPS 계산

k6 constant-arrival-rate의 rate는 HTTP 요청 수가 아니라 Iteration 실행 횟수이다.

User Flow 한 번당 HTTP 요청이 3개이므로 다음과 같이 계산한다.

HTTP RPS
= Flow 실행 횟수 × Flow당 HTTP 요청 수 ÷ 테스트 시간(초)
단계 Flow / 10분 Flow당 요청 총 HTTP 요청 HTTP RPS
Baseline 32 3 96 0.16
Load 1 64 3 192 0.32
Load 2 160 3 480 0.80
Load 3 320 3 960 1.60
Load 4 640 3 1,920 3.20

8. 테스트 코드 구현

8.1 테스트 계정

SharedArray를 이용하여 tokens.json의 테스트 계정을 공유한다.

테스트 요청은 여러 테스트 계정을 순환하며 실행하여 특정 계정에 요청이 집중되지 않도록 한다.

8.2 Endpoint Tag

각 API 요청에 다음 Tag를 설정한다.

endpoint=friends
endpoint=friend-detail
endpoint=personal-recommendations
endpoint=gift-recommendations

이를 통해 API별 http_req_duration, http_req_failed를 분리하여 확인한다.

8.3 Threshold

기존 Backend SLO를 기준으로 Threshold를 설정한다.

export const thresholds = {
  http_req_failed: ['rate<=0.01'],
  http_req_duration: [
    'p(95)<=300',
    'p(99)<=1000',
  ],

  'http_req_failed{endpoint:friends}': ['rate<=0.01'],
  'http_req_duration{endpoint:friends}': [
    'p(95)<=300',
    'p(99)<=1000',
  ],

  'http_req_failed{endpoint:friend-detail}': ['rate<=0.01'],
  'http_req_duration{endpoint:friend-detail}': [
    'p(95)<=300',
    'p(99)<=1000',
  ],

  'http_req_failed{endpoint:personal-recommendations}': ['rate<=0.01'],
  'http_req_duration{endpoint:personal-recommendations}': [
    'p(95)<=300',
    'p(99)<=1000',
  ],

  'http_req_failed{endpoint:gift-recommendations}': ['rate<=0.01'],
  'http_req_duration{endpoint:gift-recommendations}': [
    'p(95)<=300',
    'p(99)<=1000',
  ],
};

Threshold 통과 여부만으로 병목을 판단하지 않고 시스템 지표와 함께 분석한다.


9. 테스트 수행 순서

테스트 데이터 준비
        ↓
Smoke Test
        ↓
Baseline Test
        ↓
API별 Load Test
        ↓
User Flow Load Test
        ↓
Stress Test
        ↓
병목 분석

10. Load Test 단계

V1 예상 Peak Traffic인 0.16 HTTP RPS를 기준으로 부하를 단계적으로 증가시킨다.

단계 목표 HTTP RPS Peak 대비 지속시간
Baseline 0.16 1× 10분
Load 1 0.32 2× 10분
Load 2 0.80 5× 10분
Load 3 1.60 10× 10분
Load 4 3.20 20× 10분

10.1 Baseline

0.16 HTTP RPS에서 SLO 수준의 성능을 유지하는지 확인한다.

Baseline 결과는 이후 높은 부하 단계와 비교하기 위한 기준으로 사용한다.

10.2 부하 증가

현재 예상 트래픽만 테스트하면 현재 규모에서 정상 동작하는지는 확인할 수 있지만 처리 여유는 확인할 수 없다.

따라서 Peak 대비 2배, 5배, 10배, 20배까지 부하를 증가시킨다.


11. Stress Test

기본 Load Test에서 3.20 HTTP RPS까지 안정적으로 처리한다면 추가 Stress Test를 수행한다.

목적은 장애를 발생시키는 것이 아니라 트래픽 증가 시 핵심 기능의 성능 저하가 시작되는 구간과 원인 후보를 확인하는 것이다.

다음 변화를 확인한다.

  • API별 p95 / p99 증가
  • HTTP 요청 실패율 증가
  • EC2 CPU / Memory 증가
  • HikariCP Connection 대기
  • MySQL 동시 처리량 증가
  • Slow Query 발생
  • CPU Credit 감소

12. 테스트 결과에서 확인할 내용

12.1 예상 Peak에서 핵심 기능이 정상적으로 제공되는가?

0.16 HTTP RPS에서 친구 조회, 개인 추천, 친구 선물 추천 기능이 SLO 수준을 유지하는지 확인한다.

12.2 어떤 기능이 먼저 느려지는가?

친구 조회와 추천 조회의 p95/p99를 비교하여 특정 기능에서 먼저 성능 저하가 발생하는지 확인한다.

12.3 실제 선물 추천 흐름이 유지되는가?

친구 목록
   ↓
친구 상세
   ↓
친구 선물 추천

연속적인 사용자 흐름이 높은 부하에서도 정상적으로 완료되는지 확인한다.

12.4 예상 트래픽 대비 어느 정도의 여유가 있는가?

0.16 HTTP RPS
      ↓
부하 증가
      ↓
기능별 성능 비교
      ↓
성능 저하 구간 확인
      ↓
시스템 지표 분석
      ↓
병목 후보 확인

13. 관측 지표 선정

k6에서는 API의 성공률과 응답시간을 확인할 수 있지만 왜 느려졌는지는 알 수 없다.

따라서 실제 서비스 기능에서 발생하는 처리와 연결하여 필요한 시스템 지표를 확인한다.

서비스 기능 / 특성 확인 지표 확인 이유
친구 목록 / 상세 조회 API별 p95/p99, Slow Query 친구 관계와 사용자 조회 Query가 지연되는지 확인
개인 추천 조회 API별 p95/p99, Slow Query 추천 결과 및 상품 조회 Query가 지연되는지 확인
친구 선물 추천 조회 API별 p95/p99, Slow Query 사용자 확인, 추천 목록, 상품 및 가격 조회가 많은 기능이 먼저 느려지는지 확인
친구 / 추천 동시 요청 HikariCP Active / Pending DB Connection을 확보하지 못해 요청이 대기하는지 확인
여러 DB 조회 동시 실행 MySQL Connections, Threads_running MySQL의 동시 처리량이 증가하여 병목이 발생하는지 확인
단일 EC2에서 전체 서비스 운영 CPUUtilization, mem_used_percent 여러 구성요소가 공유하는 EC2 Resource가 한계에 접근하는지 확인
t3.medium 지속 부하 CPUCreditBalance CPU Credit 감소가 지속 부하 성능에 영향을 주는지 확인

13.1 API별 성공률 / p95 / p99

친구 조회와 추천 조회는 내부 처리 과정이 다르다.

따라서 Endpoint Tag를 이용해 API별 성공률과 p95/p99를 비교하여 어떤 사용자 기능에서 성능 저하가 먼저 발생하는지 확인한다.

13.2 MySQL Slow Query

친구 기능은 친구 관계와 사용자 정보를 조회하고, 추천 기능은 추천 결과와 상품 및 가격 정보를 조회한다.

특히 친구 선물 추천은 여러 DB 조회가 발생하므로 추천 API의 응답시간이 증가할 경우 Slow Query를 확인한다.

이를 통해 특정 조회 Query가 API 지연에 영향을 주는지 확인한다.

13.3 HikariCP Active / Pending

친구 및 추천 API는 모두 MySQL Connection을 사용한다.

동시 요청 증가로 HikariCP Pending Connection이 발생하면 API 요청이 DB Connection을 기다릴 수 있다.

따라서 API Latency와 HikariCP Active/Pending을 함께 확인하여 Connection Pool 대기가 병목인지 확인한다.

13.4 MySQL Connections / Threads_running

친구 및 추천 요청이 증가하면 MySQL에 전달되는 조회 요청도 증가한다.

  • Threads_connected: 현재 MySQL에 연결된 Connection 수 확인
  • Threads_running: MySQL에서 동시에 실행 중인 작업 확인

이를 통해 MySQL의 동시 처리량이 성능 저하와 관련되는지 확인한다.

13.5 EC2 CPU / Memory

V1은 하나의 EC2에서 다음 구성요소를 함께 운영한다.

Nginx
Frontend
Backend
AI Server
Redis
MySQL
PostgreSQL

따라서 CloudWatch의 CPUUtilization, mem_used_percent를 통해 부하 증가가 EC2 전체 Resource에 미치는 영향을 확인한다.

13.6 CloudWatch 인프라 지표

부하 증가가 단일 EC2의 Resource와 Nginx 요청 처리에 미치는 영향을 확인하기 위해 CloudWatch Dashboard에서 다음 지표를 함께 관측한다.

구분 CloudWatch 지표 확인 목적
CPU CPUUtilization 부하 증가에 따라 EC2 CPU 사용률이 증가하는지 확인
Memory mem_used_percent Backend, MySQL 등 동일 EC2에서 실행되는 프로세스로 인해 메모리 사용률이 증가하는지 확인
Disk disk_used_percent Root Disk 사용률을 확인하여 디스크 용량 부족 여부 확인
CPU Credit CPUCreditBalance t3.medium의 지속적인 CPU 사용으로 CPU Credit이 감소하는지 확인
Nginx 요청량 Nginx Requests 부하 테스트 중 실제 운영 서버로 유입되는 요청량 변화 확인
Nginx Client Error Nginx 4xx 인증 오류, Rate Limit의 429 등 Client Error 발생 여부 확인
Nginx Server Error Nginx 5xx 부하 증가 시 Backend 연결 실패 등 Server Error 발생 여부 확인
Service 상태 Service Health 부하 테스트 중 Backend, Frontend, AI 컨테이너의 Health 상태 변화 확인
HikariCP 사용 Connection hikaricp.connections.active Spring Actuator
HikariCP 대기 Thread hikaricp.connections.pending Spring Actuator

특히 V1은 하나의 t3.medium EC2에서 Nginx, Frontend, Backend, AI Server, Redis, MySQL, PostgreSQL을 함께 운영하므로 CPUUtilization과 mem_used_percent를 API Latency 변화와 함께 비교한다.

또한 t3.medium은 Burstable Instance이므로 지속 부하에서 CPUCreditBalance가 감소하는지 확인하고, CPU Credit 감소 시점과 CPUUtilization, API p95/p99 변화를 함께 비교한다.

Nginx 지표는 k6에서 발생시킨 요청이 실제 운영 서버에 유입되고 있는지 확인하고, 부하 증가에 따라 4xx/5xx 오류가 증가하는지 확인하는 용도로 사용한다.

특히 Stress Test에서 10 RPS에 접근할 경우 운영 Nginx의 Source IP 기준 Rate Limit에 의해 429 Too Many Requests가 발생할 수 있으므로, Backend의 처리 한계와 Nginx Rate Limit에 의한 실패를 구분하여 분석한다.


14. 테스트 제약사항

운영 Nginx에는 Source IP 기준 10 req/s Rate Limit이 적용되어 있다.

k6를 개발자 PC 한 대에서 실행하므로 여러 Access Token을 사용하더라도 Nginx에서는 동일한 Source IP로 인식한다.

따라서 기본 Load Test의 최대 3.20 HTTP RPS까지는 현재 설정 안에서 테스트할 수 있다.

Stress Test에서 10 RPS 이상으로 증가할 경우 Backend보다 먼저 Nginx Rate Limit에 도달할 수 있다.

이때 발생하는 429 Too Many Requests는 Backend/MySQL의 처리 한계가 아니라 Nginx Rate Limit에 의한 테스트 제약으로 구분한다.


15. 결과 기록

15.1 부하 단계별 결과

초기에는 예상 Peak Traffic인 0.16 HTTP RPS를 기준으로 2배, 5배, 10배, 20배까지 단계적으로 증가시키는 것을 계획하였다.

그러나 해당 부하 구간에서는 현재 V1의 성능 한계를 확인하기 어려워 실제 테스트에서는 부하를 100 → 200 → 250 → 300 RPS까지 증가시키며 SLO 충족 여부와 성능 저하 구간을 확인하였다.

목표 RPS 실제 RPS 성공률 평균 p95 p99 최대 VU Dropped Iterations 결과
100 99.94 100% 15.22ms 22.21ms 47.02ms 18 0 PASS
200 199.97 100% 28.15ms 70.23ms 238.30ms 78 0 PASS
250 249.72 99.99% 65.52ms 279.81ms 808.24ms 155 0 PASS
300 279.64 99.99% 439.19ms 1.96s 2.87s 438 193 FAIL

100 RPS

100 RPS에서는 실제 평균 99.94 RPS를 처리했으며 요청 실패와 Dropped Iteration이 발생하지 않았다.

  • 성공률: 100%
  • p95: 22.21ms
  • p99: 47.02ms
  • 최대 VU: 18
  • Dropped Iteration: 0

일반 Backend API의 SLO인 p95 ≤ 300ms, p99 ≤ 1,000ms를 충분한 여유를 두고 충족하였다.

200 RPS

200 RPS에서도 실제 평균 199.97 RPS를 처리했으며 모든 요청이 성공하였다.

  • 성공률: 100%
  • p95: 70.23ms
  • p99: 238.30ms
  • 최대 VU: 78
  • Dropped Iteration: 0

100 RPS 대비 응답시간은 증가했지만 여전히 SLO 기준보다 충분히 낮아 안정적으로 처리 가능한 구간으로 판단하였다.

250 RPS

250 RPS에서는 실제 평균 249.72 RPS를 처리하였다.

  • 성공률: 99.99%
  • 평균 응답시간: 65.52ms
  • p95: 279.81ms
  • p99: 808.24ms
  • Timeout: 8건
  • 최대 VU: 155
  • Dropped Iteration: 0

성공률과 지연시간 모두 SLO를 충족하였다.

그러나 p95가 279.81ms로 SLO 기준인 300ms에 근접하였으며, 200 RPS의 p95 70.23ms와 비교했을 때 응답시간이 크게 증가하였다.

따라서 250 RPS는 테스트에서 확인된 최대 SLO 충족 부하이지만, 충분한 성능 여유를 가진 안정적인 운영 한계로 보기는 어렵다.

300 RPS

300 RPS를 목표로 테스트하였으나 실제 평균 처리량은 약 279.64 RPS였다.

  • 성공률: 99.99%
  • 평균 응답시간: 439.19ms
  • p95: 1.96s
  • p99: 2.87s
  • Timeout: 6건
  • 최대 VU: 438
  • Dropped Iteration: 193

p95와 p99가 모두 SLO를 초과하였다.

응답시간이 증가하면서 각 VU가 요청 완료를 기다리는 시간이 길어졌고, 목표 요청률을 유지하기 위해 필요한 VU가 최대 438까지 증가하였다.

결국 193개의 Iteration을 예정된 시점에 시작하지 못해 목표한 300 RPS를 완전히 전달하지 못하였다.

다만 실제 약 280 RPS가 처리되는 상황에서 이미 p95 1.96s, p99 2.87s가 측정되었으므로, 성능 저하를 단순히 VU 부족으로만 설명할 수는 없다.


15.2 API별 결과

250 RPS 테스트에서 API별 p95를 확인한 결과 모든 API가 SLO 기준인 300ms 이하를 충족하였다.

API p95 SLO 결과
Friends 268.88ms ≤ 300ms PASS
Friend Detail 266.29ms ≤ 300ms PASS
Personal Recommendations 284.21ms ≤ 300ms PASS
Gift Recommendations 297.48ms ≤ 300ms PASS

API별로 비교하면 Gift Recommendations의 p95가 297.48ms로 가장 높았으며 SLO 기준인 300ms에 가장 근접하였다.

그다음으로 Personal Recommendations가 284.21ms로 높은 응답시간을 보였다.

따라서 250 RPS에서는 모든 API가 SLO를 충족했지만 Recommendation 관련 API가 상대적으로 먼저 SLO 경계에 접근하는 모습을 확인하였다.

현재 기록된 테스트 결과에서는 API별 p50, p99 및 성공률을 별도로 확보하지 않았기 때문에 해당 값은 비교에서 제외하였다.


15.3 Timeout 분석

250 RPS 테스트에서는 총 8건의 Timeout이 발생하였다.

k6 결과에서는 다음과 같이 확인되었다.

전체 요청: 74,980
정상 응답: 74,972
Timeout: 8

동일 시간대의 Nginx Access Log를 k6 User-Agent 기준으로 확인한 결과 완료된 요청은 총 74,972건이었으며 모두 HTTP 200이었다.

k6 정상 응답:          74,972
Nginx 완료 요청:       74,972
Nginx HTTP Status:     모두 200

따라서 8건의 실패는 Backend에서 HTTP 4xx 또는 5xx를 반환한 요청이 아니라, 요청 또는 응답 처리 과정에서 k6의 Timeout에 도달한 것으로 판단하였다.


15.4 병목 분석

250 RPS부터 응답시간이 급격하게 증가하기 시작했기 때문에 해당 구간의 시스템 지표를 추가로 확인하였다.

부하 구간 EC2 CPU EC2 Memory HikariCP Active HikariCP Pending MySQL Threads Connected MySQL Threads Running 분석
250 RPS 최대 약 77.787% 약 51% 최대 3 0 11 최대 약 2.4 명확한 자원 포화 없음

EC2

250 RPS에서 응답시간이 SLO 경계까지 증가했지만 EC2 CPU는 최대 약 77.787%, Memory는 약 51% 수준이었다.

따라서 EC2 전체 CPU 또는 Memory 부족을 직접적인 병목으로 판단하기 어렵다.

HikariCP

HikariCP Active Connection은 최대 3이었으며 Pending Connection은 0이었다.

DB Connection을 얻기 위해 대기하는 요청이 발생하지 않았기 때문에 Connection Pool 고갈 현상은 확인되지 않았다.

MySQL

MySQL Threads_connected는 11, Threads_running은 최대 약 2.4 수준이었다.

따라서 MySQL Connection 또는 실행 Thread에서도 명확한 포화 징후는 확인되지 않았다.

분석 결과

부하 테스트를 통해 성능 저하가 발생하는 구간은 확인했지만 정확한 병목 위치는 특정하지 못하였다.

100 RPS
p95 22.21ms
      │
      ▼
200 RPS
p95 70.23ms
      │
      ▼
250 RPS
p95 279.81ms
      │
      │ 성능 여유 급격히 감소
      ▼
약 280 RPS
p95 1.96s
      │
      ▼
SLO 초과

따라서 250~280 RPS 사이를 현재 V1의 성능 한계 구간으로 판단하였다.

다만 해당 구간에서도 CPU, Memory, HikariCP, MySQL에서 명확한 포화가 확인되지 않았기 때문에 현재 수집한 지표만으로 정확한 병목 원인을 특정하기 어렵다.

정확한 병목 위치를 확인하기 위해서는 향후 다음 지표를 추가로 수집할 필요가 있다.

  • Endpoint별 Server Response Time
  • Tomcat Active / Busy Thread
  • JVM Heap 사용량
  • GC Pause
  • DB Query Latency
  • Slow Query
  • Nginx $request_time
  • Nginx $upstream_response_time

16. 최종 분석

16.1 예상 Peak Traffic 처리 가능 여부

V1에서 예상한 Peak Traffic은 약 0.16 HTTP RPS이다.

실제 부하 테스트에서는 이보다 훨씬 높은 100 RPS와 200 RPS에서도 SLO를 충분한 여유를 두고 충족하였다.

100 RPS에서 측정된 결과는 다음과 같다.

실제 RPS: 99.94
성공률: 100%
p95: 22.21ms
p99: 47.02ms

따라서 현재 예상 Peak Traffic인 0.16 RPS는 V1에서 확인한 처리 범위보다 매우 낮으며, 현재 Traffic 수준에서는 충분한 성능 여유가 있는 것으로 판단한다.


16.2 Peak 대비 증가 Traffic 처리 가능 여부

초기 테스트 계획에서는 예상 Peak Traffic을 기준으로 다음과 같은 부하를 검증하려고 하였다.

구분 RPS
예상 Peak 0.16
2배 0.32
5배 0.80
10배 1.60
20배 3.20

그러나 실제 테스트에서는 최대 약 250 RPS까지 SLO를 충족하였다.

따라서 계획했던 Peak 대비 2배, 5배, 10배, 20배 수준은 현재 V1에서 확인된 성능 한계보다 매우 낮은 부하 구간이다.


16.3 먼저 성능 저하가 나타난 기능

250 RPS의 API별 p95는 다음과 같다.

Friend Detail             266.29ms
Friends                   268.88ms
Personal Recommendations  284.21ms
Gift Recommendations      297.48ms

Gift Recommendations의 p95가 297.48ms로 가장 먼저 SLO 기준인 300ms에 근접하였다.

따라서 테스트 대상 중 Recommendation 관련 API가 상대적으로 높은 지연시간을 보였으며, 특히 Gift Recommendations가 가장 먼저 SLO 경계에 접근하였다.

다만 이것만으로 해당 API가 전체 시스템의 병목 원인이라고 판단할 수는 없다.


16.4 성능 저하가 발생하는 부하 구간

테스트 결과 약 200 RPS까지는 SLO를 충분한 여유를 두고 충족하였다.

250 RPS에서는 SLO를 충족했지만 p95가 279.81ms까지 증가하여 성능 여유가 크게 감소하였다.

이후 약 280 RPS에서는 p95가 1.96s까지 증가하여 SLO를 초과하였다.

따라서 현재 V1의 성능 저하 구간은 250~280 RPS 사이로 판단한다.


16.5 성능 저하 시 시스템 지표 변화

250 RPS에서 응답시간은 크게 증가했지만 다음 시스템 지표에서는 명확한 포화가 확인되지 않았다.

EC2 CPU               최대 약 77.787%
EC2 Memory            약 51%
HikariCP Active       최대 3
HikariCP Pending      0
MySQL Connections     11
MySQL Threads Running 최대 약 2.4

따라서 CPU, Memory, DB Connection Pool 또는 MySQL Thread 증가와 응답시간 증가 사이에서 직접적인 상관관계를 확인하지 못하였다.


16.6 V1의 한계 구성요소

현재 수집한 지표만으로는 V1에서 가장 먼저 한계에 접근하는 특정 구성요소를 확인하지 못하였다.

EC2 CPU와 Memory에는 여유가 있었으며 HikariCP와 MySQL에서도 명확한 포화 현상이 발생하지 않았다.

따라서 단순히 EC2 Instance 사양을 높이거나 DB Connection Pool을 증가시키는 것을 해결책으로 결정할 근거는 부족하다.

현재 확인된 것은 다음과 같다.

확인 가능

250~280 RPS
→ 응답시간 급격히 증가
→ SLO 초과


확인 불가

정확히 어느 구간에서
요청 대기시간이 증가했는가?

향후 Tomcat Thread, JVM/GC, Endpoint별 처리시간, DB Query Latency 및 Nginx Upstream 처리시간 등을 추가로 관측하여 실제 병목 위치를 확인할 필요가 있다.


16.7 최종 결과

부하 테스트 결과 V1은 예상 Peak Traffic인 0.16 HTTP RPS를 크게 초과하는 부하에서도 Backend API SLO를 만족하였다.

테스트에서 확인된 최대 SLO 충족 부하는 약 250 RPS였으며, 약 280 RPS에서는 p95와 p99가 SLO를 초과하였다.

따라서 현재 결과를 기준으로 다음과 같이 정리할 수 있다.

항목 결과
예상 Peak 0.16 RPS 처리 충분히 가능
Peak 대비 20배 3.2 RPS 충분한 성능 여유
최대 SLO 충족 테스트 약 250 RPS
성능 저하 구간 약 250~280 RPS
250 RPS p95 279.81ms
약 280 RPS p95 1.96s
CPU 병목 확인되지 않음
Memory 병목 확인되지 않음
HikariCP 병목 확인되지 않음
MySQL 병목 확인되지 않음
정확한 병목 위치 추가 관측 필요

따라서 현재 예상 Traffic을 기준으로 V1에서 즉시 Scale-out 또는 Instance Scale-up이 필요한 상태라고 판단할 근거는 없다.

또한 성능 한계 구간에서 CPU, Memory, DB Connection Pool의 포화가 확인되지 않았기 때문에 단순한 Instance 변경이나 Connection Pool 확대보다 정확한 병목 위치를 확인할 수 있는 Application Level 관측을 우선적으로 보강할 필요가 있다.

이후 V2에서는 다음 지표를 추가하여 동일한 부하 테스트를 수행하고 V1과 비교한다.

  • Endpoint별 Server Response Time
  • Tomcat Thread
  • JVM / GC
  • DB Query Latency
  • Nginx Request / Upstream Response Time

이를 통해 V2 구조 변경 이후 처리 성능이 실제로 개선되었는지와 병목 위치가 어떻게 변화했는지를 비교한다.