Post

부하테스트 도구 선택 (1)

프로젝트 배포 환경에 부하테스트를 진행하기 위해 부하테스트 툴로 Locust를 결정하게 된 이유와 Locust를 사용하는 방법에 대하여 학습한 내용을 적어보았습니다.

1. 부하테스트 툴 선정 (Jmeter, k6, locust)

부하테스트를 진행하기 위해 가장 먼저 직면했던 과제는 적절한 테스트 도구의 선정이었습니다. 이전에 Jmeter를 사용했던 경험을 되돌아보며, 더 효율적이고 프로젝트에 적합한 도구를 선택하기 위한 분석을 시작했습니다.

과거 Jmeter를 선택했던 주된 이유는 다음과 같았습니다:

  1. Java 애플리케이션이라 기술적 친숙도가 높았음
  2. GUI 인터페이스를 제공하여 시각적으로 작업 가능

그러나 실제 사용 과정에서 몇 가지 중요한 한계점을 경험했습니다:

uniprograming

Jmeter의 GUI는 기능이 풍부하지만, 그만큼 인터페이스가 복잡하고 직관적이지 않았습니다. 단일 HTTP 요청을 설정하는 간단한 작업조차 여러 단계와 설정이 필요했습니다. 특히 실제 사용자의 행동 패턴을 반영한 복잡한 시나리오를 구성할 때, XML 기반의 설정 파일 관리가 매우 번거롭다고 느꼈습니다.

최종적으로 간단한 시나리오를 작성하는 것은 문제가 없지만 실제 서비스 로직을 반영하여 시나리오를 작성하고 해당 시나리오를 저장해놓는 구조가 너무 번거롭다고 느꼈습니다.

이러한 경험을 바탕으로, 이번 프로젝트에서는 다음 두 가지 핵심 기준을 중심으로 도구를 선정하기로 결정했습니다:

  1. 사용자 시나리오 작성의 용이성: 실제 서비스 흐름을 코드로 명확하게 표현할 수 있는가?
  2. 결과 분석의 직관성: 테스트 결과를 실시간으로 쉽게 확인하고 해석할 수 있는가?


K6와 Locust 비교

이 두 기준을 바탕으로 현대적인 부하테스트 도구인 k6와 Locust를 집중적으로 비교했습니다.

1
2
3
4
5
k6의 특징
- JavaScript 기반의 시나리오 작성 지원
- 가볍고 빠른 실행 속도
- 확장성이 뛰어나며 CI/CD 파이프라인과 통합 용이
- 기본적으로 CLI 결과 출력에 의존
1
2
3
4
5
Locust의 특징:
- Python 기반의 시나리오 작성 지원
- 객체지향적인 테스트 구조 설계 가능
- 분산 부하 테스트 지원
- 내장된 웹 기반 실시간 대시보드 제공

시나리오 작성 측면에서는 두 도구 모두 코드 기반 접근법을 제공하므로 첫 번째 기준을 충족했습니다. Python과 JavaScript 모두 접근성이 높은 언어이기 때문에 시나리오 작성의 난이도 차이는 크지 않았습니다.

Locust 선정의 결정적 요소: 결과 시각화

최종 결정에 있어 가장 중요한 차이점은 테스트 결과의 시각화 방식이었습니다. k6는 기본적으로 CLI 인터페이스만 제공하며, 그래픽 대시보드를 구성하기 위해서는 Prometheus와 Grafana와 같은 추가 도구와의 통합이 필요했습니다:

k6

이러한 추가 설정은 시간과 리소스를 더 필요로 합니다. 반면, Locust는 테스트 실행과 동시에 바로 사용할 수 있는 웹 기반 대시보드를 기본으로 제공합니다. 이 대시보드를 통해 테스트 진행 상황, 응답 시간, 오류율, RPS 등의 핵심 지표를 실시간으로 확인할 수 있어 즉각적인 피드백과 분석이 가능했습니다.

또한 Locust는 테스트 결과를 CSV 형식으로 내보내는 기능도 기본적으로 지원하여, 테스트 후 상세 분석을 위한 데이터 추출이 용이했습니다.

최종 선택: Locust 이러한 분석을 바탕으로, 프로젝트의 부하테스트 도구로 Locust를 최종 선택했습니다. 물론 서버 자체의 성능 메트릭을 모니터링하기 위해서는 여전히 Prometheus와 Grafana를 설정해야 했지만, 부하테스트 도구로서의 기능과 사용성 측면에서 Locust가 프로젝트 요구사항에 가장 적합했습니다.

결론적으로, 코드 기반의 시나리오 작성을 선호하며 별도의 설정 없이 즉시 결과를 시각화하여 확인하고 싶다면 Locust가 탁월한 선택입니다. 반면, JavaScript를 선호하거나 이미 Prometheus와 Grafana 환경이 구축되어 있다면 k6도 충분히 매력적인 대안이 될 수 있습니다.


2. Locust 사용해보기

https://docs.locust.io/en/stable/index.html

위 사이트를 기반으로 학습을 진행했습니다.

먼저 따로 저는 파이썬 가상환경을 생성하여 진행했습니다. 따라서 아래와 같은 구조로 locust를 설치했습니다.

1
2
3
4
5
6
7
8
가상환경 생성 (window)
python -m venv venv

가상환경 실행 (window)
.\venv\Scripts\Activate.ps1

locust 설치
pip install locust

