인프런에서 제공하는 "대규모 트래픽 처리를 위한 부하 테스트 입문/실전" 강의 수강을 마치고
공부한 내용을 토이 프로젝트에 적용해본다.
개인 프로젝트를 배포하기 전에, 실제 트래픽을 가정한 부하 테스트를 시행해보았다.
그 결과, 애플리케이션의 최대 Throughput을 10 TPS → 20 TPS, 2배 향상시킬 수 있었다.
이 글은 그 과정을 정리한 기록이다.
부하 테스트 도구로 `k6`를 활용하여, 회원가입 API 에 점진적인 부하를 가하며
서버의 처리 한계와 병목 지점을 분석하고 개선하는 과정을 담고 있다.
부하 테스트 단계 목차
- 부하 테스트 #1 – 기본 구조 점검 : Failed
- 부하 테스트 #2 – DB를 별도 인스턴스로 분리 : Failed
- 부하 테스트 #3 – 커넥션 상태 점검 및 DB 설정 조정 :Failed
- 부하 테스트 #4 – HikariCP 커넥션 갯수 조정 : Failed
- 부하 테스트 #5 – CloudWatch로 리소스 시각화 : -
- 부하 테스트 #6 – DB 인스턴스 스케일 업 : Failed
- 부하 테스트 #7 – Application 서버 CPU 확인 및 스케일 업 : Success
📍부하 테스트 #1 - 기본 구조 점검
처음에는 EC2 인스턴스 하나에 Application + MySQL 컨테이너를 동시에 올려두었다.

▶ 시도:
- k6를 활용한 부하 테스트 진행
- `k6` 스크립트
import http from 'k6/http';
import { sleep, check } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 30 },
{ duration: '1m', target: 60 },
{ duration: '1m', target: 90 },
{ duration: '1m', target: 120 },
{ duration: '1m', target: 150 },
{ duration: '1m', target: 180 },
{ duration: '1m', target: 210 },
{ duration: '1m', target: 240 },
{ duration: '1m', target: 270 },
{ duration: '1m', target: 300 },
{ duration: '1m', target: 0 }, // 종료 처리
],
};
export default function () {
const url = 'http://{인스턴스IP}/api/register';
const uniqueName = `${Math.random().toString(36).substring(7)}`;
const payload = JSON.stringify({
name: uniqueName,
password: '1234',
});
const params = {
headers: { 'Content-Type': 'application/json' },
};
const res = http.post(url, payload, params);
check(res, {
'회원가입 성공 (200 or 201)': (r) => r.status === 200 || r.status === 201,
});
sleep(1);
}
- 10분 동안 VU(가상 사용자)를 30 → 300명까지 점진적으로 증가시키며 부하를 가함
- 각 사용자는 1초 간격으로 랜덤한 사용자 이름으로 POST 요청을 전송
- 응답 코드가 200 또는 201이면 회원가입 성공으로 간주하고 `check()`로 검증
▶ 부하테스트 결과 :
- Failed
- TPS: 9~10 TPS
- k6 대시보드에서 최대 Throughput 9~10 TPS 도달하고 그 이상 오르지 않는 것을 확인할 수 있음
- 병목 원인 불명 (Application과 DB가 같은 인스턴스에 존재)

📍부하 테스트 #2 - DB를 별도 인스턴스로 분리
한 인스턴스에서 Application과 DB가 동시에 실행되면 리소스를 공유하므로, 병목 지점이 명확하지 않다고 판단. DB와 Application을 분리하면 어느 쪽에 병목이 있는지 명확히 알 수 있을 거라고 생각했다.
▶ 조치:
- EC2 인스턴스를 추가 생성하여 DB 컨테이너를 분리함

▶ 부하테스트 결과 :
- Failed
- TPS 변화 없음 (여전히 9~10 TPS)
📍부하 테스트 #3 - 커넥션 상태 점검 및 DB 설정 조정
부하테스트 도중 SHOW PROCESSLIST; 명령어로 확인해 보니, 긴 시간동안 Sleep 상태로 유지된 커넥션들이 상당히 많아서 DB 커넥션 갯수가 부족한 것이 병목지점으로 의심되었다.
| ID | User | Host | DB | Command | Time | State | Info |
|-----|----------------|----------------------|---------------|---------|------|-------------------------|----------------------------------------------------------------------|
| 5 | event_scheduler| localhost | | Daemon | 827 | Waiting on empty queue | |
| 10 | root | 221.147.81.129:51038 | wedlessInvite | Sleep | 812 | | |
| 11 | root | 221.147.81.129:51039 | wedlessInvite | Sleep | 792 | | |
...
▶ 조치 :
- /etc/my.cnf 에 아래 설정 추가
[mysqld]
wait_timeout = 60
interactive_timeout = 60
MySQL의 비활성 커넥션(= idle connection) 에 대한 자동 종료 시간을 설정하고,
불필요한 Sleep 커넥션은 수동으로 KILL 명령어를 통해 제거함
▶ 부하테스트 결과:
- Failed
- 가용한 커넥션이 많아져서 성능 향상을 기대했으나, TPS는 여전히 9~10 수준 유지
📍부하 테스트 #4 - HikariCP 커넥션 갯수 조정

