로컬 테스트
실제 서비스 환경
요약:
이번 테스트는 초기 검증 및 병목 현상 탐지를 위해 로컬 환경에서 진행했지만,
실제 서비스 환경과 유사하게 테스트하려면 클라우드에서 별도의 인스턴스를 사용해야 합니다.
(참고로 로컬 테스트에서는 레이턴시가 낮게 측정되어 신빙성이 떨어집니다. ) 😊
프로젝트가 어느 정도의 트래픽을 안정적으로 처리할 수 있는지,
그리고 성능 병목 현상을 빠르게 발견해 개선할 수 있도록 하기 위해서라고 생각합니다.
여러 부하 테스트 도구 중 Artillery를 선택한 이유는 다음과 같습니다:
TPS (Transactions Per Second)
초당 처리되는 트랜잭션 수를 의미하며, 시스템의 전체 처리량을 평가합니다.
RPS (Requests Per Second)
초당 발생하는 요청 수를 의미합니다. HTTP 요청 기반 테스트에서 주로 사용됩니다.
레이턴시 (Latency)
요청을 보내고 응답을 받기까지 걸리는 시간입니다.
퍼센타일 (Percentile)
예를 들어, p90은 전체 요청 중 90%가 해당 시간 이하로 응답받았음을, p99는 99%가 그 이하의 응답시간을 경험했음을 의미합니다.
vUser (Virtual User)
부하 테스트에서 시뮬레이션 되는 가상의 사용자를 의미합니다.
하나의 vUser는 미리 정의된 시나리오(예: 로그인, 데이터 요청, 로그아웃)를 수행합니다.
아래는 실제 테스트에 사용한 YAML 파일 예시입니다.
이 예시는 로컬에서 Docker로 어플리케이션을 띄우고 부하 테스트를 진행한 환경을 기반으로 작성되었습니다.
config:
target: "http://localhost:8080" # 실제 API 서버 주소로 변경
phases:
# 램업: 2분 동안 5에서 25까지 가상 사용자를 점진적으로 증가
- duration: 120
arrivalRate: 5
rampTo: 25
# 안정: 1분 동안 초당 25건 유지
- duration: 60
arrivalRate: 25
# 램다운: 2분 동안 25에서 5까지 감소
- duration: 120
arrivalRate: 25
rampTo: 5
processor: "./processor.js"
defaults:
headers:
Content-Type: "application/json"
variables:
userId: 1 # 테스트할 사용자 ID (필요에 맞게 수정)
payload:
path: "./data.csv" # CSV 파일 경로 (예: 스크립트와 동일한 디렉토리 내)
fields:
- email
- password
before:
flow:
- post:
url: "/api/v1/users/sign-in"
json:
email: "{{ email }}"
password: "{{ password }}"
capture:
- json: "$.data.accessToken"
as: "accessToken"
- header: "set-cookie"
as: "refreshToken"
afterResponse: "combineTokens" # processor.js 내 함수로 캡처된 토큰들을 조합
scenarios:
- name: "로그인 후 투자 일기 생성, 투자 자산 추가 및 삭제 플로우"
flow:
# 1. 투자 일기 생성
- post:
url: "/api/v1/users/{{ userId }}/diaries"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
json:
title: "Investment Diary Title"
content: "Investment diary content"
date: "2025-03-01T14:55:35.478Z"
capture:
- json: "$.data.id"
as: "diaryId"
# 2. 자산 목록 조회
- get:
url: "/api/v1/assets?offset=0"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
# 3. 투자 일기 자산 추가
- post:
url: "/api/v1/users/{{ userId }}/diaries/{{ diaryId }}/diary-assets"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
json:
diaryId: "{{ diaryId }}"
assetId: "{{ diaryId }}" # 예시: diaryId를 assetId로 사용 (실제 테스트에 맞게 수정)
amount: 100
buyPrice: 50
capture:
- json: "$.data.id"
as: "diaryAssetId"
# 4. 투자 일기 자산 삭제
- delete:
url: "/api/v1/users/{{ userId }}/diaries/{{ diaryId }}/diary-assets/{{ diaryAssetId }}"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
# 5. 투자 일기 목록 조회
- get:
url: "/api/v1/users/{{ userId }}/diaries"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
# 6. 투자 일기 삭제
- delete:
url: "/api/v1/users/{{ userId }}/diaries/{{ diaryId }}"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
# 7. 자산 목록 재조회
- get:
url: "/api/v1/assets?offset=0"
headers:
Authorization: "Bearer {{ tokens.accessToken }}"
cookie:
refreshToken: "{{ tokens.refreshToken }}"
npm install -g artillery
artillery run test.yaml --key yourkey --record
테스트 실행 후 Artillery 대시보드에 접속하면 아래와 같이 결과를 확인할 수 있습니다.
또한, 아래와 같이 각 요청의 퍼센타일 정보를 통해 성능 병목을 확인할 수 있습니다.
전체 시나리오는 아래와 같이 p99는 433ms, p90은 408ms로 나타나 하나의 트랜잭션의 레이턴시도 확인할 수 있습니다.
테스트 환경에 맞춰 YAML 파일을 작성하는 동안 여러 시행착오를 겪었지만,
이번 테스트를 통해 내 프로젝트가 어느 정도의 트래픽을 견딜 수 있는지, 그리고
어떤 부분에서 성능 병목이 발생하는지 파악할 수 있었습니다.
추가적으로, 더 많은 부하를 주었을 때 아래와 같이 연결이 끊기거나
컨테이너 CPU 사용률이 급증하는 문제도 확인했습니다.
다음 포스트에서는 Grafana + Prometheus를 활용해 모니터링을 구축하고,
병목 현상을 분석하여 개선하는 사례를 다룰 예정입니다. 👍