Cómo optimizar un servidor MTA:SA: rendimiento, tickrate y fin del lag
Guía práctica para optimizar un servidor MTA:SA: ajustar fps_limit, tickrate, scripts Lua, base de datos y red para eliminar lag y mantener 100 jugadores estables.
Un servidor MTA:SA con lag no es un problema único — es un conjunto de cuellos de botella que se manifiesta como rubberbanding, desincronización de disparos, caída de FPS en el cliente y timeouts. La mayoría de las guías en internet recomienda “aumentar la RAM” o “cambiar de host”, lo que rara vez resuelve. El cuello de botella real casi siempre es CPU saturada por scripts Lua mal escritos, sync rate desalineado con el ancho de banda disponible o base de datos bloqueando el main thread.
Este tutorial es para quien administra un servidor MTA:SA — roleplay, deathmatch, freeroam o racing — y quiere dejar de tratar el síntoma y atacar la causa. Vas a aprender a identificar dónde está el cuello de botella, ajustar el mtaserver.conf con base en datos reales, optimizar los resources Lua más pesados y configurar el sistema operativo para entregar paquetes UDP con latencia baja y consistente. Tiempo estimado: 40-60 minutos con el servidor ya instalado.
Antes de cambiar cualquier parámetro, queda claro: optimización sin medición es adivinanza. Cada sección a continuación incluye el comando de diagnóstico antes del cambio y cómo validar el resultado.
Requisitos previos
Servidor MTA:SA 1.6+ corriendo en Linux (Ubuntu 22.04 LTS o Debian 12), acceso SSH con sudo, y al menos una sesión de prueba con 10-20 jugadores o un bot de stress para generar carga representativa. La optimización basada en un servidor vacío es inútil — el comportamiento cambia por completo bajo carga.
22003 UDP 22005 TCP 22126 UDP mta-server64 Ten también acceso al panel o shell donde corre el servidor y al archivo mtaserver.conf. Si administras múltiples servidores compartiendo el mismo hardware, aísla las métricas — CPU steal de un vecino ruidoso puede parecer problema tuyo.
Midiendo el estado actual
Antes de ajustar nada, registra un baseline. Sin números antes/después, no vas a saber si el cambio ayudó.
Conéctate a la consola del servidor (vía terminal local o panel) y habilita el monitor de rendimiento interno:
debugscript 3
showperfEl comando showperf muestra el tiempo medio de procesamiento por frame en milisegundos, separado por categoría (sync, scripts, base de datos). Anota los valores con el servidor vacío y con al menos el 50% de la capacidad ocupada.
En el shell de Linux, captura el uso de CPU por thread del proceso:
top -H -p $(pgrep -f mta-server64)El thread principal (normalmente el primero listado con más CPU) es donde se ejecutan los scripts Lua. Si está al 95-100% y el servidor es multi-core, tienes un cuello de botella single-threaded — común en MTA, que no paraleliza Lua.
Mira el uso de red en tiempo real:
sudo iftop -i eth0 -P -BObserva el ancho de banda usado por jugador conectado. Un servidor sano usa 8-15 KB/s por jugador en estado normal. Por encima de 25 KB/s indica sync rate demasiado alto o broadcast innecesario en scripts.
Guarda esos tres snapshots en un archivo de texto. Son tu referencia para validar cada cambio.
Ajustando el mtaserver.conf
El mtaserver.conf controla los intervalos de sincronización entre cliente y servidor. Valores demasiado agresivos saturan el ancho de banda y la CPU; valores demasiado conservadores generan la sensación de “goma” en el movimiento de los demás jugadores.
Localiza el archivo (normalmente en /home/mta/mods/deathmatch/mtaserver.conf) y ábrelo con el editor de tu preferencia. Busca las secciones de sync.
Configura los intervalos para un servidor de 60-100 jugadores con objetivo de gameplay fluido:
<player_sync_interval>100</player_sync_interval>
<light_sync_interval>1500</light_sync_interval>
<camera_sync_interval>500</camera_sync_interval>
<ped_sync_interval>400</ped_sync_interval>
<unoccupied_vehicle_sync_interval>1000</unoccupied_vehicle_sync_interval>
<keysync_mouse_sync_interval>100</keysync_mouse_sync_interval>
<keysync_analog_sync_interval>100</keysync_analog_sync_interval>Esos valores son el sweet spot para DM y roleplay. Para servidores de racing competitivo, reduce player_sync_interval a 80 y keysync_mouse_sync_interval a 80 — ganas responsividad a costa de ~25% más banda por jugador.
Ajusta el límite de FPS del servidor y la asignación de recursos del simulador físico:
<fps_limit>0</fps_limit>
<server_logic_fps>100</server_logic_fps>
<bandwidth_reduction>none</bandwidth_reduction>fps_limit=0 desactiva el límite en el cliente — esto no afecta directamente al servidor, pero evita que los jugadores se quejen de un cap artificial. server_logic_fps=100 es el valor por defecto y casi nunca hace falta cambiarlo. bandwidth_reduction en “medium” solo vale la pena si tu banda total está reventando.
Al reiniciar el servidor para aplicar estos cambios, los jugadores conectados pierden la sesión. Hazlo en una ventana de mantenimiento avisada o usa refresh + refreshall en la consola para recargar resources sin tirar conexiones — aunque mtaserver.conf exige un restart completo.
Reinicia el servidor y vuelve a medir:
sudo systemctl restart mta-serverRepite showperf e iftop tras 10 minutos de operación con jugadores. Los tiempos de frame deben estar consistentemente por debajo de 12ms en el thread principal.
Optimizando scripts Lua
Los scripts mal escritos son responsables del 70% de los casos de servidor MTA con lag — independiente del hardware. La buena noticia es que se puede identificar y corregir a los peores ofensores rápidamente.
En la consola del servidor, activa el profiler embebido durante 60 segundos con el servidor bajo carga real:
debugscript 3
debug-resources --top 10La salida lista los 10 resources que más consumen CPU en ms por frame. Concéntrate en los 3 primeros — normalmente responden por el 50%+ del consumo total.
Identifica y elimina setTimer con intervalo corto. Este es el anti-pattern más común:
setTimer(function()
for _, player in ipairs(getElementsByType("player")) do
local money = getPlayerMoney(player)
end
end, 50, 0)Ese código corre 20 veces por segundo iterando todos los jugadores. En un servidor con 80 jugadores son 1600 iteraciones por segundo solo para ese timer. Sustitúyelo por eventos disparados bajo demanda:
addEventHandler("onPlayerMoneyChange", root, function()
end)Cachea resultados de getElementsByType y similares. Cada llamada recorre el árbol de elementos del servidor:
local cachedPlayers = {}
addEventHandler("onPlayerJoin", root, function()
table.insert(cachedPlayers, source)
end)
addEventHandler("onPlayerQuit", root, function()
for i, p in ipairs(cachedPlayers) do
if p == source then
table.remove(cachedPlayers, i)
break
end
end
end)Iterar cachedPlayers es 10-30x más rápido que getElementsByType("player") en un loop apretado.
Disparar un evento al root lo envía a todos los clientes, costando banda proporcional al número de jugadores. Usa triggerClientEvent(targetPlayer, ...) o triggerClientEvent(getPlayersInRange(x, y, z, 200), ...) para limitar el broadcast. Servidores roleplay con inventario en UI gastan la mitad de la banda solo por culpa de eventos no dirigidos.
Base de datos y persistencia
SQLite es el default de MTA y normalmente es la mejor opción. Pero la configuración por defecto no habilita Write-Ahead Logging, lo que provoca bloqueos en writes simultáneos.
En la primera carga de la base, activa WAL mode vía SQL:
executeSQLQuery("PRAGMA journal_mode=WAL")
executeSQLQuery("PRAGMA synchronous=NORMAL")
executeSQLQuery("PRAGMA cache_size=-20000")WAL permite lecturas concurrentes con escrituras. synchronous=NORMAL reduce fsync sin comprometer la durabilidad de forma catastrófica — acepta perder el último 1 segundo en caso de crash del servidor. cache_size=-20000 asigna 20 MB de caché a SQLite.
Identifica queries lentas habilitando log temporal:
local startTime = getTickCount()
local result = executeSQLQuery("SELECT * FROM accounts WHERE last_login > ?", os.time() - 86400)
local elapsed = getTickCount() - startTime
if elapsed > 50 then
outputDebugString("Query lenta: " .. elapsed .. "ms")
endCualquier query por encima de 50ms bloquea el main thread y es candidata a optimización — añade índices en columnas de búsqueda o paginación en resultados grandes.
Red y sistema operativo
La capa de red de Linux trae defaults conservadores que no fueron pensados para UDP de juegos. Ajustes correctos reducen la latencia percibida sin cambiar nada en el juego.
Habilita fq_codel como qdisc en la interfaz principal:
sudo tc qdisc replace dev eth0 root fq_codelfq_codel elimina el bufferbloat, manteniendo la latencia consistente incluso con la banda saturada. La diferencia práctica: los jugadores en hora pico ya no ven el ping subir de 30ms a 200ms cuando el tráfico total crece.
Aumenta los buffers de UDP en el kernel:
sudo sysctl -w net.core.rmem_max=8388608
sudo sysctl -w net.core.wmem_max=8388608
sudo sysctl -w net.core.netdev_max_backlog=5000Persístelo en /etc/sysctl.d/99-mta.conf para sobrevivir al reboot. Buffers más grandes absorben ráfagas sin dropear paquetes, que es lo que provoca rubberbanding cuando el link se llena.
Algunas guías antiguas recomiendan tcp_congestion_control=bbr o desactivar GRO/LRO en servidores de juegos. En hardware moderno, eso puede empeorar el rendimiento. Mide siempre antes y después — no copies configuraciones a ciegas de foros.
Verificación
Después de aplicar los cambios, valida con carga real:
showperf
Los tiempos por frame deben estar consistentemente por debajo de 12ms en el main thread, incluso con el servidor lleno. Usa iftop para confirmar que la banda por jugador bajó (si redujiste broadcast innecesario) o se mantuvo estable (si subiste el sync rate por gameplay).
En el cliente, pide a 3-5 jugadores que ejecuten Net Stats (F11 en el menú MTA) y reporten jitter y packet loss durante 10 minutos de gameplay normal. Jitter por debajo de 5ms y cero packet loss es el objetivo.
Resolución de problemas
El servidor se reinicia solo bajo carga
Normalmente es el OOM killer matando el proceso. Comprueba con dmesg | grep -i "killed process". La solución es aumentar RAM o crear swap de 4 GB. Scripts Lua que filtran memoria también lo provocan — usa getResourceUsedMemory para identificar al resource culpable.
Latencia alta solo para algunos jugadores
No es el servidor. Pídeles que ejecuten mtr o WinMTR a la IP del servidor durante el problema. Casi siempre es congestión en la ruta del ISP del jugador. Si el problema es consistente para clientes de un único ISP, considera mover el servidor a una región con mejor peering con ese ISP.
”Server is full” pero hay slots libres
Comprueba MaxPlayers en mtaserver.conf y maxplayers en el archivo de servidor master. Los dos tienen que coincidir. En algunos casos, scripts personalizados implementan un límite propio vía cancelEvent() en onPlayerConnect — busca eso en los resources personalizados.
Próximos pasos
La optimización es un proceso continuo — mide periódicamente, especialmente después de añadir resources nuevos. Algunas direcciones para profundizar:
- Configurar monitoreo a largo plazo con un sistema de métricas externo que grafique frame time y player count a lo largo del día
- Estudiar Lua JIT y estructuras de datos eficientes para scripts que procesan grandes volúmenes (inventario, leaderboards)
- Implementar rate limiting en los eventos que vienen del cliente para prevenir flood malicioso
- Documentar el baseline de cada server hash para detectar regresiones tras updates de resources
Si estás creciendo el servidor más allá de 100 jugadores simultáneos o corriendo múltiples servidores MTA de la misma comunidad, un hosting dedicado de MTA de Hostini ya viene con kernel tuneado para UDP de juegos, protección DDoS en el borde y red con peering optimizado — tres cosas que eliminan el 80% de las causas comunes de lag incluso antes de que se conecte el primer jugador.
Preguntas frecuentes
¿Cuál es el tickrate ideal para un servidor MTA:SA?
MTA:SA no tiene un "tickrate" único — el servidor opera con varios intervalos configurables en mtaserver.conf. Los principales son player_sync_interval (por defecto 100ms), light_sync_interval (1500ms) y camera_sync_interval (500ms). Reducir player_sync_interval a 80ms mejora la fluidez de los tiroteos, pero aumenta el ancho de banda usado en ~25%. No bajes de 60ms sin una red dedicada.
¿Cuántos jugadores aguanta un servidor MTA:SA en una VPS de 4 vCPUs?
Un servidor stock vanilla con pocos scripts personalizados aguanta 80-120 jugadores con 4 vCPUs y 4 GB de RAM. Servidores roleplay (DayZ, RPG con inventario, garaje, etc.) bajan a 40-60 jugadores en el mismo hardware porque el cuello de botella ya no es la red sino la CPU ejecutando Lua. Haz profiling antes de escalar hardware.
¿Por qué mi servidor MTA queda al 60% de CPU incluso vacío?
Los timers Lua mal hechos son la causa #1. Cualquier setTimer con intervalo por debajo de 100ms corriendo en bucle infinito consume CPU incluso sin jugadores. Usa debugscript 3 in-game para ver qué resources están consumiendo CPU en ms por frame. Resources mal escritos pueden quemar 30-40% por sí solos.
¿Compensa usar base de datos externa o basta con SQLite interno?
Para menos de 100 jugadores simultáneos y queries simples, SQLite es más rápido porque elimina la latencia de red. MySQL/MariaDB externo solo compensa cuando tienes múltiples servidores compartiendo datos o queries complejas con JOIN en tablas grandes. Para un servidor único, SQLite con WAL mode es la mejor opción.
¿MTU 1500 o MTU optimizada para MTA:SA?
MTA:SA usa paquetes UDP pequeños (60-400 bytes la mayor parte del tiempo). El MTU por defecto de 1500 funciona bien — no hay ganancia práctica en reducirlo. Lo que importa es eliminar el bufferbloat: habilitar fq_codel o cake como qdisc en Linux marca más diferencia que ajustar MTU.
¿Cómo sé si el lag de los jugadores es del servidor o de su red?
Usa el comando /showperf en la consola del servidor para ver el tiempo de cada frame en ms. Si está por debajo de 16ms de forma consistente, el servidor está sano. El lag reportado en ese escenario es problema de la ruta del jugador. Usa mtrping o WinMTR desde las IPs de los jugadores que se quejan para confirmar pérdida de paquetes en el camino.