
개발 한 서버의 성능이 어느정도인지 객관적 지표로 파악 후 개선단계를 진행하고자 한다.
그 첫번째 단계인 성능 파악을 진행한 과정을 기록한다.
성능 테스트는 왜 필요한가?
서버 기능 작성을 마쳤다. 서버는 정상적으로 작동한다. 하지만 그렇지 않을수도 있다.
사람들이 몰리면 부하가 발생한 서버는 예상한 것과 다르게 작동할 수 있기 때문이다. (락 발생, 쿼리 지연, 병목 현상 등)
때문에 위와 같은 문제를 해결하기 위해 성능 테스트, 부하테스트를 진행하려 한다.
성능 테스트를 통해 시스템의 성능상 한계와 리소스의 한계를 파악할 수 있다.
최대 성능이 어느정도인지 파악해 서버를 최적화 할 수 있다.
서버를 최적화하는 과정을 아래의 단계를 따른다
성능 측정 <-> 결함 검출 <-> 병목 제거 <-> 용량 검증 => 반복
Response Time, 응답시간: 사용자의 Request 시점에서 시스템이 Response 하기까지의 종단시간.

User: 서버를 사용중인 사람
- Current User: 언제든 부하를 줄 수 있는 사용자
TPS(Transaction Per Test) : 초당 트랜잭션 수
Saturation Point, 임계점: 최대 TPS가 나타나는 최초의 지점.

