Locust와 JMeter로 게임 서버 동시 접속·로드 스트레스 테스트하는 방법
Locust와 JMeter로 게임 서버의 동시 접속, 로그인, 매치메이킹, 전투 API 부하를 재현하는 방법을 정리합니다. 목표 TPS 산정, 시나리오 설계, 지표 분석, 병목 진단과 단계별 장애 대응 기준까지 실무적으로 설명합니다.

게임 서버의 동시 접속 테스트는 단순히 많은 HTTP 요청을 보내는 작업이 아니다. 실제 플레이 흐름을 반영해 로그인·세션 생성·매치메이킹·게임 액션을 조합하고 p95 지연 시간·오류율·서버 자원 지표를 함께 확인해야 병목을 찾을 수 있다. Locust는 Python 기반의 유연한 시나리오 작성에 JMeter는 GUI 기반의 빠른 HTTP/TCP 테스트 구성과 리포트 확인에 적합하다.
게임 서버 로드 테스트는 무엇을 검증해야 하는가?
부하 테스트의 목표는 서버가 동시 사용자 수(CCU)를 버티는지만 확인하는 데 있지 않다. 특정 트래픽 패턴에서 서비스 품질이 어디까지 유지되는지 한계에 도달했을 때 어떤 구성 요소가 먼저 포화되는지 확인하는 일이다.
게임 서버에서는 다음 흐름을 분리해 측정하는 편이 좋다.
| 흐름 | 대표 요청 또는 이벤트 | 주로 확인할 지표 |
|---|---|---|
| 접속 폭주 | 로그인, 토큰 검증, 캐릭터 로딩 | 인증 API TPS, DB 커넥션, 캐시 적중률 |
| 로비 체류 | 친구 목록, 우편함, 상점 조회 | 읽기 API p95, Redis 지연 시간 |
| 매치 시작 | 큐 등록, 룸 생성, 매칭 결과 수신 | 큐 대기 시간, 매치 서버 할당 시간 |
| 인게임 액션 | 이동, 스킬, 판정, 상태 동기화 | 틱 지연, 패킷 유실, CPU 사용률 |
| 종료 및 보상 | 결과 저장, 보상 지급, 랭킹 반영 | DB 쓰기 지연, 중복 지급, 비동기 큐 적체 |
목표 트래픽은 감으로 정하지 말고 실제 서비스 비율과 요청 빈도로 계산한다. 예를 들어 목표 CCU가 10,000명이고 사용자 한 명이 평균 5초마다 하나의 게임 액션 요청을 보낸다면 이 액션만의 평균 초당 요청 수는 다음과 같다.
다만 실제 테스트에서는 평균값만으로 충분하지 않다. 패치 직후 접속, 이벤트 시작, 푸시 알림 발송처럼 짧은 시간에 사용자가 몰리는 상황을 별도 시나리오로 만들어야 한다.
Locust와 JMeter 중 게임 서버 테스트 도구는 무엇을 선택해야 할까?
두 도구는 경쟁 관계라기보다 적용 범위가 다르다.
| 항목 | Locust | Apache JMeter |
|---|---|---|
| 시나리오 작성 | Python 코드 | GUI와 테스트 플랜 XML |
| 강점 | 복잡한 상태 전이, 동적 데이터, 사용자 행동 모델링 | 빠른 HTTP 구성, 팀 내 공유, 기본 리포트 |
| 부하 생성 | 분산 실행 구조 구성 가능 | 분산 테스트 구성 가능 |
| 적합한 대상 | 로그인 후 상태가 바뀌는 게임 API, 커스텀 프로토콜 연동 | REST API, 웹 백엔드, 단순 반복 요청 |
| 주의점 | 코드 품질과 테스트 데이터 관리가 필요 | GUI 실행기를 부하 발생기로 쓰면 자원 낭비가 큼 |
WebSocket, TCP, UDP처럼 HTTP가 아닌 게임 프로토콜은 도구 기본 기능만으로 충분하지 않을 수 있다. 이 경우 프로토콜 전용 클라이언트나 플러그인 사용 여부를 검토하고 서버와 같은 직렬화 규칙·압축·암호화·하트비트 주기를 재현해야 한다. HTTP API만 측정한 결과를 실시간 게임 서버 전체 성능으로 해석하면 안 된다.

