MAATRIX GAMES / Блог / SA-MP: лагает при большом онлайне

SA-MP: лагает при большом онлайне

MAATRIX GAMES

Сервер летает на 10-15 игроках, а как только онлайн подбирается к полусотне — начинаются рывки, задержка команд, синхронизация машин съезжает, а иногда сервер вообще подвисает на пару секунд и все чувствуют характерный «скачок». Первая мысль — не хватает RAM или процессора, но в 9 случаях из 10 в SA-MP дело не в железе, а в том, как гейммод на Pawn использует таймеры, циклы и запросы к базе на каждый игровой тик. Разберём, как найти конкретное узкое место и что с ним делать, не переписывая гейммод с нуля.

Почему SA-MP вообще упирается не в RAM, а в CPU

SA-MP — однопоточный движок. Весь гейммод, все filterscript-ы и вся логика крутятся в одном потоке на одном ядре, и это ключевое отличие от современных серверных движков, которые умеют раскидывать нагрузку по ядрам. Сколько бы vCPU ни было выделено под VPS, samp03svr использует эффективно только одно — остальные простаивают, пока не запущены отдельные процессы (например NPC-боты samp-npc, которые действительно отдельные процессы и могут сесть на другое ядро).

Отсюда типичная картина: htop показывает суммарную загрузку 20-30% на 4-ядерной машине, а сервер при этом откровенно лагает — одно конкретное ядро, на котором висит samp03svr, забито на 100%, остальные простаивают. Смотреть нужно не на средний показатель, а на загрузку конкретного процесса:

top -H -p $(pgrep samp03svr)

Флаг -H покажет потоки процесса — если движок однопоточный, будет один поток с загрузкой близкой к 100%, когда сервер тормозит. Это первый диагностический сигнал: если он есть, апгрейд тарифа с бОльшим числом ядер не поможет — дело в самом Pawn-коде гейммода, который не успевает отработать очередной такт.

RAM в SA-MP почти никогда не первопричина лагов при росте онлайна — движок лёгкий по памяти, и даже RP-гейммод с полусотней активных сессий редко выедает больше пары гигабайт. Если free -m показывает, что память упирается в своп (swap растёт, available около нуля) — это отдельная и более редкая проблема, обычно утечка в скрипте (массивы игроков не очищаются при disconnect), а не нормальная нагрузка от онлайна.

Как найти конкретный таймер или цикл, который жрёт такт

В Pawn-гейммодах SA-MP основная нагрузка идёт от SetTimer/SetTimerEx — функций, которые дёргают колбэк с заданным интервалом, и от циклов for/while внутри callback-ов вроде OnPlayerUpdate или OnPlayerStreamIn, которые выполняются очень часто (OnPlayerUpdate вызывается многократно в секунду на каждого онлайн-игрока). Если внутри такого хука висит цикл по всем игрокам или NPC — при 10 онлайне это незаметно, при 60 — уже сотни лишних итераций в такт.

