Locust로 서버 성능 테스트하기

Hyeseong_M·2025년 3월 20일
post-thumbnail

개발 한 서버의 성능이 어느정도인지 객관적 지표로 파악 후 개선단계를 진행하고자 한다.
그 첫번째 단계인 성능 파악을 진행한 과정을 기록한다.


목차

  1. 성능 테스트
  2. Locust란?
  3. Locust 설치
  4. 스크립트 작성
  5. 부하테스트 실행
  6. 결과
  7. 성능 개선

1. 성능테스트

성능 테스트는 왜 필요한가?

서버 기능 작성을 마쳤다. 서버는 정상적으로 작동한다. 하지만 그렇지 않을수도 있다.

사람들이 몰리면 부하가 발생한 서버는 예상한 것과 다르게 작동할 수 있기 때문이다. (락 발생, 쿼리 지연, 병목 현상 등)
때문에 위와 같은 문제를 해결하기 위해 성능 테스트, 부하테스트를 진행하려 한다.

1.1 성능테스트 종류

  • 부하테스트(Load Test): 특정 부하를 제한시간동안 부여. 일반적으로 1시간 기준
  • 스트레스 테스트 : peak Load 보다 높은 부하(일반적으로 2배)에 어떻게 대응하는지, 정상 복구되는지 테스트
  • 내구 테스트(Endurance Test): 장기간에 걸쳐 나타나는 문제(메모리 누수)를 확인하기 위해 장시간에 걸쳐 시스템의 안정성을 검사
  • 스파이크 테스트(Spike Test): 급작스러운 사용량 급증 시나리오에 대해 시뮬레이션
  • 중단점 테스트(Breaking Point Test): 동시단말 사용자 수를 점진적 증가시켜, 시스템 장애 시점을 결정하는 테스트

1.2. 성능테스트 목적

성능 테스트를 통해 시스템의 성능상 한계와 리소스의 한계를 파악할 수 있다.
최대 성능이 어느정도인지 파악해 서버를 최적화 할 수 있다.
서버를 최적화하는 과정을 아래의 단계를 따른다

성능 측정 <-> 결함 검출 <-> 병목 제거 <-> 용량 검증 => 반복

1.3. 배경지식

  • Response Time, 응답시간: 사용자의 Request 시점에서 시스템이 Response 하기까지의 종단시간.

  • User: 서버를 사용중인 사람
    - Current User: 언제든 부하를 줄 수 있는 사용자

    • Active User: 실제로 부하를 주고 있는 사용자
  • TPS(Transaction Per Test) : 초당 트랜잭션 수

  • Saturation Point, 임계점: 최대 TPS가 나타나는 최초의 지점.

2. Locust란?

다양한 부하테스트 툴(JMeter, Locust, K6)중 가볍고(로컬에서 요청 발생시킬 예정), 러닝커브가 적은(Python으로 스크립트 작성 가능) Locust를 선택하게 되었다.

Locust는 오픈소스 부하테스트 도구이다.
Python으로 스크립트를 작성할 수 있으며, 웹 애플리케이션의 성능을 측정할 수 있도록 도와준다.
웹 인터페이스를 제공하여 실시간으로 테스트를 모니터링하고 결과를 분석하기 쉽도록 한다.

2.1. Locust의 장점

  • 쉽게 설치가 가능
  • Python 스크립트를 통해 쉽게 작성 가능
  • 빠르게 테스트 진행 가능
  • 웹 인터페이스 지원으로 결과 시각화

3. Locust 설치

pip를 통해 로컬에 Locust를 설치해준다.
pip가 설치되어있지 않다면 다음 링크를 참고하여 Python, pip 등을 설치하여 환경을 준비해준다

#설치
$ pip install locust
# 설치확인
$ locust -V

4. 스크립트 작성

locustfile.py 파일에 작성해도 되며, locust -f {경로} 명령어를 통해 경로를 첨부해 실행해도 된다.

4.1. 스크립트

  • 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

4.2. 필요한 모듈 임포트

기본적인 HTTP 요청을 테스트하려면 HttpUser와 task 데코레이터를 임포트해야 한다.

from locust import HttpUser, task, TaskSet

4.3. HttpUser 클래스 정의

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)})

  # 나머지 테스트 코드 작성

4.4. 스크립트 작성

위 코드 작성 방법을 바탕으로 로그인 이후 단순 게시글 조회를 진행하는 테스트 코드를 작성해보았다.

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)

5. 부하 테스트 실행

5.1. 부하 테스트 환경

기기: 시놀로지 DS220+
CPU: INTEL Celeron J4025(2코어, 2 GHz)
RAM: 18GB

5.2. Locust 실행

아래 명령어를 실행하여 로커스트를 실행한다.

locust -f [파일경로]

이후 아래 명령어에 나오는 URL로 접속해주면 웹 인터페이스를 볼 수 있다.
별도의 설정이 없었다면 http://localhost:8089/로 접속하면 된다.

5.3. 테스트 설정

위 주소로 접속하면 아래와 같은 화면이 나온다

각 값의 의미는 아래와 같다

  • Number of User : 최대(피크) 동시접속 유저 수
  • Ramp Up : 초마다 증가될 유저 수(사용자 점진적 증가)
  • Host : 테스트를 진행할 서버 주소. 코드에서 지정해 줬다면 미리 입력되어있다.

테스트를 원하는 값을 입력해주고 start 버튼을 누른다.

아래 조건을 입력 후 진행해보았다.

6. 결과

6.1. 결과 지표

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

6.2. 결과 분석

  • 테스트 시작 후 RPS가 점차 증가하여 0.2 ~ 0.4 범위를 변동
  • 응답 시간이 테스트 시작부터 꾸준히 증가하여 60,000ms(60초) 이상까지 도달
  • 50번째와 95번째 백분위수 응답 시간이 거의 동일하게 증가하는 것으로 보아, 대부분의 요청이 매우 느리게 처리되고 있음을 알 수 있음

6.3. 종합 평가

  • 테스트 대상 시스템은 100명의 사용자를 처리하지만, 초당 요청 수가 매우 낮고 응답 시간이 매우 높음
  • 시스템이 요청을 제대로 처리하지 못하고 있거나, 매우 느리게 처리하고 있음을 나타냄
  • 낮은 RPS와 높은 응답 시간은 시스템의 심각한 성능 문제를 시사

7. 성능 개선

... 심각하다

성능 개선을 위해 아래 목록을 점검하고자 한다.

  • 시스템 자원 점검: 서버의 CPU, 메모리, 디스크 I/O 등을 점검하여 병목 지점 확인
  • 데이터베이스 성능 개선: 데이터베이스 쿼리 최적화, 인덱싱 등을 통해 데이터베이스 성능을 개선
  • 애플리케이션 코드 분석: 애플리케이션 코드의 성능 저하 요인을 분석하고 개선
  • 네트워크 점검: 네트워크 지연이나 대역폭 문제를 점검

참고자료

profile
Dev_Hyeseong

0개의 댓글