본문으로 건너뛰기
중급약 22분

Locust — 파이썬으로 가상 사용자 행동 정의하기

Locust는 파이썬 코드로 가상 사용자의 행동을 정의하는 오픈소스 부하 테스트 도구다. 모듈 16(레슨 3)의 k6와 나란히 놓고, 언어 차이·task 가중치·실시간 웹 UI·분산 실행이라는 네 가지 차별점을 확인한다.

  • #Locust
  • #부하테스트
  • #Python
  • #분산부하

개념

k6를 배웠다면 절반은 안다

모듈 16(레슨 3)에서 k6로 가상 사용자를 만들고 p(95)와 실패율로 결과를 판단하는 법을 배웠다. Locust도 코드로 시나리오를 짠다는 철학은 똑같다. 가장 큰 차이는 언어다 — k6는 JavaScript, Locust는 Python 이다.

이건 취향이 아니라 팀 구성의 문제다. 백엔드가 Django·FastAPI이거나 파이썬이 공용어인 조직에서는 부하 테스트만 JavaScript로 쓰는 게 진입 장벽이 된다. 사내 파이썬 유틸리티(테스트 데이터 생성기, 인증 모듈)를 그대로 import 해 쓸 수 있다는 것도 실무에서 큰 이점이다.

k6 (모듈 16 레슨 3)Locust
언어JavaScriptPython
시나리오 단위export default function() 한 덩어리HttpUser 클래스 + 여러 개의 @task
사용자 행동 분포코드 안에서 직접 분기 처리@task(가중치)로 선언
실행 중 모니터링종료 후 요약 출력이 기본웹 UI에서 실시간 그래프
분산 실행별도 구성/클라우드 서비스--master / --worker 옵션으로 기본 제공

기본 스크립트 구조

k6의 예시와 나란히 놓고 보면 대응 관계가 바로 보인다.

from locust import HttpUser, task, between
 
class ShopUser(HttpUser):
    wait_time = between(1, 3)   # k6의 sleep(1)에 해당
 
    @task(8)                     # 8:2 = 80%가 이 행동
    def view_products(self):
        with self.client.get("/products", catch_response=True) as res:
            if res.elapsed.total_seconds() > 0.5:
                res.failure("응답이 500ms를 넘었다")
 
    @task(2)                     # 20%가 이 행동
    def purchase(self):
        self.client.post("/orders", json={"productId": "P001", "quantity": 1})

HttpUser를 상속한 클래스 하나가 가상 사용자 한 명의 인격 이다. self.client는 요청을 보내는 객체이고, catch_response=True와 res.failure() 조합이 k6의 check() 자리를 대신한다.

task 가중치 — Locust가 특히 편한 지점

k6의 export default function()은 함수 하나가 전부라, "80%는 상품 조회, 20%는 구매"를 만들려면 함수 안에서 난수를 뽑아 직접 분기해야 한다. Locust는 그 분포를 @task(8), @task(2)처럼 선언만 하면 런타임이 알아서 섞어준다.

부하 테스트의 목적은 "현실과 닮은 트래픽"을 재현하는 것이다. 실제 서비스에서 모든 사용자가 결제만 반복하지는 않는다 — 조회가 압도적으로 많고 구매는 소수다. 그 비율이 코드에 드러나면 시나리오를 리뷰하는 개발자도 "이 부하가 우리 서비스와 닮았는지"를 판단할 수 있다.

실시간 웹 UI와 지표 대응

locust -f locustfile.py 로 실행하면 http://localhost:8089에 웹 화면이 뜬다. 사용자 수와 초당 증가율(spawn rate)을 입력하고 시작하면 실행되는 동안 RPS·응답 시간·실패율 그래프가 실시간으로 그려진다. k6가 끝난 뒤 요약을 읽는 방식이라면, Locust는 무너지는 순간을 눈으로 보는 방식에 가깝다. 이름만 다를 뿐 읽어야 할 지표는 모듈 16과 같다.

k6 (모듈 16 레슨 3)Locust 웹 UI의미
http_req_duration avgAverage (ms)평균 응답 시간 — 왜곡되기 쉬움
http_req_duration p(95)95%ile (ms)요청의 95%가 이 시간 안에 끝남
http_req_failedFails / Fail ratio에러 응답 비율
vusUsers동시 가상 사용자 수
—RPS (Requests/s)초당 처리 요청 수

"평균의 함정"도 그대로다. Average가 200ms인데 95%ile이 1.8초라면, 20명 중 1명은 거의 2초를 기다리고 있다는 뜻이다.

분산 실행 — 부하가 커질 때

가상 사용자 수천 명을 만들려면 부하를 만드는 쪽(테스트를 돌리는 장비)이 먼저 한계에 부딪힌다. Locust는 마스터 하나가 여러 워커를 조율하는 구조를 옵션 두 개로 제공한다.

locust -f locustfile.py --master   # 조율하는 쪽 — 웹 UI가 여기서 뜬다
 
# 실제로 부하를 만드는 쪽 — 여러 대에서 반복 실행
locust -f locustfile.py --worker --master-host=10.0.0.5