다양한 부하테스트 툴(JMeter, Locust, K6)중 가볍고(로컬에서 요청 발생시킬 예정), 러닝커브가 적은(Python으로 스크립트 작성 가능) Locust를 선택하게 되었다.
Locust는 오픈소스 부하테스트 도구이다.
Python으로 스크립트를 작성할 수 있으며, 웹 애플리케이션의 성능을 측정할 수 있도록 도와준다.
웹 인터페이스를 제공하여 실시간으로 테스트를 모니터링하고 결과를 분석하기 쉽도록 한다.
pip를 통해 로컬에 Locust를 설치해준다.
pip가 설치되어있지 않다면 다음 링크를 참고하여 Python, pip 등을 설치하여 환경을 준비해준다
#설치
$ pip install locust
# 설치확인
$ locust -V
locustfile.py 파일에 작성해도 되며, locust -f {경로} 명령어를 통해 경로를 첨부해 실행해도 된다.
wait_time : 가상 사용자의 대기시간 정의host : 테스트 할 대상 호스트 정의.(주소, IP 등)@task : 테스트 할 작업을 정의하는 데코레이터. TaskSet이 실행될 때 TaskSet 안의 task를 랜덤 하게 선택해서 실행한다. 가중치를 줄 수 있다.seq_task: 테스트 할 작업을 순서까지 정의하는 데코레이터. TaskSequence가 실행될 때 안의 seq_task 순서에 따라 실행한다.setup & teardown : setup과, teardown은 TaskSet의 생성시, 종료시 한번씩 실행된다.on_start,on_stop: 매 클라이언트마다 실행된다.'실행 순서
Locust setup
TaskSet setup
TaskSet on_start
TaskSet tasks…
TaskSet on_stop
TaskSet teardown
Locust teardown
기본적인 HTTP 요청을 테스트하려면 HttpUser와 task 데코레이터를 임포트해야 한다.
from locust import HttpUser, task, TaskSet
HttpUser class를 상속받아 가상의 사용자를 정의한다.
class QuickstartUser(HttpUser):
# 사용자 간 대기 시간 설정
wait_time = between(1, 2)
# 테스트 대상 호스트 설정
host = "http://example.com"
@task
def index_page(self):
self.client.get("/")
@task
def about_page(self):
self.client.get("/about/")
아래는 스크립트 예제이다.
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
# 시뮬레이션 된 유저는 작업을 실행하기 전 5~9초 기다린다.
wait_time = between(5, 9)
def on_start(self):
# 토큰으로 인증 헤더를 초기화
self.client.headers.update({'Authorization': 'Bearer TOKEN_HERE'})
@task(1)
def index_page(self):
# 대기 시간동안 아래 네번의 Get 요청을 진행
self.client.get("/api/hosts")
self.client.get("/api/tests")
self.client.get("/api/result/1488")
self.client.get("/api/result/1463")
아래는 로그인 방식을 처리하는 예제이다.
from locust import HttpUser, TaskSet, task
import requests
class UserBehavior(TaskSet):
def on_start(self):
#로그인 요청 작성
response = requests.post("http://mysite.com/login", {
"username": "user",
"password": "pass"
})
# 1. response header에서 토큰 설정
self.client.headers.update({'Authorization': response.headers.get('token')})
# 2. cookies에서 토큰 설정
self.client.cookies.set('Authorization', response.cookies.get('token'))
# 3. response body에서 토큰 설정
token = str(response.content.decode().find('token')
self.client.headers.update({'Authorization': token)})
# 나머지 테스트 코드 작성
위 코드 작성 방법을 바탕으로 로그인 이후 단순 게시글 조회를 진행하는 테스트 코드를 작성해보았다.
from locust import HttpUser, task, TaskSet, between
import requests
import re
HOST = "[호스트 경로]:8900"
class UserBehavior(TaskSet):
def on_start(self):
#로그인 요청
response = requests.post(HOST + "/api/auth/login", json={
"userEmail": "testEmail@test.com",
"userPassword" :"Test"
})
data = response.json()
if data["code"] == 200:
set_cookie = response.headers.get('Set-Cookie')
if set_cookie:
token_match = re.search(r'accessToken=([^;]+)', set_cookie)
if token_match:
token = token_match.group(1)
print(token) # 순수 토큰 값 출력
self.client.cookies.set('accessToken', token)
else:
print("토큰을 찾을 수 없습니다.")
else:
print("Set-Cookie 헤더가 없습니다.")
else:
print("로그인실패:", data["message"])
@task
def get_board(self):
with self.client.get("/api/board/1", catch_response=True) as response:
if response.status_code == 200:
print("게시글 조회 성공")
else:
print("게시글 조회 실패: {response.status_code}")
response.faiure(f"게시글 조회 실패: {response.status_code}")
class WebsiteUser(HttpUser):
host = HOST
tasks = [UserBehavior]
wait_time = between(1, 1)
기기: 시놀로지 DS220+
CPU: INTEL Celeron J4025(2코어, 2 GHz)
RAM: 18GB
아래 명령어를 실행하여 로커스트를 실행한다.
locust -f [파일경로]
이후 아래 명령어에 나오는 URL로 접속해주면 웹 인터페이스를 볼 수 있다.
별도의 설정이 없었다면 http://localhost:8089/로 접속하면 된다.
위 주소로 접속하면 아래와 같은 화면이 나온다

각 값의 의미는 아래와 같다
Number of User : 최대(피크) 동시접속 유저 수Ramp Up : 초마다 증가될 유저 수(사용자 점진적 증가)Host : 테스트를 진행할 서버 주소. 코드에서 지정해 줬다면 미리 입력되어있다.테스트를 원하는 값을 입력해주고 start 버튼을 누른다.
아래 조건을 입력 후 진행해보았다.

RPS: 초당 요청 수. 값이 높을 수록 초당 처리할 수 있는 요청수가 많음을 의미. 성능 테스트의 부하를 알 수 있음.Failures: 실패 비율. 요청이 실패한 비율과 횟수를 표기50th Percentile: 요청 응답시간의 중앙값. 평균보다 왜곡이 적은 응답 값(평균은 극단적 값을 포함)95th Percentile:요청 응답시간의 95번째 백분위의 수. 성능의 일관성을 평가할 수 있음Number Of User: 활성 사용자 수
... 심각하다
성능 개선을 위해 아래 목록을 점검하고자 한다.