k6 서버 에러로그
2025-05-29 14:25:35 [http-nio-8080-exec-162] WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: null
2025-05-29 14:25:35 [http-nio-8080-exec-162] ERROR o.h.e.jdbc.spi.SqlExceptionHelper - HikariPool-1 - Connection is not available, request timed out after 30001ms (total=50, active=50, idle=0, waiting=149)
spring boot application 로그
k6 서버에서 에러 로그가 위 이미지와 같이 쌓여서 spring boot application 로그를 확인해봤다.
로그를 보면 아래와 같은 상황이 벌어지고 있었다
| 항목 | 의미 |
| total=50 | 최대 커넥션 수는 50개로 설정됨 |
| active=50 | 지금 그 50개를 모두 사용 중 |
| idle=0 | 놀고 있는 커넥션 없음 |
| waiting=149 | 무려 149개의 요청이 커넥션 대기 중 |
| timed out | 30초 기다렸지만 커넥션 못 받아서 실패 (기본 connection-timeout=30000) |
HikariCP 커넥션 풀이 모두 점유되면서 이후 요청들이 커넥션을 못 받아 처리에 실패한 것이다.
MySQL 최대 커넥션 수는 151 개.
Spring Boot 에서 자동으로 maximum-pool-size 는 50개로 설정되어있었다.
참고로 Spring Boot에서는 maximum-pool-size 를 따로 지정하지 않으면,
HikariCP가 시스템 리소스를 참고해 적절한 값을 자동으로 설정한다.
로그상 커넥션 풀 크기가 50으로 설정되어 있었던 이유다
MySQL은 max_connections=151까지 지원하기 때문에 더 많은 커넥션을 활용할 여지가 있었다.
▶ 조치 :
- 전체 커넥션을 애플리케이션에서 독점하지 않도록, MySQL 최대 커넥션 수(151개)의 약 60% 수준으로 조정
spring.datasource.hikari.maximum-pool-size=80 // 50 >> 80
▶ 부하테스트 결과:
- Failed
- 최대 Throughput : 9~10TPS
- 변화 없음 → 커넥션 갯수 병목 아님 판단
이 시도는 사실 지나고 보니 정확한 원인 파악 없이 적용한 조치였다. 부하 테스트 중 다음 명령어를 통해 현재 사용 중인 커넥션 수를 확인해봤는데
SELECT
VARIABLE_VALUE AS current,
(SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'MAX_CONNECTIONS') AS max
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Threads_connected';
확인 결과, 최대 TPS가 나오는 시점에도 최대 커넥션 수의 30%도 사용되지 않고 있었다. 이를 통해 커넥션 갯수가 원인이 아님을 사전에 알 수 있었고, 이 점을 테스트 이전에 파악했더라면 이 설정은 건드리지 않았을 것이다.
📍부하 테스트 #5 - CloudWatch로 리소스 시각화
지금까지 시도는 모두 [가설 > 검증] 기반이었다. 이제는 리소스 사용량을 시각화해, 어디에서 병목이 발생하는지를 눈으로 확인하고 싶었다.
▶ 조치 :
- CloudWatch 대시보드를 구성해 부하테스트 하는 동안의 DB와 Application 서버 리소스를 모니터링. 모니터링한 정보는 아래와 같다.
- Application CPU 사용량
- Application 메모리 사용량
- DB CPU 사용량
- DB 메모리 사용량
CPU 사용률은 바로 시각화할 수 있었지만 메모리 사용률을 CloudWatch에 시각화하려고 하니 꽤나 복잡했다. 추후 과정을 잊어버릴 것 같아서 아래 글로 작성해두었으니 참고하길 바란다.
https://feelfreetothink.tistory.com/242
[AWS] 부하 테스트를 위한 CloudWatch 대시보드 구축 : 메모리 사용률까지 띄워보기
개인 프로젝트에서 부하 테스트를 진행하면서, CPU 사용률은 바로 시각화할 수 있었지만 메모리 사용률을 CloudWatch에 시각화하려고 하니 꽤나 복잡했다. 나중에 잊어버릴 것 같아서 이 글로 과정
feelfreetothink.tistory.com