Первый шаг — грубая диагностика без правки кода: временно закомментировать (//) подозрительные таймеры по одному в исходнике .pwn, пересобрать и посмотреть, снимает ли это лаг. Долго, но надёжно там, где нет профилировщика под рукой.

Второй, более быстрый способ — включить встроенное логирование скрипта и время выполнения отдельных функций через GetTickCount():

new tick = GetTickCount();
// тело функции или подозрительного блока
printf("Функция X заняла %d ms", GetTickCount() - tick);

Если конкретный блок регулярно выдаёт десятки миллисекунд — это и есть узкое место. Колбэки в SA-MP отрабатывают синхронно и последовательно для каждого игрока, поэтому один медленный кусок кода при росте онлайна съедает всё больше суммарного времени такта — не потому что стал тяжелее сам, а потому что вызывается чаще.

Частые виновники, которые стоит проверить в первую очередь:

  • Циклы по MAX_PLAYERS в частых колбэках. Если гейммод определяет #define MAX_PLAYERS 500 (дефолт многих старых шаблонов) и где-то есть for(new i = 0; i < MAX_PLAYERS; i++) внутри OnPlayerUpdate или похожего частого хука — цикл гоняется по всем 500 слотам, даже если реально играет 40 человек. Замена на GetPlayerPoolSize() или явную проверку IsPlayerConnected(i) с ранним continue снижает нагрузку в разы.
  • Таймеры с коротким интервалом, повешенные на каждого игрока отдельно. Если при коннекте каждого игрока стартует персональный SetTimerEx с интервалом 100-200 мс (частая история для систем анимаций, HUD-таймеров, античита), при 60 онлайне это 60 параллельно тикающих таймеров вместо одного общего цикла с перебором активных игроков.
  • SQL-запросы внутри игровых callback-ов без пула соединений. Синхронные (блокирующие) запросы к MySQL прямо в OnPlayerUpdate или похожих частых хуках — это гарантированный фриз сервера на время ответа базы, умноженный на число игроков. Решение — только через асинхронный MySQL-плагин (например форки на базе mysql от BlueG с mysql_tquery) и вынос всех запросов из горячих путей в события, которые случаются редко (логин, сохранение по таймеру раз в несколько минут, а не каждый тик).

Про сами SQL-плагины и настройку MySQL под игровые серверы подробнее в статье MySQL для игровых плагинов: настройка.

Поднять сервер SA-MP за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Stream-настройки: когда лагает не логика, а синхронизация мира

Отдельный источник лагов на большом онлайне в SA-MP — не сам Pawn-код, а параметры стриминга объектов и игроков из server.cfg:

stream_distance 300.0
stream_rate 1000
onfoot_rate 40
incar_rate 40
weapon_rate 40

stream_distance определяет, на каком расстоянии игрок видит других игроков и объекты, stream_rate — как часто пересчитывается, кто в кого попадает в радиус видимости. На DM-сервере с 20 игроками дефолтные значения незаметны. На RP-сервере с плотной застройкой (кастомные интерьеры через объекты, десятки NPC, сотни динамических объектов от Incognito Streamer — стандарт де-факто для динамических объектов в SA-MP RP) пересчёт стриминга на каждом такте для полусотни игроков одновременно — заметная часть той самой нагрузки на единственное рабочее ядро. Стоит проверить и собственные настройки радиуса чанков плагина — они конфигурируются отдельно от server.cfg, через дефайны в его инклюде, и слишком мелкий чанк на большой карте объектов даёт больше пересчётов на каждое движение игрока.

Практический компромисс: stream_distance можно аккуратно снизить (например до 250 вместо дефолтных 300) — игроки почти не заметят разницу в видимости, а сервер сделает заметно меньше работы по каждому пересчёту. Резкое занижение до совсем малых значений даёт эффект «объекты появляются из ниоткуда», так что нужен баланс, а не слепое урезание. Подробнее про дальность прорисовки и связанные анти-лаг параметры — в статье View distance и другие настройки анти-лага.

NPC-боты: почему они не бесплатны

samp-npc — отдельный от samp03svr бинарник, каждый NPC-бот запускается как собственный процесс-клиент, который подключается к серверу как игрок. Это и плюс (NPC можно раскидать по разным ядрам, в отличие от основного движка), и источник лагов, если ботов много: каждый NPC создаёт ту же нагрузку на стриминг и синхронизацию, что и живой игрок. Если гейммод поднимает десятки NPC (охрана, транспорт, фермы событий) одновременно с ростом реального онлайна — суммарная нагрузка складывается из игроков и ботов вместе. Проверить, сколько ботов реально висит:

ps aux | grep samp-npc | grep -v grep | wc -l

Если число неожиданно большое — вероятно, где-то в гейммоде баг с повторным спавном NPC без остановки предыдущего процесса (частая история, если ConnectNPC вызывается по таймеру без проверки, не подключён ли уже этот бот).

RAM, своп и когда апгрейд тарифа реально поможет

Хотя первопричина лагов почти всегда в CPU и коде, RAM всё же стоит проверить отдельно, особенно на длинной дистанции (сервер лагает не сразу, а через несколько часов работы после рестарта):

free -m
watch -n 5 'ps -o rss,vsz,cmd -p $(pgrep samp03svr)'

Если RSS процесса samp03svr стабильно растёт со временем без выхода на плато — это утечка памяти в скрипте. Частые причины: массивы данных игроков (например PlayerVehicle[MAX_PLAYERS][20]), не очищаемые в OnPlayerDisconnect, или динамические объекты/зоны, которые никогда не уничтожаются через Destroy*-функции при логическом удалении.

Апгрейд тарифа по CPU (главное — частота одного ядра, раз движок однопоточный) реально помогает только когда top -H подтвердил, что поток samp03svr упирается в 100% по вычислениям, а не ждёт ответа от диска или базы. Если узкое место — блокирующие SQL-запросы, апгрейд CPU не поможет, нужно чинить асинхронность. Таблица условных ориентиров для RP-проектов с уже оптимизированным гейммодом:

ОнлайнRAMvCPU (важна частота, не только число ядер)
до 301 ГБ1
30-602 ГБ1-2
60-1002-4 ГБ2 (с запасом по частоте)
100+ с ботами и активной экономикой4 ГБ+2-4

Это ориентир, а не гарантия — конкретные цифры сильно зависят от качества конкретного гейммода. Про выбор количества ядер под стабильный тикрейт в целом (не только для SA-MP) подробнее в статье Сколько ядер CPU нужно под стабильный тикрейт.

Мониторинг на живом сервере: что смотреть постоянно

Разовая диагностика находит текущую проблему, но лаги на большом онлайне часто приходят волнами (ивент, наплыв игроков в прайм-тайм) — стоит держать под рукой лёгкий постоянный мониторинг, а не гадать заново каждый раз:

# загрузка конкретного процесса раз в 5 секунд
watch -n 5 'top -bn1 -p $(pgrep samp03svr) | tail -3'

# лог сервера в реальном времени — часто содержит warning про долгие операции
tail -f ~/sampserver/server_log.txt

Если гейммод пишет собственные debug-логи (многие RP-фреймворки логируют время сохранения аккаунта или загрузки инвентаря) — держи их тоже во tail -f во время пиковых часов, это часто быстрее находит проблему, чем гадание по коду вслепую. Общие принципы мониторинга разобраны в статье Мониторинг TPS и лагов на игровом сервере.

Что делать, если оптимизация кода невозможна (чужой гейммод без исходников)

Не всегда есть доступ к исходному .pwn — иногда используется готовый скомпилированный .amx без исходников. Прямая оптимизация кода тогда недоступна, остаются обходные пути:

  • Снизить stream_distance/stream_rate в server.cfg — правится без доступа к исходникам гейммода и иногда даёт заметный эффект, если нагрузка от синхронизации, а не от скрипта.
  • Ограничить maxplayers чуть ниже реального пикового спроса — грубо, но честно: лучше стабильные 45 без лагов, чем 60 с рывками у всех.
  • Разнести нагрузку по времени — если тяжёлые NPC-события настраиваются через админ-команды или конфиг фильтрскрипта, сдвинуть их так, чтобы не совпадали с прайм-таймом.
  • Рассмотреть переход на open.mp — форк-преемник SA-MP с современным движком под капотом при совместимости Pawn-гейммодов на уровне API. Он не решит плохой код гейммода, но лучше держит нагрузку на движковом уровне и не тащит 32-битные ограничения. Миграция требует тестирования — не все старые плагины совместимы один в один.

Если стоит выбор между SA-MP и другими вариантами GTA-мультиплеера для нового RP-проекта — сравнение подходов в статье SA-MP, FiveM или RAGE:MP — что выбрать для RP-сервера.

Поднять сервер SA-MP за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Частые вопросы

Больше ядер CPU решит проблему лагов на SA-MP?

Почти никогда. samp03svr однопоточный, лишние ядра простаивают, если на них не запущены отдельные NPC-процессы. Помогает не число ядер, а частота одного, и то только если узкое место реально в вычислениях, а не в блокирующих SQL-запросах.

Как понять, лагает CPU или сеть игрока?

Если лагает у всех сразу и top -H -p $(pgrep samp03svr) показывает поток около 100% — проблема на сервере. Если лагает у одного при нормальной загрузке сервера — это его сеть или клиент, поможет только ping/traceroute с его стороны.

Почему при 40 игроках всё нормально, а на 55 начинаются рывки?

Классический признак нелинейной сложности в коде — цикл или таймер, зависящий от числа игроков, который на малом онлайне незаметен, а на большом упирается в бюджет такта. Ищи циклы по MAX_PLAYERS в частых хуках, как описано выше.

Помогает ли NVMe вместо обычного SSD?

Практически нет — SA-MP почти не читает диск во время игры, дисковая подсистема на лаги при росте онлайна не влияет.

Стоит ли сразу переписывать гейммод под open.mp ради производительности?

Только если проблема в движковых ограничениях SA-MP (32-битность, однопоточность на пределе), а не в багах кода — иначе те же таймеры и циклы дадут те же лаги на новом движке, просто на чуть более высоком онлайне.