게임 서버 동시 접속 테스트 시나리오는 어떻게 설계할까?
1. 실제 사용자 비율과 행동 시간을 정의한다
먼저 운영 데이터 또는 기획 가정으로 사용자 유형을 나눈다. 예를 들어 접속 사용자 중 70%는 로비에 머물고 20%는 매치메이킹을 시도하며 10%는 실제 전투 상태에 있다고 가정할 수 있다.
각 행동에는 현실적인 대기 시간을 넣는다. 요청을 쉬지 않고 반복하면 사용자 행동이 아니라 공격 트래픽을 만들게 된다. Locust에서는 between(1, 3)처럼 사용자별 대기 시간을 두고 JMeter에서는 Timer를 사용한다.
2. 테스트 데이터와 계정을 분리한다
테스트 계정은 운영 계정과 분리하고 반복 실행으로 인한 재화·보상·랭킹 데이터 오염을 막아야 한다. 로그인 토큰이 일회성이면 계정 목록을 준비하거나 테스트 전용 인증 발급 경로를 만들되 운영 환경에는 노출하지 않는다.
다음 데이터는 테스트 전에 준비한다.
- 서로 다른 테스트 계정과 캐릭터 ID
- 매치 가능한 MMR 또는 티어 범위
- 인벤토리, 우편, 보상 검증용 고정 데이터
- 테스트 실행 ID를 포함한 요청 헤더 또는 태그
- 종료 후 정리할 테스트 데이터의 식별 기준
3. 부하 패턴을 나누어 실행한다
한 번의 최대 부하 테스트로 모든 사실을 알 수는 없다. 최소한 다음 네 종류를 분리한다.
| 테스트 종류 | 목적 | 예시 |
|---|---|---|
| 베이스라인 테스트 | 정상 성능 기준선 확보 | 500명 CCU에서 30분 유지 |
| 램프업 테스트 | 증가 구간의 임계점 탐색 | 5분마다 500명씩 증가 |
| 스파이크 테스트 | 접속 폭주 대응 확인 | 1분 안에 0명에서 5,000명 접속 |
| 소크 테스트 | 누수와 큐 적체 탐지 | 목표 CCU에서 4~24시간 유지 |
flowchart LR
A[테스트 계정 준비] --> B[로그인 및 세션 생성]
B --> C[로비 API 호출]
C --> D[매치메이킹 큐 등록]
D --> E[룸 배정 및 게임 액션]
E --> F[결과 저장과 보상 지급]
F --> G[응답 지연·오류·자원 지표 분석]
이 흐름에서 단계별 성공 조건을 따로 둔다. 예를 들어 로그인 성공률은 높지만 룸 배정 지연이 늘어난다면 인증 서버가 아니라 매치메이킹 큐 또는 게임 서버 할당 정책이 병목일 가능성이 높다.
Locust로 로그인과 매치메이킹 부하를 만드는 방법
Locust에서는 한 가상 사용자를 HttpUser 클래스로 표현한다. 아래 예시는 로그인 후 액세스 토큰을 보관하고 로비 조회와 매치메이킹 요청을 서로 다른 비율로 실행한다.
from locust import HttpUser, task, between
from locust.exception import StopUser
class GameUser(HttpUser):
wait_time = between(1, 3)
def on_start(self):
response = self.client.post(
"/v1/auth/login",
json={"accountId": "loadtest-user-001", "password": "test-password"},
name="POST /v1/auth/login",
)
if response.status_code != 200:
response.failure("로그인 실패")
return
self.token = response.json()["accessToken"]
self.headers = {"Authorization": f"Bearer {self.token}"}
@task(4)
def get_lobby(self):
self.client.get(
"/v1/lobby/state",
headers=self.headers,
name="GET /v1/lobby/state",
)
@task(1)
def request_matchmaking(self):
self.client.post(
"/v1/matchmaking/queue",
headers=self.headers,
json={"mode": "ranked", "region": "kr"},
name="POST /v1/matchmaking/queue",
)
실행할 때는 테스트 대상 주소와 사용자 수, 증가 속도를 명시한다.
locust -f locustfile.py --host=https://staging-api.example.game
웹 UI 또는 헤드리스 실행에서 다음 값을 명확히 기록한다.
- Users: 목표 동시 사용자 수
- Spawn rate: 초당 가상 사용자 증가 수
- Run time: 테스트 유지 시간
- 요청별 Name: 엔드포인트와 기능이 구분되는 이름
계정 ID를 코드에 고정한 예시는 구조 설명용이다. 실제 테스트에서는 CSV, 데이터베이스 또는 내부 테스트 데이터 API에서 계정을 읽어 사용자마다 중복되지 않게 할당해야 한다. 하나의 계정으로 동시 로그인하면 세션 교체 정책 때문에 의도하지 않은 실패가 생길 수 있다.
JMeter로 API 부하와 임계값을 확인하는 방법
JMeter는 HTTP API의 빠른 검증에 유용하다. 기본 테스트 플랜은 다음 순서로 구성한다.
Thread Group에서 스레드 수, 램프업 시간, 반복 횟수 또는 지속 시간을 설정한다.HTTP Request Defaults에 대상 호스트와 기본 프로토콜을 등록한다.- 로그인 요청에
JSON Extractor를 연결해 액세스 토큰을 추출한다. - 후속 HTTP 요청의
HTTP Header Manager에Authorization: Bearer ${accessToken}을 설정한다. Uniform Random Timer또는Gaussian Random Timer로 사용자 행동 간격을 둔다.Response Assertion과JSON Assertion으로 상태 코드뿐 아니라 응답 내용도 검증한다.
로그인 응답이 다음과 같다고 가정하자.
{
"accessToken": "eyJhbGciOi...",
"playerId": "p-10001"
}
JSON Extractor의 JSON Path Expressions에 $.accessToken을 입력하고 변수명을 accessToken으로 지정하면 후속 요청에서 ${accessToken}을 사용할 수 있다.
부하 실행은 GUI보다 CLI 모드를 사용한다. GUI Listener는 대량 샘플을 메모리에 보관할 수 있어 부하 생성기 자체의 성능을 떨어뜨릴 수 있다.
jmeter -n -t game-api-load-test.jmx -l results.jtl -e -o report
View Results Tree는 소규모 디버깅에서만 사용하고 본 부하에서는 Aggregate Report, HTML 리포트, 외부 모니터링 시스템을 사용한다.