▶ 인사이트:
- 맨 오른쪽의 DB 메모리 사용량을 보면, 79%까지 치솟아 있는 것을 확인할 수 있다.
- DB 메모리 사용량이 75% 이상 → 병목 의심
📍부하 테스트 #6 - DB 인스턴스 스케일 업
DB 메모리가 79% 이어서 병목 위험 구간으로 의심됨.

▶ 조치:
- DB 인스턴스 스케일 업: t2.micro → t2.medium
| 항목 | t2.micro | t2.medium | 설명 |
| RAM (메모리) | 1 GiB | 4 GiB | 🔺 4배 증가 (메모리 여유 확보) |
| vCPU | 1 | 2 | 멀티스레드 지원 가능 |

▶ 결과:
- Failed
- DB 인스턴스를 t2.micro 에서 t2.medium 으로 변경 후 79%이던 DB메모리 사용량이 20% 이하로 떨어진 것을 확인할 수 있다.
- 그러나 부하테스트 진행 시 최대 Throughput에는 변화가 없음(9~10 TPS) → 병목은 DB가 아님
📍부하 테스트 #7 - Application 서버 CPU 확인 및 스케일 업
DB가 아니라면 Application 서버 쪽 리소스가 병목일 수 있다. 실제로 부하테스트 중 Application 서버에서 top 명령어로 확인해봤다.
ubuntu@~~~:~$ top
Tasks: 111 total, 1 running, 110 sleeping, 0 stopped, 0 zombie
%Cpu(s): 99.0 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.7 st
MiB Mem : 1963.9 total, 160.4 free, 971.0 used, 999.0 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 992.9 avail Mem
위 결과를 통해 현재 CPU(%CPU)는 99%를 사용하고 있음을 확인할 수 있었다.
CPU 가 완전히 포화된 상태에서는 응답지연, 컨테이너 쓰레드 대기, GC 지연 등이 야기된다.

CloudWatch dashboard 에서도 같은 결과를 그래프로 확인할 수 있었다.
부하테스트 실행 시 Application CPU가 부하 시점마다 급등하는 것 확인하고 병목 지점이 Application CPU로 명확히 드러남

▶ 조치:
- Application 서버 스케일 업: t2.small → t2.medium
| 항목 | t2.small | t2.medium | 설명 |
| vCPU | 1 | 2 | 병목 지점이었던 CPU 리소스 2배 증가 |
| 메모리 | 2 GiB | 4 GiB | 메모리 부족 현상 완화 (캐시, GC 등 여유 확보) |
| CPU 크레딧 | 시간당 12개 | 시간당 24개 | CPU Burst 가능 시간 2배 증가 (지속적인 고부하에 유리) |
▶ 부하테스트 결과:
- Application 서버의 병목 지점이 CPU라는 것이 명확히 확인되었고, 이를 기반으로 리소스를 튜닝하고 구성 변경을 마무리했다.

- Application 서버의 CPU 병목 해결을 위한 스케일 업 이후 부하테스트 진행 중 CPU 사용량이 50~70% 사이에 머무르는 것을 확인할 수 있다(좌측).
- t2.medium 유형으로 변경 후 메모리 사용량은 현저히 낮아졌다(우측).

- TPS 10 → 20으로 2배 향상
부하테스트를 경험하고 병목을 진단하고 해결하는 전 과정을 직접 수행해볼 수 있었다.
모니터링 → 병목 진단 → 가설 수립 → 조정 및 검증 단계를 통해서 병목을 찾아내는 과정이 흥미로웠다.
처음으로 아키텍쳐 이미지를 정리해봤는데 PPT 쉽지 않다. 다들 이렇게 만드시는건가 ? 아키텍쳐 이미지 만드는 툴이 있는걸까 ?
++ 찾아보니 아키텍쳐 툴이 있다 ;; → 링크
'TEST' 카테고리의 다른 글
| [TEST] 부하테스트 : 트래픽 증가에 따른 시스템 설계 및 확장 방법 (0) | 2025.05.27 |
|---|---|
| [TEST] 부하테스트 : 성능 개선 전략 (0) | 2025.05.27 |
| [TEST] 부하테스트 : 병목지점 (0) | 2025.05.27 |
| [TEST] 부하테스트 : k6 Web DashBoard 해석하기 (0) | 2025.05.26 |
| [TEST] 부하테스트 : 기본 개념 (1) | 2025.05.26 |