locust의 경우 하나의 모듈이기 떄문에 다른 패키지를 import해서 작성할 수 있다는 장점이 존재합니다. 따라서 시나리오 작성시 쉽게 하나의 시나리오를 완성할수 있습니다. 다음은 제가 연습할 때 사용했던 스크립트입니다.

1
2
3
4
5
6
7
8
9
10
11
12
from locust import HttpUser, task, between

class UserBehavior(HttpUser):
    wait_time = between(1, 3)
    
    @task(3)  # 이 태스크는 3배 더 자주 실행됩니다
    def view_items(self):
        self.client.get("/items")
        
    @task(1)  # 이 태스크는 상대적으로 덜 자주 실행됩니다
    def view_cart(self):
        self.client.get("/cart")

@task(n)의 숫자는 상대적 가중치를 나타내며, 절대적인 실행 횟수가 아닙니다. 첫 번째 태스크의 가중치가 3이고 두 번째 태스크의 가중치가 1이라면, 첫 번째 태스크는 약 75%의 확률로 선택되고 두 번째 태스크는 25%의 확률로 선택됩니다.

wait_time을 설정하여 실제 사용자들이 요청을 보내는 것처럼 사용자가 이용하는 형태와 비슷하게 만들어 줄 수 있습니다. HttpUser는 python의 requests 라이브러리를 사용하는 기본적인 사용자(User) 클래스입니다. requests 라이브러리는 동기적으로 작동하기 때문에, 하나의 요청을 보내고 하나의 요청이 완료될 때까지 다음 요청을 보내지 않습니다. 따라서 부하테스트에는 맞지 않고 일반적인 요청 테스트를 진행할 때 사용해 볼 수 있습니다.

Locust에서는 따로 FastHttpUser를 제공합니니다. 이는 gevent 기반의 비동기를 이용하여 요청을 보낸 후 응답을 기다리지 않고 다른 작업을 수행할 수 있습니다. 따라서, 더 많은 요청을 처리할 수 있습니다. gevent 기반의 비동기에 대한 내용은 이전 블로그 글을 참고해 주세요. 파이썬 비동기

간단하게 설명하면, 파이썬의 async/await과 비교하면 greenlet은 C 확장으로 구현되어 있어 메모리 사용량이 낮고 Context Switching 오버헤드가 극소화 되어있다는 장점이 있습니다.

이런 비동기 기반으로 하나의 CPU 코어에서 HttpUser보다 더 많은 부하를 낼 수 있습니다. 사용해 보았을 때 cpu 코어 하나로 만들 수 있는 가상 사용자수는 제 시나리오 기준 2배정도 차이가 났고 CPU코어 부하가 심할 경우 다음과 같은 문구가 나오는 걸 확인할 수 있었습니다.

1
[2025-02-28 16:00:16,498] <>/WARNING/root: CPU usage above 90%! This may constrain your throughput and may even give inconsistent response time measurements! See https://docs.locust.io/en/stable/running-distributed.html for how to distribute the load over multiple CPU cores or machines

따라서 저는 FastHttpUser를 사용하여 시나리오를 작성하였습니다. HttpUser와 같은 api를 사용하기 때문에 라이브러리만 수정해서 사용하였습니다.


3. master - worker 사용


파이썬은 GIL로 단일 CPU 코어안에서만 동작합니다. 따라서, 1000개 이상의 가상 사용자를 사용하게 될 때, FastHttpUser를 쓰더라도 제 노트북에서는 CPU 과부하가 발생하였습니다. 이때 사용할 수 있는게 master 프로세스와 worker 프로세스로 나누어서 여러 코어를 이용한 masert - worker 구조입니다. 이 구조를 바탕으로 실제 실행해 보았을 때 1000명의 가상 유저까지는 무리 없이 2개의 파이썬 프로세스로 동작할 수 있었습니다.

1
2
3
4
5
Master 프로세스
locust -f my_locustfile.py --master

Worker 프로세스
locust -f - --worker --master-host <your master> --processes 4

이 명령어는 fork()를 사용해서 실행되기 때문에 윈도우에서는 사용할 수 없습니다. 따라서 윈도우에서는 아래와 같이 직접 커맨드창을 여러개 열어 원하는 프로세스 수 만큼 실행시켜주었습니다.

1
2
3
4
5
6
cmd 1개
locust -f locust.py --master --web-host 0.0.0.0

새로운 cmd 창 2개
locust -f locust.py --worker --master-host=127.0.0.1
locust -f locust.py --worker --master-host=127.0.0.1

이 방식을 통해 간단하게 여러 코어를 이용하여 좀 더 많은 양의 부하를 서버에 줄 수 있습니다. 또한, UI를 통해 각 worker가 몇 개의 쓰레드를 생성했고 메모리는 얼마를 사용하는지 파악할 수 있습니다.

Image


4. 마무리


간단하게 Locust를 살펴보았는데 실제로 결과를 바로 웹 아래와 같은 웹 ui로 볼 수 있으며 download data탭을 이용하여 결과치를 csv와 html보고서로 다운받을 수 있습니다. 저는 이번에 사용하며 시나리오도 짜기 쉽고 결과도 바로 볼 수 있다는 점에서 큰 메리트를 느꼈는데 간단한 부하테스트를 하기에 가장 적합한 도구이지 않나 생각합니다.


Image

This post is licensed under CC BY 4.0 by the author.

© HeechanN. Some rights reserved.