p95 응답 시간과 오류율은 어떻게 해석해야 하는가?
평균 응답 시간은 일부 느린 요청을 가릴 수 있다. p95가 800ms라는 뜻은 전체 요청의 95%가 800ms 이하였다는 의미이며 나머지 5%는 더 느렸다는 뜻이다. 플레이어 체감 품질을 판단할 때는 평균보다 p95와 p99가 더 유용한 경우가 많다.
오류율은 다음과 같이 계산할 수 있다.
다만 HTTP 200만으로 성공을 판단하지 않는다. 예를 들어 보상 지급 API가 HTTP 200을 반환해도 보상 이벤트가 비동기 큐에서 유실됐다면 기능적으로는 실패다. 응답 코드, 오류 코드, 데이터베이스 반영, 메시지 큐 소비 지연을 함께 확인해야 한다.
다음 조합은 자주 나타나는 이상 신호다.
| 관측 현상 | 가능성 높은 원인 | 우선 확인할 곳 |
|---|---|---|
| RPS 증가와 함께 p95 급등 | DB 커넥션 풀 포화, 락 경합, 캐시 미스 | DB 대기 이벤트, 커넥션 풀, 느린 쿼리 |
| 오류율 상승 후 CPU는 낮음 | 제한 정책, 의존 서비스 오류, 커넥션 고갈 | 429·503 비율, 외부 API, 소켓 수 |
| CPU 100%와 틱 지연 | 게임 로직 연산, 직렬화, GC 압박 | 프로파일러, GC 로그, 스레드 덤프 |
| 메모리만 지속 상승 | 객체 또는 세션 누수 | 힙 덤프, 세션 캐시, 참조 그래프 |
| 큐 길이 증가 | 소비자 처리량 부족, 다운스트림 지연 | 큐 lag, 소비자 수, DB 쓰기 성능 |
병목은 어떤 순서로 진단해야 하는가?
- 먼저 부하 생성기가 목표 트래픽을 실제로 만들었는지 확인한다. 생성기 CPU, 네트워크 대역폭, 분산 실행 노드 수가 부족하면 서버 한계를 측정할 수 없다.
- 요청별 p50·p95·p99와 상태 코드 비율을 본다. 특정 API부터 지연이 시작되는 시점을 찾는다.
- 같은 시간축의 서버 CPU, 메모리, GC, DB 커넥션, 캐시 지연, 메시지 큐 lag를 겹쳐 본다.
- 병목 후보를 하나씩 낮은 부하에서 재현한다. 쿼리 계획, 락, 직렬화 비용, 동기 호출 체인을 확인한다.
- 수정 후에는 같은 테스트 데이터·같은 램프업·같은 배포 버전 조건으로 다시 실행해 결과를 비교한다.
성능 개선 전후 비교는 평균만 남기지 말고 사용자 수와 부하 패턴을 포함해 기록한다. 예를 들어 “3,000 CCU 스파이크에서 로그인 p95가 1.8초에서 420ms로 감소했고 5xx 오류율이 2.1%에서 0.1%로 감소했다”처럼 재현 가능한 형태가 좋다.
자주 묻는 질문 (FAQ)
Locust와 JMeter를 함께 써도 되는가?
가능하다. 복잡한 로그인 상태와 사용자 행동은 Locust로 모델링하고 단순 REST API의 회귀성 부하 테스트나 빠른 수동 구성은 JMeter로 운영하는 방식이 실용적이다. 다만 동일 API를 두 도구로 동시에 때려 결과를 섞으면 원인 분석이 어려우므로 목적과 실행 시간을 분리한다.
목표 CCU만큼 가상 사용자를 만들면 실제 서비스와 같은가?
아니다. CCU는 접속 수이고 서버 부하는 요청 빈도와 행동 분포에 따라 달라진다. 로비 10,000명과 전투 중인 10,000명은 CPU·네트워크·DB 부하가 크게 다르다.
운영 서버에서 스트레스 테스트를 해도 되는가?
일반적으로는 권장하지 않는다. 스테이징 또는 운영과 유사한 격리 환경에서 진행하고 운영 검증이 꼭 필요하다면 트래픽 상한·실행 시간·중단 기준·관계자 공지를 사전에 합의해야 한다.
정리
좋은 게임 서버 부하 테스트는 도구 선택보다 시나리오의 현실성, 관측 지표의 연결, 반복 가능한 비교 조건에 달려 있다. Locust 또는 JMeter로 접속·로비·매치메이킹·인게임 액션을 분리해 재현하고 p95/p99 지연 시간과 오류율을 서버·DB·큐 지표와 같은 시간축에서 분석하자. 그 결과를 배포 전 성능 기준선으로 남기면 패치 이후의 성능 회귀도 훨씬 빠르게 발견할 수 있다.