결과는 마스터의 웹 UI 한 곳에 합쳐져 보인다. 파이썬 환경만 있으면 서버를 늘리는 것만으로 부하를 늘릴 수 있다.

실무에서 왜 필요한가

성능 테스트 도구는 회사마다 다르게 굳어 있다. k6·JMeter만 알고 있으면 "우리 팀은 Locust 씁니다"라는 한마디에 멈추게 된다.

더 중요한 건 도구를 넘나드는 판단력이다. 세 도구를 겪어보면 "무엇이 더 좋은가"가 아니라 팀의 언어, 필요한 부하 규모, 결과를 볼 사람이 누구인가 로 도구를 고르게 된다. 기획자와 함께 그래프를 보며 한계를 확인해야 한다면 Locust의 실시간 UI가, CI에서 매 배포마다 자동으로 돌려야 한다면 k6가 맞다.

실습 과제

과제 1 — 사용자 행동 분포 설계하기 (10분)

커뮤니티 서비스의 부하 시나리오를 Locust로 설계한다. 글 목록 조회 70%, 글 상세 조회 25%, 글 작성 5%가 되도록 @task 가중치를 정하고 wait_time도 함께 지정한다.

해설 보기
from locust import HttpUser, task, between
 
class CommunityUser(HttpUser):
    wait_time = between(2, 5)
 
    @task(70)
    def list_posts(self):
        self.client.get("/posts")
 
    @task(25)
    def read_post(self):
        self.client.get("/posts/1", name="/posts/[id]")
 
    @task(5)
    def write_post(self):
        self.client.post("/posts", json={"title": "부하 테스트", "body": "본문"})

가중치는 합이 100일 필요가 없다 — 상대 비율만 의미가 있다. name="/posts/[id]"는 게시글 ID마다 통계가 쪼개지지 않도록 묶는 옵션으로, 빼면 결과가 URL 수천 줄로 흩어져 지표를 못 읽는다.

과제 2 — 결과 해석하기 (10분)

다음 결과를 보고 이 시스템이 동시 사용자 800명을 감당한다고 볼 수 있는지 판단하고 이유를 두 문장으로 적는다.

Users: 800   RPS: 240
Average: 210ms   95%ile: 1900ms
Fails: 3.1%

과제 3 — 도구 고르기 (8분)

두 팀에 k6와 Locust 중 무엇을 추천할지 고르고 비교 표를 근거로 이유를 적는다.

  1. FastAPI 백엔드에 데이터 엔지니어가 많고, 사내 인증 모듈이 파이썬으로 있는 팀
  2. 프런트엔드 중심 조직. CI에서 매 배포마다 성능 회귀를 자동으로 잡고 싶다

자가 체크리스트

  • HttpUser + @task 구조가 k6의 export default function()과 어떻게 대응하는지 설명할 수 있다
  • @task(가중치)로 현실적인 사용자 행동 분포를 표현할 수 있다
  • Locust 웹 UI의 Average·95%ile·Fails를 k6 지표와 짝지을 수 있다
  • --master / --worker 분산 실행이 왜 필요한지 설명할 수 있다
  • 팀의 언어와 상황에 따라 k6·JMeter·Locust 중 하나를 고를 수 있다

흔한 실수

  • 모든 행동을 @task 하나에 몰아 넣는다. k6와 똑같은 단일 함수 구조가 되어 Locust를 쓰는 이점이 사라진다.
  • wait_time을 지정하지 않는다. 생략하면 가상 사용자가 쉬지 않고 요청을 보내 실제 사람보다 공격적인 트래픽이 된다.
  • URL에 ID가 들어가는 요청에 name을 지정하지 않는다. 통계가 경로마다 쪼개져 p95를 읽을 수 없게 된다.
  • 웹 UI의 Average만 보고 판단한다. 모듈 16(레슨 3)의 평균의 함정은 도구가 바뀌어도 그대로다. 95%ile과 Fails를 함께 본다.
  • 부하가 안 올라가면 서버 탓부터 한다. 부하를 만드는 워커 쪽 CPU가 먼저 포화된 것일 수 있다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
JMeter — 제이미터
많은 사람이 한꺼번에 쓰는 상황을 흉내 내 서비스가 버티는지 보는 무료 도구.
CI — 지속적 통합
코드를 올릴 때마다 자동으로 합쳐서 빌드하고 시험까지 돌리는 방식.
ID — 식별자
무엇 하나를 다른 것과 구별하기 위해 붙인 이름이나 번호.
URL — 인터넷 주소
웹에서 무언가의 위치를 가리키는 주소 한 줄.
p95 — 95 백분위
느린 쪽 5%를 뺀 나머지가 이 안에 들어온다는 뜻. 100번 중 95번이 그 시간 안에 끝났다는 말이다.
CPU — 중앙처리장치
계산을 담당하는 부품. 사람으로 치면 머리다.

용어집 전체 보기

이 레슨의 실습

실습은 별도 저장소에서 진행합니다. 새 탭으로 열립니다.