How to automate load testing on your MU Online server's GameServer
Build a load-testing pipeline that simulates hundreds of simultaneous connections to your MU Online server's ConnectServer and GameServer, measuring latency, CPU/memory usage, and the real limit before launch.
Launching a MU Online server without knowing how many concurrent players it can actually handle is a blind bet: the first day of promotion, when the most people try to log in at once, is exactly the worst time to discover that the ConnectServer chokes at 200 connections or that the database saturate
Launching a MU Online server without knowing how many concurrent players it can actually handle is a blind bet: the first day of promotion, when the most people try to log in at once, is exactly the worst time to discover that the ConnectServer chokes at 200 connections or that the database saturates its pool sooner than expected. This tutorial shows how to build an automated load-testing pipeline, simulating simultaneous connections and actions on the ConnectServer and GameServer, measuring the right indicators, and identifying the real limit of your environment before your players do it for you.
Why test load before launch
Successful promotion (groups, YouTube, partner Discords) tends to generate a concentrated spike of traffic in the first hours after launch — well above the average the server will sustain afterward. If the environment breaks during that spike, players' first impression is terrible and many won't come back to try again. Testing load in advance lets you fix bottlenecks (database configuration, undersized hardware, OS connection limits) while there are still no real players at risk.
Differentiating test types
Not every load test needs to simulate the whole game. Splitting by layer makes it easier to isolate where the bottleneck is:
| Test type | What it simulates | Typical tool |
|---|---|---|
| Connection test (login) | Hundreds of simultaneous handshakes on the ConnectServer | Custom script (Python/C#) emulating the protocol |
| Authentication test | Login + character selection on the GameServer | Script using the emulator's login protocol |
| Gameplay test | Movement, attack, map change, NPC use | Bots that actually play (more expensive) |
| Database test | Read/write concurrency on MEMB_INFO, Character | sysbench or a direct SQL script |
| Network/infra test | Bandwidth, latency, packet loss | iperf3, interface monitoring |
Preparing the staging environment
Never run the load test against the production database/server — the risk of corrupting real data or crashing the server during a promotion is too high. Clone the configuration (same emulator versions, same database configuration, hardware as close as possible) in an isolated environment, with synthetic test data:
CREATE DATABASE mu_staging;
-- Restore a clean/synthetic dump, never real player data
Simulating connections to the ConnectServer
The ConnectServer is the first bottleneck during a traffic spike — it resolves which GameServer the client should use and holds a connection queue. A simple Python script using raw sockets can simulate hundreds of handshakes:
import socket
import threading
import time
def simulate_connection(host, port, results, i):
start = time.time()
try:
s = socket.create_connection((host, port), timeout=5)
s.close()
results.append(time.time() - start)
except Exception as e:
results.append(None)
results = []
threads = []
for i in range(500):
t = threading.Thread(target=simulate_connection, args=("staging.myserver.com", 44405, results, i))
threads.append(t)
t.start()
time.sleep(0.01) # slight spacing to simulate gradual arrival
for t in threads:
t.join()
success = [r for r in results if r is not None]
print(f"Successful connections: {len(success)}/{len(results)}")
print(f"Average latency: {sum(success)/len(success):.3f}s")
This basic test already reveals whether the ConnectServer accepts connections quickly or starts queuing/refusing above a certain volume.
Simulating the full login flow
To measure the real authentication bottleneck (which involves an account database lookup), you need to emulate your emulator's packet protocol — the structure varies between IGCN, MuEMU, and X-Team, but the principle is the same: build the binary login packet and read the response:
import struct
def build_login_packet(account, password):
# Simplified structure — adjust to your emulator's real offsets
body = account.encode('ascii').ljust(10, b'\x00') + password.encode('ascii').ljust(10, b'\x00')
size = len(body) + 4
return struct.pack('BBB', 0xC1, size, 0xF1) + body
Running this packet concurrently against the staging GameServer, measuring the time until a success/failure response, shows the real concurrent authentication capacity — the bottleneck usually shows up here first, not in the raw TCP connection.
Testing database concurrency
Regardless of the network test, the database is typically the real scaling limit. Use sysbench to simulate read/write load equivalent to the server's access pattern (many character reads, few position writes every few seconds):
sysbench oltp_read_write \
--db-driver=mysql --mysql-db=mu_staging \
--mysql-user=test --mysql-password=password \
--tables=10 --table-size=100000 \
--threads=200 --time=120 \
run
Compare the sysbench latency results (p95, p99) with the expected number of concurrent players — if p99 already degrades with 200 simulated threads and your target is 500 CCU, the database (not the GameServer) is the bottleneck to solve first.
Monitoring resources during the test
While the test runs, collect system metrics in parallel to correlate load with resource consumption:
# In a separate window, throughout the whole test:
top -b -n 100 -d 5 | grep -E "GameServer|ConnectServer|mysqld" >> /tmp/load_monitor.log
vmstat 5 100 >> /tmp/load_monitor.log
| Metric | Tool | What it indicates |
|---|---|---|
| GameServer process CPU | top/htop | Game logic processing bottleneck |
| Resident memory | top/ps | Memory leak under sustained load |
| Active MySQL connections | SHOW PROCESSLIST; | Connection pool saturation |
| Disk latency | iostat -x 5 | I/O bottleneck on large databases |
| Packet loss/network latency | iperf3, ping -f | Bandwidth or network configuration limit |
Defining test scenarios (gradual ramp-up)
Instead of firing all "simulated players" at once (which doesn't reflect the real arrival pattern), structure the test as a ramp-up — gradually increasing load and observing at which point each metric starts to degrade:
| Phase | Simulated CCU | Duration | Goal |
|---|---|---|---|
| Warm-up | 50 | 2 min | Confirm baseline with no load |
| Ramp 1 | 150 | 5 min | Minimum launch target |
| Ramp 2 | 300 | 5 min | Expected peak target |
| Ramp 3 | 500 | 5 min | Stress above target (safety margin) |
| Ramp 4 | 700+ | until it breaks | Identify the real breaking point |
Automating the full pipeline execution
A simple orchestrator script chains the steps and saves the timestamped results, so you can compare runs over time (e.g., before and after a configuration optimization):
#!/bin/bash
DATE=$(date +%F_%H%M)
mkdir -p /tests/$DATE
python3 connection_test.py > /tests/$DATE/connection.log &
sysbench oltp_read_write --threads=200 --time=300 run > /tests/$DATE/database.log &
top -b -d 5 -n 60 > /tests/$DATE/resources.log &
wait
echo "Load test $DATE completed. Results in /tests/$DATE/"
Interpreting results and acting on them
The end goal isn't just generating numbers, it's making concrete decisions. A typical analysis result:
- If login latency degrades before the GameServer's CPU saturates, the bottleneck is the database connection pool — increase
max_connectionsin MySQL/MSSQL and adjust the emulator's pool. - If GameServer CPU saturates first, the hardware is undersized for the CCU target — consider a vCPU upgrade or optimizing event logic that runs in a loop.
- If the network degrades with just a few hundred connections, the contracted link may be insufficient for the expected volume — review the plan with your provider.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Test crashes the staging environment quickly | Ramp-up too aggressive from the start | Use a gradual ramp (50 → 150 → 300 → 500) |
| Results aren't reproducible | Staging environment has different hardware than production | Match CPU/RAM/disk specs between staging and production |
| High latency but CPU/memory are fine | Database bottleneck (pool or index) | Run sysbench in isolation and review SHOW PROCESSLIST |
| Simulation script crashes with a socket error | OS connection limit (ulimit) reached on the load-generating machine | Increase ulimit -n on the load-generating machine |
| Test reveals nothing useful | System metrics not collected during the run | Always run top/vmstat/iostat in parallel |
| Production breaks even after an "approved" test | Test only covered the connection layer, not real gameplay | Complement with a bot test simulating real actions |
Load testing checklist
- Isolated staging environment, with specs equivalent to production.
- ConnectServer connection simulation script working.
- Login/authentication simulation script tested.
- Database concurrency test (
sysbench) run and analyzed. - CPU, memory, connection, and disk monitoring collected during the test.
- Gradual load ramp executed until identifying the breaking point.
- Corrective actions defined based on the bottlenecks found.
- Retest performed after adjustments to confirm improvement.
With the real limit of your environment mapped out before launch, you trade production surprises for sizing decisions backed by data. If your server's foundation is still in the initial configuration stage, start with the MU Online server creation tutorial before investing time in advanced load testing.
Frequently asked questions
Do I need real bots logging into the game, or can I just simulate the protocol?
For connection load tests (ConnectServer and login), simulating the network protocol directly with a script is more efficient and scalable than opening hundreds of real clients. To test gameplay behavior (movement, combat, drops), bots that emulate the GameServer protocol are needed, but they're still lighter than the full client.
How many concurrent players should I simulate in the test?
Start with your realistic launch target (e.g., 300 CCU) and go 50-100% beyond it to find the real safety margin. If your goal is 300 online, test up to 450-600 to find out where the system actually breaks.
Can the load test be run on the production server?
Not recommended. Use a staging environment with hardware and configuration as close to production as possible. Testing in production risks corrupting real player data or causing a visible outage during the test.
What exactly should I measure during the test?
At minimum: CPU and memory usage of the GameServer/ConnectServer process, login response time, map-change response time, packet loss/network latency, and database connection usage.
What should I do with the results afterward?
Document the exact point where performance degrades (e.g., 'above 400 CCU, map-change latency exceeds 2s') and use it to size hardware, tune database settings (connection pool), or define a login queue limit before the official launch.