Оптимизация сервера CRMP — мод, MySQL, нагрузка и память
Оптимизация сервера CRMP начинается не с уменьшения графики или постоянных перезапусков, а с поиска конкретного источника нагрузки. Зависания могут вызывать тяжёлые циклы Pawn, слишком частые таймеры, синхронная работа с файлами, медленные MySQL-запросы, несовместимые плагины, большое количество динамических объектов или постепенное накопление неосвобождённых ресурсов.
Правильная оптимизация должна проводиться по измеримым показателям. Сначала фиксируется нормальная работа сервера, затем воспроизводится проблема, измеряются CPU, память, задержки запросов и время выполнения функций. После этого изменяется один компонент и повторяется тест.
В руководстве разобраны оптимизация игрового мода CRMP, серверных скриптов, MySQL-запросов и плагинов, снижение нагрузки на VPS, устранение зависаний, поиск утечек памяти и проверка результата под нагрузкой.
Обслуживание, журналы и резервные копии сервера:
Настройка плагинов и устранение ошибок загрузки:
Настройка и диагностика базы данных:
Что означает оптимизация сервера CRMP
Оптимизация — это уменьшение времени выполнения операций и потребления ресурсов без нарушения игровой логики.
Основные цели
- стабильная работа игрового цикла;
- отсутствие длительных зависаний;
- предсказуемое потребление памяти;
- быстрая загрузка аккаунтов;
- своевременное сохранение данных;
- снижение нагрузки на CPU;
- уменьшение количества запросов MySQL;
- устранение постепенного ухудшения работы;
- стабильность при увеличении онлайна.
Оптимизация не должна приводить к
- потере данных;
- отключению важных проверок;
- ухудшению синхронизации;
- исчезновению объектов;
- редкому сохранению критических операций;
- ослаблению античита;
- скрытию ошибок постоянными перезапусками.
Главный принцип: сначала измерить проблему, затем исправить её и повторно измерить результат.
Какие виды задержек встречаются на CRMP
Серверный фриз
Все игроки одновременно замечают остановку команд, транспорта, таймеров и других игровых систем. Обычно причина находится в Pawn-коде, плагине, синхронной операции или перегруженном VPS.
Высокий сетевой пинг
Игроки продолжают двигаться, но действия приходят с задержкой. Причиной может быть маршрут сети, перегруженный канал или слишком частая синхронизация.
Задержка MySQL
Игровой мир работает, но долго открываются аккаунты, дома, бизнесы, инвентарь или административные журналы.
Постепенное замедление
После запуска сервер работает нормально, но через несколько часов или дней увеличиваются память, количество объектов, таймеров или открытых ресурсов.
Локальная проблема игрока
Зависания наблюдаются только у одного пользователя. Причина может находиться в клиентской сборке, интернете, большом количестве объектов в одной зоне или слабом компьютере.
Кратковременные пики
Сервер зависает во время общего сохранения, загрузки организаций, перезапуска систем или выполнения тяжёлой административной команды.
Фиксация исходных показателей
До изменения кода запишите показатели нормально работающего сервера.
Зафиксируйте
- средний онлайн;
- максимальный онлайн;
- загрузку CPU;
- потребление оперативной памяти;
- использование swap;
- размер базы данных;
- количество запросов в минуту;
- время входа игрока;
- время сохранения аккаунта;
- размер
server_log.txt; - время работы до появления проблемы.
Пример журнала измерений
Дата: 31.07.2026
Онлайн: 42 игрока
CPU процесса: 38%
Память RSS: 612 МБ
Swap: 0 МБ
Вход аккаунта: 180 мс
Общее сохранение: 1,8 секунды
Ошибки MySQL: отсутствуют
Время работы: 14 часов
Повторите измерения
Проверяйте показатели:
- сразу после запуска;
- через один час;
- при среднем онлайне;
- при максимальном онлайне;
- перед зависанием;
- после исправления.
Создание тестовой среды
Тестовая сборка должна содержать
- копию игрового мода;
- копию плагинов;
- отдельный
server.cfg; - другой игровой порт;
- отдельную базу MySQL;
- тестовые аккаунты;
- отдельные журналы;
- закрытый доступ по паролю.
Не подключайте тестовый сервер к рабочей базе
Две сборки могут одновременно изменять одни аккаунты, транспорт и имущество.
Создание копии базы
mysqldump \
--single-transaction \
-u crmp_user \
-p \
crmp_server \
> crmp-test.sql
Импорт в отдельную базу
mysql \
-u crmp_test_user \
-p \
crmp_test \
< crmp-test.sql
Используйте отдельный порт
port 8905
password ТЕСТОВЫЙ_ПАРОЛЬ
Как найти настоящее узкое место
Высокий CPU процесса CRMP
Проверяйте Pawn-циклы, таймеры, callbacks, стриминг и нативные плагины.
Высокий CPU MySQL
Проверяйте медленные запросы, индексы, частые UPDATE и полное сканирование таблиц.
Память процесса постоянно растёт
Проверяйте динамические объекты, таймеры, текстовые элементы, кэш плагинов и версии нативных библиотек.
Высокий I/O диска
Проверяйте журналы, частую запись INI-файлов, дампы, swap и MySQL.
Проблема появляется при входе игроков
Проверяйте загрузку аккаунтов, имущества, динамических объектов и количество последовательных SQL-запросов.
Проблема появляется при общем сохранении
Проверяйте одновременные UPDATE для всех игроков, синхронные операции и слишком большой объём данных.
Диагностика нагрузки на процессор
Просмотр процессов Linux
top
Более удобный монитор
htop
Поиск процесса CRMP
ps aux \
| grep -E "samp|crmp" \
| grep -v grep
Подробные данные процесса
ps -o \
pid,ppid,%cpu,%mem,rss,vsz,etime,cmd \
-p PID
Наблюдение каждую секунду
pidstat \
-p PID \
1
Проверьте
- постоянную загрузку одного ядра;
- пики в определённое время;
- рост нагрузки вместе с онлайном;
- пик при сохранении;
- пик при подключении игрока;
- пик после запуска filterscript;
- другие процессы, забирающие CPU.
Учитывайте одно ядро
Если процесс использует около 100% одного логического ядра, это уже может быть пределом для основной игровой логики, даже если общий CPU восьмиядерного VPS показывает небольшое значение.
Диагностика оперативной памяти
Общая память VPS
free -h
Память процесса
ps -o \
pid,rss,vsz,%mem,etime,cmd \
-p PID
Карта памяти процесса
pmap -x PID \
| tail -n 20
Следите за RSS
RSS показывает объём физической памяти, который фактически использует процесс.
Записывайте значение периодически
while true
do
date
ps -o pid,rss,vsz,%mem,etime \
-p PID
sleep 300
done
Признаки возможной утечки
- память постоянно растёт при одинаковом онлайне;
- после выхода игроков память не стабилизируется;
- рост связан с конкретной командой;
- каждый рестарт filterscript увеличивает память;
- после нескольких загрузок объектов появляется резкий рост;
- swap постепенно заполняется.
Проверка нагрузки на диск
Свободное место
df -h
Свободные inode
df -i
Нагрузка диска
iostat -xz 1
Общая активность системы
vmstat 1
Размер каталогов
du -sh \
/home/crmp/server/*
Поиск больших файлов
find /home/crmp \
-type f \
-size +100M \
-print
Источники лишней записи
- слишком подробный
server_log.txt; - логирование каждого движения игрока;
- частое сохранение INI;
- общий MySQL-журнал;
- частые резервные дампы;
- core-файлы после падений;
- активное использование swap.
Проверка сети и игрового порта
Просмотр UDP-портов
sudo ss -lunp
Сетевой трафик интерфейсов
sar -n DEV 1
Проверка потерь до сервера
ping IP_СЕРВЕРА
Диагностика маршрута
mtr IP_СЕРВЕРА
Отделяйте сетевую проблему от серверной
- если зависают все игровые системы одновременно — проверяйте сервер;
- если один игрок имеет высокий пинг — проверяйте его маршрут;
- если проблема появляется только вечером — проверяйте канал и хостинг;
- если растёт исходящий трафик — проверяйте интервалы синхронизации;
- если порт недоступен — проверяйте процесс и firewall.
Измерение времени выполнения Pawn-кода
Для поиска медленных участков можно временно измерять время функций.
Простой пример
new startedAt = GetTickCount();
LoadPlayerInventory(playerid);
new elapsedTime =
GetTickCount() - startedAt;
if (elapsedTime > 10)
{
printf(
"[PERFORMANCE] LoadPlayerInventory: %d ms",
elapsedTime
);
}
Измеряйте
- вход игрока;
- загрузку персонажа;
- загрузку транспорта;
- общее сохранение;
- обработку команд;
- создание объектов;
- работу таймеров;
- античит-проверки;
- административные отчёты.
Не оставляйте чрезмерный отладочный вывод
Если измерение выполняется сотни раз в секунду, постоянный printf сам создаст нагрузку. Записывайте только превышение установленного порога.
Поиск тяжёлых callbacks
Особого внимания требуют
OnPlayerUpdate;- частые таймеры;
- обработчики команд;
- загрузка аккаунта;
- сохранение всех игроков;
- обработка динамических зон;
- массовое создание объектов;
- обработка игровых событий.
OnPlayerUpdate вызывается очень часто
Не размещайте в нём:
- полные циклы по всем игрокам;
- SQL-запросы;
- чтение файлов;
- формирование больших строк;
- пересчёт всей экономики;
- повторное создание интерфейсов;
- тяжёлые геометрические проверки без ограничений.
Переносите действия
- в событие изменения состояния;
- в более редкий общий таймер;
- в callback завершения запроса;
- в кэшируемую функцию;
- в плагин, если для задачи существует надёжная реализация.
Оптимизация циклов Pawn
Не перебирайте максимальный размер без необходимости
Неэффективный вариант:
for (
new i = 0;
i < MAX_SERVER_ITEMS;
i++
)
{
if (!ItemData[i][itemExists])
{
continue;
}
UpdateItem(i);
}
Если из 50 000 слотов используются только 300, большая часть итераций выполняется впустую.
Храните список активных элементов
for (
new index = 0;
index < g_ActiveItemCount;
index++
)
{
new itemid =
g_ActiveItems[index];
UpdateItem(itemid);
}
Выносите неизменяемые значения из цикла
Не вызывайте повторно одну и ту же тяжёлую функцию внутри каждой итерации, если её результат не меняется.
Завершайте поиск после нахождения результата
for (new i = 0; i < count; i++)
{
if (Items[i] == searchedItem)
{
foundIndex = i;
break;
}
}
Не создавайте вложенные циклы без оценки
Цикл по 500 игрокам внутри цикла по 10 000 объектов может привести к миллионам проверок за одно выполнение.
Оптимизация циклов по игрокам
Проверяйте только подключённых игроков
for (
new playerid = 0;
playerid < MAX_PLAYERS;
playerid++
)
{
if (!IsPlayerConnected(playerid))
{
continue;
}
ProcessConnectedPlayer(playerid);
}
Для частых операций используйте список активных игроков
Итератор или собственный список позволяет не проверять пустые слоты.
Не запускайте один и тот же полный цикл
- для каждого подключённого игрока;
- в каждом обновлении позиции;
- для каждого динамического объекта;
- несколько раз в одном callback;
- для простой проверки одного ID.
Разделяйте обработку по времени
Вместо одновременной проверки всех игроков можно обрабатывать часть списка на каждом запуске общего таймера.
Оптимизация серверных таймеров
Типичные проблемы
- отдельный частый таймер для каждого игрока;
- таймер продолжает работать после выхода;
- несколько одинаковых таймеров запускаются повторно;
- таймер выполняет полный цикл по серверу;
- интервал установлен в несколько миллисекунд без необходимости;
- callback таймера выполняется дольше его интервала.
Плохая архитектура
SetTimerEx(
"UpdatePlayerData",
100,
true,
"i",
playerid
);
При большом онлайне создаётся множество частых таймеров.
Практичнее использовать общий таймер
SetTimer(
"ProcessPlayers",
1000,
true
);
Распределяйте тяжёлую работу
Например, не сохраняйте всех игроков в одну миллисекунду. Обрабатывайте несколько аккаунтов за цикл.
Храните идентификаторы таймеров
При отключении игрока или удалении объекта завершайте связанные повторяющиеся таймеры.
Оптимизация строк и форматирования
Не формируйте строку без необходимости
Если сообщение не будет отправлено, сначала выполните проверки, а затем вызывайте format.
Используйте подходящий размер буфера
Слишком маленький буфер обрежет строку, а огромный локальный массив внутри часто вызываемой функции увеличит потребление стека.
Не объединяйте строки многократно в цикле
Для большого административного списка лучше использовать постраничный вывод или заранее рассчитанный буфер.
Не форматируйте одинаковый текст для каждого игрока
Если сообщение одинаковое, сформируйте его один раз и затем отправьте получателям.
Не храните большие временные строки глобально без необходимости
Разделяйте постоянные данные и временные буферы.
Оптимизация журналирования
Не записывайте каждое игровое обновление
Лог позиции каждого игрока несколько раз в секунду быстро создаст большой файл и нагрузку на диск.
Логируйте важные события
- ошибки;
- авторизацию;
- административные действия;
- экономические операции;
- наказания;
- сбои сохранения;
- медленные функции;
- подозрительные действия.
Используйте уровни журналирования
ERROR
WARNING
INFO
DEBUG
TRACE
На рабочем сервере
Оставляйте необходимые уровни, а подробный DEBUG включайте временно для диагностики.
Настройте ротацию
Старые журналы следует архивировать и удалять по установленному сроку, чтобы они не заполнили диск.
Оптимизация сервера CRMP начинается не с уменьшения графики или постоянных перезапусков, а с поиска конкретного источника нагрузки. Зависания могут вызывать тяжёлые циклы Pawn, слишком частые таймеры, синхронная работа с файлами, медленные MySQL-запросы, несовместимые плагины, большое количество динамических объектов или постепенное накопление неосвобождённых ресурсов.
Правильная оптимизация должна проводиться по измеримым показателям. Сначала фиксируется нормальная работа сервера, затем воспроизводится проблема, измеряются CPU, память, задержки запросов и время выполнения функций. После этого изменяется один компонент и повторяется тест.
В руководстве разобраны оптимизация игрового мода CRMP, серверных скриптов, MySQL-запросов и плагинов, снижение нагрузки на VPS, устранение зависаний, поиск утечек памяти и проверка результата под нагрузкой.
Обслуживание, журналы и резервные копии сервера:
Настройка плагинов и устранение ошибок загрузки:
Настройка и диагностика базы данных:
Что означает оптимизация сервера CRMP
Оптимизация — это уменьшение времени выполнения операций и потребления ресурсов без нарушения игровой логики.
Основные цели
- стабильная работа игрового цикла;
- отсутствие длительных зависаний;
- предсказуемое потребление памяти;
- быстрая загрузка аккаунтов;
- своевременное сохранение данных;
- снижение нагрузки на CPU;
- уменьшение количества запросов MySQL;
- устранение постепенного ухудшения работы;
- стабильность при увеличении онлайна.
Оптимизация не должна приводить к
- потере данных;
- отключению важных проверок;
- ухудшению синхронизации;
- исчезновению объектов;
- редкому сохранению критических операций;
- ослаблению античита;
- скрытию ошибок постоянными перезапусками.
Главный принцип: сначала измерить проблему, затем исправить её и повторно измерить результат.
Какие виды задержек встречаются на CRMP
Серверный фриз
Все игроки одновременно замечают остановку команд, транспорта, таймеров и других игровых систем. Обычно причина находится в Pawn-коде, плагине, синхронной операции или перегруженном VPS.
Высокий сетевой пинг
Игроки продолжают двигаться, но действия приходят с задержкой. Причиной может быть маршрут сети, перегруженный канал или слишком частая синхронизация.
Задержка MySQL
Игровой мир работает, но долго открываются аккаунты, дома, бизнесы, инвентарь или административные журналы.
Постепенное замедление
После запуска сервер работает нормально, но через несколько часов или дней увеличиваются память, количество объектов, таймеров или открытых ресурсов.
Локальная проблема игрока
Зависания наблюдаются только у одного пользователя. Причина может находиться в клиентской сборке, интернете, большом количестве объектов в одной зоне или слабом компьютере.
Кратковременные пики
Сервер зависает во время общего сохранения, загрузки организаций, перезапуска систем или выполнения тяжёлой административной команды.
Фиксация исходных показателей
До изменения кода запишите показатели нормально работающего сервера.
Зафиксируйте
- средний онлайн;
- максимальный онлайн;
- загрузку CPU;
- потребление оперативной памяти;
- использование swap;
- размер базы данных;
- количество запросов в минуту;
- время входа игрока;
- время сохранения аккаунта;
- размер
server_log.txt; - время работы до появления проблемы.
Пример журнала измерений
Дата: 31.07.2026
Онлайн: 42 игрока
CPU процесса: 38%
Память RSS: 612 МБ
Swap: 0 МБ
Вход аккаунта: 180 мс
Общее сохранение: 1,8 секунды
Ошибки MySQL: отсутствуют
Время работы: 14 часов
Повторите измерения
Проверяйте показатели:
- сразу после запуска;
- через один час;
- при среднем онлайне;
- при максимальном онлайне;
- перед зависанием;
- после исправления.
Создание тестовой среды
Тестовая сборка должна содержать
- копию игрового мода;
- копию плагинов;
- отдельный
server.cfg; - другой игровой порт;
- отдельную базу MySQL;
- тестовые аккаунты;
- отдельные журналы;
- закрытый доступ по паролю.
Не подключайте тестовый сервер к рабочей базе
Две сборки могут одновременно изменять одни аккаунты, транспорт и имущество.
Создание копии базы
mysqldump \
--single-transaction \
-u crmp_user \
-p \
crmp_server \
> crmp-test.sql
Импорт в отдельную базу
mysql \
-u crmp_test_user \
-p \
crmp_test \
< crmp-test.sql
Используйте отдельный порт
port 8905
password ТЕСТОВЫЙ_ПАРОЛЬ
Как найти настоящее узкое место
Высокий CPU процесса CRMP
Проверяйте Pawn-циклы, таймеры, callbacks, стриминг и нативные плагины.
Высокий CPU MySQL
Проверяйте медленные запросы, индексы, частые UPDATE и полное сканирование таблиц.
Память процесса постоянно растёт
Проверяйте динамические объекты, таймеры, текстовые элементы, кэш плагинов и версии нативных библиотек.
Высокий I/O диска
Проверяйте журналы, частую запись INI-файлов, дампы, swap и MySQL.
Проблема появляется при входе игроков
Проверяйте загрузку аккаунтов, имущества, динамических объектов и количество последовательных SQL-запросов.
Проблема появляется при общем сохранении
Проверяйте одновременные UPDATE для всех игроков, синхронные операции и слишком большой объём данных.
Диагностика нагрузки на процессор
Просмотр процессов Linux
top
Более удобный монитор
htop
Поиск процесса CRMP
ps aux \
| grep -E "samp|crmp" \
| grep -v grep
Подробные данные процесса
ps -o \
pid,ppid,%cpu,%mem,rss,vsz,etime,cmd \
-p PID
Наблюдение каждую секунду
pidstat \
-p PID \
1
Проверьте
- постоянную загрузку одного ядра;
- пики в определённое время;
- рост нагрузки вместе с онлайном;
- пик при сохранении;
- пик при подключении игрока;
- пик после запуска filterscript;
- другие процессы, забирающие CPU.
Учитывайте одно ядро
Если процесс использует около 100% одного логического ядра, это уже может быть пределом для основной игровой логики, даже если общий CPU восьмиядерного VPS показывает небольшое значение.
Диагностика оперативной памяти
Общая память VPS
free -h
Память процесса
ps -o \
pid,rss,vsz,%mem,etime,cmd \
-p PID
Карта памяти процесса
pmap -x PID \
| tail -n 20
Следите за RSS
RSS показывает объём физической памяти, который фактически использует процесс.
Записывайте значение периодически
while true
do
date
ps -o pid,rss,vsz,%mem,etime \
-p PID
sleep 300
done
Признаки возможной утечки
- память постоянно растёт при одинаковом онлайне;
- после выхода игроков память не стабилизируется;
- рост связан с конкретной командой;
- каждый рестарт filterscript увеличивает память;
- после нескольких загрузок объектов появляется резкий рост;
- swap постепенно заполняется.
Проверка нагрузки на диск
Свободное место
df -h
Свободные inode
df -i
Нагрузка диска
iostat -xz 1
Общая активность системы
vmstat 1
Размер каталогов
du -sh \
/home/crmp/server/*
Поиск больших файлов
find /home/crmp \
-type f \
-size +100M \
-print
Источники лишней записи
- слишком подробный
server_log.txt; - логирование каждого движения игрока;
- частое сохранение INI;
- общий MySQL-журнал;
- частые резервные дампы;
- core-файлы после падений;
- активное использование swap.
Проверка сети и игрового порта
Просмотр UDP-портов
sudo ss -lunp
Сетевой трафик интерфейсов
sar -n DEV 1
Проверка потерь до сервера
ping IP_СЕРВЕРА
Диагностика маршрута
mtr IP_СЕРВЕРА
Отделяйте сетевую проблему от серверной
- если зависают все игровые системы одновременно — проверяйте сервер;
- если один игрок имеет высокий пинг — проверяйте его маршрут;
- если проблема появляется только вечером — проверяйте канал и хостинг;
- если растёт исходящий трафик — проверяйте интервалы синхронизации;
- если порт недоступен — проверяйте процесс и firewall.
Измерение времени выполнения Pawn-кода
Для поиска медленных участков можно временно измерять время функций.
Простой пример
new startedAt = GetTickCount();
LoadPlayerInventory(playerid);
new elapsedTime =
GetTickCount() - startedAt;
if (elapsedTime > 10)
{
printf(
"[PERFORMANCE] LoadPlayerInventory: %d ms",
elapsedTime
);
}
Измеряйте
- вход игрока;
- загрузку персонажа;
- загрузку транспорта;
- общее сохранение;
- обработку команд;
- создание объектов;
- работу таймеров;
- античит-проверки;
- административные отчёты.
Не оставляйте чрезмерный отладочный вывод
Если измерение выполняется сотни раз в секунду, постоянный printf сам создаст нагрузку. Записывайте только превышение установленного порога.
Поиск тяжёлых callbacks
Особого внимания требуют
OnPlayerUpdate;- частые таймеры;
- обработчики команд;
- загрузка аккаунта;
- сохранение всех игроков;
- обработка динамических зон;
- массовое создание объектов;
- обработка игровых событий.
OnPlayerUpdate вызывается очень часто
Не размещайте в нём:
- полные циклы по всем игрокам;
- SQL-запросы;
- чтение файлов;
- формирование больших строк;
- пересчёт всей экономики;
- повторное создание интерфейсов;
- тяжёлые геометрические проверки без ограничений.
Переносите действия
- в событие изменения состояния;
- в более редкий общий таймер;
- в callback завершения запроса;
- в кэшируемую функцию;
- в плагин, если для задачи существует надёжная реализация.
Оптимизация циклов Pawn
Не перебирайте максимальный размер без необходимости
Неэффективный вариант:
for (
new i = 0;
i < MAX_SERVER_ITEMS;
i++
)
{
if (!ItemData[i][itemExists])
{
continue;
}
UpdateItem(i);
}
Если из 50 000 слотов используются только 300, большая часть итераций выполняется впустую.
Храните список активных элементов
for (
new index = 0;
index < g_ActiveItemCount;
index++
)
{
new itemid =
g_ActiveItems[index];
UpdateItem(itemid);
}
Выносите неизменяемые значения из цикла
Не вызывайте повторно одну и ту же тяжёлую функцию внутри каждой итерации, если её результат не меняется.
Завершайте поиск после нахождения результата
for (new i = 0; i < count; i++)
{
if (Items[i] == searchedItem)
{
foundIndex = i;
break;
}
}
Не создавайте вложенные циклы без оценки
Цикл по 500 игрокам внутри цикла по 10 000 объектов может привести к миллионам проверок за одно выполнение.
Оптимизация циклов по игрокам
Проверяйте только подключённых игроков
for (
new playerid = 0;
playerid < MAX_PLAYERS;
playerid++
)
{
if (!IsPlayerConnected(playerid))
{
continue;
}
ProcessConnectedPlayer(playerid);
}
Для частых операций используйте список активных игроков
Итератор или собственный список позволяет не проверять пустые слоты.
Не запускайте один и тот же полный цикл
- для каждого подключённого игрока;
- в каждом обновлении позиции;
- для каждого динамического объекта;
- несколько раз в одном callback;
- для простой проверки одного ID.
Разделяйте обработку по времени
Вместо одновременной проверки всех игроков можно обрабатывать часть списка на каждом запуске общего таймера.
Оптимизация серверных таймеров
Типичные проблемы
- отдельный частый таймер для каждого игрока;
- таймер продолжает работать после выхода;
- несколько одинаковых таймеров запускаются повторно;
- таймер выполняет полный цикл по серверу;
- интервал установлен в несколько миллисекунд без необходимости;
- callback таймера выполняется дольше его интервала.
Плохая архитектура
SetTimerEx(
"UpdatePlayerData",
100,
true,
"i",
playerid
);
При большом онлайне создаётся множество частых таймеров.
Практичнее использовать общий таймер
SetTimer(
"ProcessPlayers",
1000,
true
);
Распределяйте тяжёлую работу
Например, не сохраняйте всех игроков в одну миллисекунду. Обрабатывайте несколько аккаунтов за цикл.
Храните идентификаторы таймеров
При отключении игрока или удалении объекта завершайте связанные повторяющиеся таймеры.
Оптимизация строк и форматирования
Не формируйте строку без необходимости
Если сообщение не будет отправлено, сначала выполните проверки, а затем вызывайте format.
Используйте подходящий размер буфера
Слишком маленький буфер обрежет строку, а огромный локальный массив внутри часто вызываемой функции увеличит потребление стека.
Не объединяйте строки многократно в цикле
Для большого административного списка лучше использовать постраничный вывод или заранее рассчитанный буфер.
Не форматируйте одинаковый текст для каждого игрока
Если сообщение одинаковое, сформируйте его один раз и затем отправьте получателям.
Не храните большие временные строки глобально без необходимости
Разделяйте постоянные данные и временные буферы.
Оптимизация журналирования
Не записывайте каждое игровое обновление
Лог позиции каждого игрока несколько раз в секунду быстро создаст большой файл и нагрузку на диск.
Логируйте важные события
- ошибки;
- авторизацию;
- административные действия;
- экономические операции;
- наказания;
- сбои сохранения;
- медленные функции;
- подозрительные действия.
Используйте уровни журналирования
ERROR
WARNING
INFO
DEBUG
TRACE
На рабочем сервере
Оставляйте необходимые уровни, а подробный DEBUG включайте временно для диагностики.
Настройте ротацию
Старые журналы следует архивировать и удалять по установленному сроку, чтобы они не заполнили диск.
Как искать утечки памяти
Под утечкой понимается устойчивый рост памяти из-за ресурсов, которые создаются повторно, но не освобождаются.
Сначала подтвердите рост
- Запустите сервер.
- Запишите RSS процесса.
- Выполните одинаковый набор действий.
- Отключите тестовых игроков.
- Повторите действия несколько раз.
- Сравните память после каждого цикла.
Пример тестового цикла
- подключить 20 тестовых игроков;
- авторизовать их;
- открыть интерфейсы;
- создать временный транспорт;
- запустить работу;
- отключить всех игроков;
- повторить цикл.
Если память растёт одинаковыми ступенями
Ищите ресурс, который создаётся во время каждого цикла и не уничтожается после его завершения.
Не путайте утечку с кэшем
Кэш может вырасти до определённого предела, а затем стабилизироваться. Утечка обычно продолжает расти при повторении действий.
Накопление ресурсов игрового мода
Даже без классической утечки памяти сервер может постепенно накапливать игровые сущности.
Проверяйте создание и удаление
- таймеров;
- динамических объектов;
- пикапов;
- зон;
- текстовых надписей;
- PlayerTextDraw;
- временного транспорта;
- NPC;
- динамических актёров;
- кэшированных результатов запросов.
Типичная ошибка
public ShowPlayerInterface(playerid)
{
CreatePlayerInterface(playerid);
ShowPlayerInterfaceElements(playerid);
return 1;
}
При каждом открытии создаётся новый комплект интерфейса.
Практичнее
if (!g_PlayerInterfaceCreated[playerid])
{
CreatePlayerInterface(playerid);
g_PlayerInterfaceCreated[playerid] =
true;
}
ShowPlayerInterfaceElements(playerid);
При отключении игрока
- уничтожьте его персональные ресурсы;
- завершите повторяющиеся таймеры;
- очистите массивы;
- сбросьте флаги существования;
- отмените незавершённые операции, если API это поддерживает.
Поиск утечек внутри серверных плагинов
Признаки проблемы плагина
- рост памяти начался после обновления;
- рост продолжается при пустом сервере;
- отключение конкретного filterscript не помогает;
- проблема исчезает без одного плагина;
- повторная загрузка системы увеличивает память;
- появляются ошибки нативных функций.
Порядок проверки
- Сохраните рабочую сборку.
- Верните предыдущую версию плагина.
- Повторите нагрузочный тест.
- Отключите необязательные вызовы плагина.
- Проверьте официальный список исправлений.
- Подготовьте минимальный тестовый мод.
Valgrind используйте только на тестовой среде
valgrind \
--leak-check=full \
--show-leak-kinds=all \
./samp03svr-cr
Valgrind значительно замедляет процесс и не подходит для обычной работы публичного сервера.
Для отчёта разработчику подготовьте
- версию операционной системы;
- архитектуру ядра;
- версию плагина;
- минимальный пример;
- последовательность воспроизведения;
- график роста памяти;
- журнал ошибок.
Поиск причины полного зависания
Если процесс работает, но сервер не отвечает
Проверьте:
- загрузку одного ядра;
- активность диска;
- swap;
- соединения MySQL;
- последние строки журнала;
- активный общий таймер;
- недавно выполненную команду;
- недавно установленный плагин.
Просмотр журнала
tail -n 200 \
server_log.txt
Проверка MySQL
SHOW FULL PROCESSLIST;
Проверка системной нагрузки
top
vmstat 1
iostat -xz 1
Краткая трассировка системных вызовов
sudo strace \
-f \
-tt \
-T \
-p PID
Типичные причины полного фриза
- бесконечный цикл;
- слишком большой цикл за один callback;
- синхронное ожидание внешнего сервиса;
- зависший нативный плагин;
- массовое сохранение;
- блокировка базы;
- активное использование swap;
- исчерпание места на диске.
Полезные команды мониторинга Linux
CPU и процессы
top
htop
pidstat -p PID 1
Память
free -h
ps -o \
pid,rss,vsz,%mem,etime,cmd \
-p PID
pmap -x PID
Диск
df -h
df -i
iostat -xz 1
du -sh /home/crmp/server/*
Сеть
sudo ss -lunp
sar -n DEV 1
Служба MySQL
sudo systemctl status mysql
sudo journalctl \
-u mysql \
-n 100 \
--no-pager
Последние ошибки сервера
grep -i \
-E "error|failed|runtime|warning|denied" \
server_log.txt \
| tail -n 100
Не устанавливайте инструменты наугад
Перед использованием на рабочем VPS проверьте назначение команды и создаваемую ею нагрузку.
Нагрузочное тестирование сервера CRMP
Оптимизацию необходимо проверять в условиях, приближенных к реальной работе.
Сценарии теста
- массовое подключение игроков;
- одновременная авторизация;
- загрузка личного транспорта;
- массовое появление в одной зоне;
- работа большого количества объектов;
- общее сохранение;
- открытие административных логов;
- перезапуск игровых систем;
- длительная работа без рестарта.
Во время теста записывайте
- CPU процесса;
- CPU MySQL;
- RSS памяти;
- использование swap;
- время SQL-запросов;
- размер журналов;
- время выполнения Pawn-функций;
- задержку команд;
- ошибки и предупреждения.
Сравнивайте одинаковые условия
Нельзя объективно сравнить две версии, если в одном тесте было 10 игроков, а в другом — 60 и массовое событие.
Проверяйте длительную стабильность
Короткий тест не обнаружит постепенное накопление объектов, таймеров или памяти. Оставляйте тестовую сборку работать несколько часов с повторяющимися сценариями.
Контрольный список оптимизации CRMP
Диагностика
- зафиксированы исходные показатели;
- проблема воспроизводится;
- определён процесс с высокой нагрузкой;
- проверены CPU, память, диск и сеть;
- проверены последние журналы;
- создана тестовая среда.
Pawn-код
- измерено время тяжёлых функций;
- нет SQL в частых callbacks;
- нет ненужных полных циклов;
- таймеры не дублируются;
- работа распределена по времени;
- не создаются повторные интерфейсы;
- временные ресурсы уничтожаются.
MySQL
- проверен журнал медленных запросов;
- тяжёлые запросы проверены через EXPLAIN;
- созданы необходимые индексы;
- нет лишнего SELECT *;
- устранены N+1 запросы;
- используются асинхронные операции;
- сохранение не выполняется всем одновременно;
- связанные операции используют транзакции.
Плагины
- версии записаны;
- проверена совместимость;
- нет ненужных расширений;
- проверены зависимости;
- после обновления выполнен тест;
- сохранены предыдущие рабочие версии.
server.cfg
- параметры синхронизации не изменены вслепую;
- измерена нагрузка до и после;
- проверена отзывчивость;
- проверен сетевой трафик;
- проверена работа транспорта и объектов.
Память
- RSS записывается во времени;
- повторён одинаковый тестовый цикл;
- проверено освобождение ресурсов игроков;
- проверены динамические объекты;
- проверены таймеры;
- проверены нативные плагины;
- рост памяти стабилизируется.
Результат
- сравнены одинаковые сценарии;
- нагрузка действительно снизилась;
- игровая логика не нарушена;
- данные сохраняются;
- нет новых ошибок;
- создана резервная копия рабочей версии.
Частые вопросы по оптимизации сервера CRMP
Почему пустой сервер использует много CPU?
Проверьте частые таймеры, бесконечные циклы, обработку объектов, filterscripts и несовместимые плагины.
Почему нагрузка растёт вместе с онлайном?
Чаще всего увеличивается число циклов по игрокам, запросов MySQL, объектов, интерфейсов и операций синхронизации.
Почему сервер зависает при входе игрока?
Проверьте количество запросов при авторизации, загрузку имущества, создание объектов и выполнение тяжёлых функций в callback подключения.
Почему сервер зависает во время сохранения?
Возможно, все аккаунты одновременно отправляют большое количество UPDATE или выполняется синхронная операция.
Нужно ли сохранять игроков каждую секунду?
Нет. Критические операции сохраняются сразу, остальные данные — периодически и распределённо.
Как найти медленную Pawn-функцию?
Временно измеряйте время через GetTickCount и записывайте вызовы, превысившие заданный порог.
Можно ли выполнять MySQL-запрос в OnPlayerUpdate?
Не следует. Callback вызывается слишком часто. Перенесите запрос в событие изменения данных или редкий таймер.
Что означает N+1 запросов?
Один запрос получает список записей, после чего для каждой записи выполняется дополнительный запрос.
Как найти медленные SQL-запросы?
Включить журнал медленных запросов и проверить найденные команды через EXPLAIN.
Нужно ли добавлять индекс на каждый столбец?
Нет. Индексы создаются под реальные условия поиска, соединения и сортировки.
Почему MySQL не использует созданный индекс?
Причиной может быть порядок столбцов, тип сравнения, функция над столбцом, низкая избирательность или другой более выгодный план.
Асинхронный запрос всегда быстрый?
Нет. Он не блокирует игровую логику, но может оставаться тяжёлым для базы.
Поможет ли увеличение мощности VPS?
Поможет только при реальном недостатке ресурсов. Бесконечный цикл, плохой запрос или утечку необходимо исправлять в коде.
Можно ли уменьшить sleep до нуля?
Экстремальное уменьшение может значительно повысить CPU. Значение нужно проверять на тестовой сборке.
Что произойдёт при уменьшении sync rate?
Обновления станут чаще, но возрастут нагрузка и сетевой трафик.
Почему память увеличилась после запуска и остановилась?
Плагины и кэши могли выделить рабочую память. Это не обязательно утечка, если дальнейшего роста нет.
Как подтвердить утечку памяти?
Повторяйте одинаковые действия и проверяйте, продолжает ли RSS расти после освобождения игровых ресурсов.
Что чаще всего не удаляется?
Таймеры, динамические объекты, TextDraw, временный транспорт, кэш запросов и ресурсы плагинов.
Можно ли запускать Valgrind на рабочем сервере?
Не рекомендуется. Он значительно замедляет процесс и предназначен для отдельной тестовой среды.
Поможет ли автоматический рестарт каждый час?
Он временно сбросит память и нагрузку, но скроет причину проблемы и может привести к потере данных.
Как часто проверять производительность?
После крупных обновлений, изменения плагинов, роста онлайна и появления новых задержек.
Нужно ли оптимизировать код, который выполняется один раз при запуске?
Только если он заметно задерживает старт или потребляет чрезмерные ресурсы. Сначала оптимизируйте часто выполняемые участки.
Как понять, что оптимизация помогла?
Сравните одинаковые тестовые сценарии по CPU, памяти, времени запросов, задержке команд и стабильности длительной работы.
Официальные и технические источники
Заключение
Оптимизация сервера CRMP должна начинаться с измерений. Сначала определите, где возникает нагрузка: в игровом процессе, MySQL, диске, сети или серверном плагине.
В Pawn-коде особого внимания требуют частые callbacks, полные циклы, отдельные таймеры для каждого игрока и повторное создание динамических ресурсов. Тяжёлую работу необходимо распределять по времени и запускать только при реальном изменении данных.
MySQL-запросы следует выполнять асинхронно, но одной асинхронности недостаточно. Проверяйте медленные запросы через журнал и EXPLAIN, добавляйте обоснованные индексы, выбирайте только необходимые поля и устраняйте N+1 операции.
Параметры синхронизации в server.cfg изменяются только после измерений. Частые обновления повышают отзывчивость, но увеличивают нагрузку и сетевой трафик.
При росте памяти повторяйте одинаковый тестовый сценарий и проверяйте освобождение таймеров, объектов, TextDraw, транспорта и ресурсов плагинов. Постоянный рост RSS после каждого цикла указывает на необходимость дальнейшей диагностики.
Не используйте частые перезапуски как основное решение. Они временно скрывают перегрузку или утечку, но не устраняют ошибку и могут приводить к потере последних данных игроков.
После каждого изменения выполняйте нагрузочный тест, сравнивайте показатели с исходной версией и сохраняйте рабочую резервную копию.
Обслуживание и контроль сервера:
Оптимизация подключения и запросов базы:
Диагностика серверных расширений:
Настройка параметров производительности:
Комментарии к инструкции
Обсудите решение, задайте вопрос или дополните инструкцию