부하테스트 시나리오 작성 및 부하테스트 진행 (2)
1. 들어가며..
예상치 못한 순간에 찾아온 트래픽 폭주는 개발자의 악몽과도 같습니다. 이전에 런칭했던 서비스는 일정에 쫓겨 부하테스트 없이 급하게 배포했고, 사용자가 몰렸을 때 속수무책으로 당할 수밖에 없었습니다.
"다시는 같은 실수를 반복하지 말자."
새롭게 준비 중인 서비스에는 사용자 행동 패턴과 Google 애널리틱스를 바탕으로 부하테스트를 계획했습니다. 목표 고객층을 명확히 설정하고 그에 맞는 트래픽 규모와 패턴을 예측하여, 안정적으로 작동할 수 있는 서비스 구축을 목표로 했습니다.
이 글에서는 실제 사용자 데이터를 바탕으로 부하테스트 시나리오를 구성하고, 목표 트래픽을 설정하여 테스트를 진행한 전 과정을 작성하려고 합니다.
2. 실제 사용자 분석 및 시나리오 작성
먼저 제가 진행하는 프로젝트는 지도 기반 축제 정보 제공 서비스
입니다. 이전에 런칭했던 축제는 경희대학교 10월 대학 축제로 3일동안 7400명
이 이용하였고 총 30초의 평균 참여 시간
을 기록했습니다.
Google 애널리틱스 데이터를 분석한 결과, 총 페이지 조회수 59,000개 중 상위 5개 페이지가 52,522개(약 89%)를 차지했습니다. 따라서 이 5개 핵심 기능을 중심으로 시나리오를 작성하기로 결정했습니다.
다음은 페이지 조회수 순위별로 작성한 기능입니다.
1
2
3
4
5
1. 메인페이지
2. 지도페이지
3. 스탬프투어
4. 공연정보
5. 공지사항
이를 토대로 Locust를 이용하여 다음 디렉토리 구조로 부하테스트를 작성하였습니다.
1
2
3
4
5
6
7
waba_tests/
├─ locust.py # Locust 실행 엔트리 포인트
├─ scenario_concert.py # 콘서트 정보 시나리오 (ConcertScenarioUser)
├─ scenario_home.py # 홈 화면 시나리오 (HomeScenarioUser)
├─ scenario_map.py # 지도 시나리오 (MapScenarioUser)
├─ scenario_notice.py # 공지사항 시나리오 (NoticeScenarioUser)
└─ scenario_stamp.py # 스탬프 투어 시나리오 (NewStampUser, ExistingStampUser)
locust.py에는 각 시나리오의 가중치를 실제 페이지 조회수 비율에 맞춰 설정했습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
locust.py
from scenario_home import HomeScenarioUser
from scenario_map import MapScenarioUser
from scenario_concert import ConcertScenarioUser
from scenario_notice import NoticeScenarioUser
from scenario_stamp import NewStampUser, ExistingStampUser
HomeScenarioUser.weight = 49
MapScenarioUser.weight = 25
ConcertScenarioUser.weight = 11
NoticeScenarioUser.weight = 4
NewStampUser.weight = 3
ExistingStampUser.weight = 8
다음은 실제 작성한 시나리오 중 하나의 예시입니다. 다른 시나리오들도 이와 유사한 방식으로 구현했습니다. 이 예시에서 가상 사용자는 공지사항 목록을 조회하고 특정 공지사항의 상세 내용을 확인하는 행동을 수행합니다. wait_time을 1~3초로 설정한 이유는 실제 사용자의 브라우징 패턴을 최대한 현실적으로 시뮬레이션하기 위해 설정하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class NoticeScenarioUser(FastHttpUser):
wait_time = between(1, 3)
# 미리 정의된 공지사항 ID 목록
notice_ids = [3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20]
@task
def notice_scenario(self):
# (1) 공지사항 목록 조회
with self.client.get(
"/notice?type=NOTICE", name="/notice list", catch_response=True
) as response:
if response.status_code != 200:
response.failure(
f"Failed GET /notice?type=NOTICE => {response.status_code}"
)
# (2) 개별 공지 상세 요청 n회
# 실제 상황에 맞춰 2~5 정도의 횟수를 랜덤으로 조회한다고 가정
count = random.randint(2, 5)
for _ in range(count):
nid = random.choice(self.notice_ids)
with self.client.get(
f"/notice/{nid}", name="/notice detail", catch_response=True
) as response:
if response.status_code != 200:
response.failure(
f"Failed GET /notice/1/{nid} => {response.status_code}"
)
전체적으로 4개의 시나리오는 각각 2~5개의 GET 요청을 API로 보내는 방식으로 구현했으며, 스탬프투어 시나리오는 사용자 행동 패턴에 맞게 POST와 GET 요청을 모두 포함하도록 작성했습니다.
다음으로, 이를 기반으로 테스트할 서버에 대한 부하테스트 목표를 설정하고, 부하테스트를 진행한 결과를 살펴보겠습니다.
3. 부하테스트 목표 설정 (실제 축제 규모를 고려한 트래픽 예측)
다음 타겟은 2025년 3월 제주도에서 개최되는 들불축제입니다. 이 축제는 규모가 상당히 큰 행사로, 2023년 기준으로 3일간 약 8만 명의 방문객이 참여했습니다. 일별 방문객 통계는 다음과 같았습니다:
- 첫째 날: 25,277명
- 둘째 날: 51,348명 (최대 방문일)
- 셋째 날: 2,601명
이 데이터를 바탕으로, 가장 방문객이 많은 둘째 날을 기준으로 동시접속자 수를 예측해보았습니다. 이론적 동시접속자 수를 계산하기 위해 다음과 같은 접근 방식을 사용했습니다:
1
2
3
4
5
6
7
8
9
가정:
- 방문객 51,348명이 모두 서비스를 이용
- 평균 사이트 체류시간: 1분 (기존 데이터기반 2배)
- 총 사용자 체류시간: 51,348분 (약 856시간)
- 축제 운영시간: 10시~21시 (11시간)
계산:
- 시간당 평균 동시접속자 = 총 체류시간 / 운영시간
- 856시간 / 11시간 = 약 76명
하지만 이러한 계산은 방문객이 11시간 동안 균등하게 분포된다는 가정하에 이루어진 평균값입니다. 실제 상황에서는 특정 시간대에 트래픽이 집중되는 피크 타임이 발생할 수 있습니다. 따라서, 계산된 평균값(76명)의 약 3배인 200명의 동시접속자를 목표로 설정했습니다. 이는 예상치 못한 트래픽에도 서비스가 안정적으로 작동할 수 있도록 하는 안전장치라고 생각합니다.
응답 시간의 경우, 이전 서비스 운영 데이터가 충분하지 않았기 때문에 사용자 경험을 고려하여 보수적으로 500ms를 목표치로 설정했습니다. 웹 서비스에서 사용자가 “즉각적인 반응”으로 느끼는 시간이 약 100-300ms이고, “지연되지만 수용 가능한” 범위가 약 500ms 정도임을 고려했을 때, 이는 사용자 경험을 해치지 않는 적절한 기준이라고 판단했습니다.
따라서 최종 테스트 환경 및 목표는 다음과 같습니다:
1
2
3
4
5
6
7
8
### 서버 사양
애플리케이션: Spring Boot Tomcat Server + HikariCP
인프라: AWS t4g.micro (2vCPU, 1GB 메모리)
### 부하테스트 목표
최대 동시 가상 사용자: 200명
목표 응답 시간: 500ms 이하
테스트 지속 시간: 1시간
4. 부하 테스트 결과 및 자원 사용량 분석
1시간 동안 지속적인 부하 테스트를 진행한 결과, 예상보다 훨씬 뛰어난 성능을 확인할 수 있었습니다. CSV 결과를 통해 다음과 같은 주요 지표가 측정되었습니다:
1
2
3
4
5
총 요청 수 : 1770646
실패 수 : 0건
평균 응답 시간 : 21ms
99% 응답 시간 : 115ms
초당 요청 처리량 (RPS) : 514
이러한 결과는 처음 설정했던 목표(응답 시간 500ms 이하)가 상당히 보수적이었음을 보여줍니다. 서버가 예상보다 훨씬 좋은 성능을 보였기 때문에, 단순히 목표 달성 여부를 확인하는 것에서 나아가 서버의 실제 한계점을 파악하기 위한 종단점 테스트(endpoint test)
를 진행하기로 결정했습니다.
부하 테스트와 동시에 Grafana와 Prometheus를 활용하여 서버의 자원 사용량을 실시간으로 모니터링했습니다. 주요 메트릭을 분석한 결과는 다음과 같습니다:
CPU 사용량
테스트 과정에서 CPU 사용량은 평균 40% 수준을 유지했습니다. 이는 서버가 아직 상당한 여유 자원을 가지고 있음을 의미합니다.
메모리 사용량
힙 메모리(Heap) 사용량은 평균 100MB 정도로 유지되었습니다. Max 설정값이 200MB인 점을 고려했을 때, 메모리 측면에서 부하가 더 가해지면 문제가 발생할 수 있을 것이라고 판단하였습니다.
쓰레드 분석
흥미로운 점은 쓰레드 사용 패턴이었습니다. 총 180개의 쓰레드 중 약 100개(55%)가 TIME_WAITING 상태였습니다. 이 현상을 데이터베이스 커넥션 풀 지표와 함께 분석한 결과, 데이터베이스 커넥션을 기다리는 대기 쓰레드(pending)가 거의 없었습니다. 따라서 TIME_WAITING 상태의 쓰레드는 실제로 데이터베이스 연결을 기다리는 것이 아니라, 2개의 CPU 코어로 인한 자원 제약 때문에 발생한 것으로 판단됩니다.
이 분석을 통해 현재 시스템의 중요한 개선 포인트를 발견할 수 있었습니다. Tomcat의 기본 쓰레드 풀 크기(200개)
가 서버의 CPU 코어 개수(2개)
에 비해 과도하게 크게 설정되어 있었습니다. 이로 인해 불필요한 컨텍스트 스위칭
이 발생하고 메모리가 낭비
되고 있었습니다.
따라서, 종단점 테스트를 통해 한계점을 찾고 그 지점에서 쓰레드 풀의 개수를 줄여보기로 결정하였습니다.
5. 중단점 테스트 (시스템의 한계 지점 파악)
서버의 실제 한계를 파악하기 위해 점진적으로 부하를 증가시키는 중단점 테스트를 실시했습니다. 가상 사용자 수를 200명부터 시작하여 100명씩 증가시키면서 시스템의 반응을 관찰했습니다.
테스트 결과, 가상 사용자 수와 초당 요청 처리량(RPS) 사이에는 일정 지점까지 선형적인 관계가 있었으나, 특정 임계점을 넘어서면 성능이 더 이상 향상되지 않는 포화 상태에 도달하는 것을 확인할 수 있었습니다. 구체적인 측정 결과는 다음과 같습니다:
1
2
3
4
5
6
200명: RPS 평균 520 (기준점)
300명: RPS 평균 770 (48% 증가)
400명: RPS 평균 820 (6.5% 추가 증가)
500명: RPS 평균 840 (처음에 하락 후 870까지 회복, 2.4% 추가 증가)
600명: RPS 변화 없음 (성능 정체 시작)
700명: RPS 변화 없음 (성능 정체 지속)
응답 시간(Response Time)
은 사용자 경험의 중요한 지표입니다. 테스트 결과, 500명까지는 목표했던 500ms 이하의 응답 시간을 유지했으나, 600명을 초과하는 순간 응답 시간이 급격히 증가하기 시작했습니다:
1
2
600명: 평균 응답 시간 약 700ms (목표치 초과)
700명: 평균 응답 시간 약 1,000ms (1초, 사용자 경험 저하 심각)
이러한 결과를 종합적으로 분석할 때, 현재 시스템 구성에서 한계 동시 사용자 수는 500명으로 판단됩니다.
6. 향후 계획: 성능 최적화와 확장성 개선
중단점 테스트를 통해 파악한 시스템의 한계점(동시 접속자 500명)을 기준으로, 다음 단계에서는 더 심층적인 분석과 최적화 작업을 진행할 예정입니다.
세부 자원 사용량 분석: 가상 사용자 500명 조건에서 CPU, 메모리, 디스크 I/O 등 각 자원의 사용량을 모니터링하여 정확한 병목 지점 파악
쓰레드 풀 최적화: 앞서 발견한 문제점인 과도한 쓰레드 풀 크기를 조정하고, 그 효과를 측정
수평적 확장 가능성 검토: 현재의 단일 서버 구성을 넘어, 로드 밸런서를 통한 다중 서버 구성으로 확장할 경우의 성능 향상 시뮬레이션
이러한 최적화 작업을 통해 현재 시스템의 성능을 개선하고, 한계 트래픽인 동시 접속자 500명 상황에서도 부하테스트 목표에 부합되도록 하는 것이 목표입니다.