Организация правильного кэширования данных (PlayerInfo) в RAM
Одной из самых распространенных архитектурных ошибок при разработке модов в SA:MP/CRMP является отправка SQL-запросов на запись в базу данных при каждом изменении состояния игрока. Выдача денег, покупка оружия, получение опыта, изменение фракционного ранга — если на каждое из этих событий ваш мод реагирует вызовом тяжелого запроса UPDATE, сервер очень быстро начнет лагать.
При онлайне в 150–200 игроков количество мелких транзакций в секунду начинает исчисляться сотнями. Дисковая подсистема хостинга (даже быстрые NVMe SSD) перегружается операциями ввода-вывода (I/O). Как результат — сервер зависает, а у игроков резко возрастает пинг. В этой статье мы разберем, как организовать правильное кэширование данных через структуру PlayerInfo в оперативной памяти (RAM) сервера и минимизировать нагрузку на хостинг.
Концепция кэширования: RAM как основной источник правды
Оперативная память работает в тысячи раз быстрее любого физического диска. Правильный подход к архитектуре игрового мода строится на простом правиле:
Данные игрока должны считываться из базы данных строго один раз — при авторизации, и записываться обратно в базу только при выходе игрока или по глобальному таймеру автосохранения.
Всё остальное время, пока игрок находится на сервере, любые манипуляции с его уровнем, деньгами или инвентарем должны происходить исключительно внутри оперативной памяти сервера — в специальном массиве (кэше).
Шаг 1: Правильное структурирование переменных (массив PlayerInfo)
Для создания кэша в Pawn используется перечисление (enum) и глобальный двухмерный массив, размер которого жестко привязан к максимальному количеству слотов сервера (параметр MAX_PLAYERS).
enum E_PLAYER_DATA
{
pID,
pName[MAX_PLAYER_NAME],
pLevel,
pMoney,
pAdminLevel,
pFraction,
bool:pIsLoggedIn
}
new PlayerInfo[MAX_PLAYERS][E_PLAYER_DATA];
При подключении и отключении игрока этот кэш обязательно нужно очищать, чтобы данные старого игрока не "перешли" новичку, зашедшему под тем же ID:
public OnPlayerDisconnect(playerid, reason)
{
SavePlayerAccount(playerid);
PlayerInfo[playerid][pID] = 0;
PlayerInfo[playerid][pName][0] = EOS;
PlayerInfo[playerid][pLevel] = 0;
PlayerInfo[playerid][pMoney] = 0;
PlayerInfo[playerid][pAdminLevel] = 0;
PlayerInfo[playerid][pFraction] = 0;
PlayerInfo[playerid][pIsLoggedIn] = false;
return 1;
}
Шаг 2: Создание функции асинхронного сохранения
Функция сохранения должна агрегировать все измененные за время игры параметры из массива PlayerInfo и одной командой отправлять их в базу данных через асинхронный поток mysql_tquery:
stock SavePlayerAccount(playerid)
{
if(!PlayerInfo[playerid][pIsLoggedIn]) return 0;
new query[256];
format(query, sizeof(query),
"UPDATE `users` SET `level` = %d, `money` = %d, `admin` = %d, `fraction` = %d WHERE `id` = %d",
PlayerInfo[playerid][pLevel],
PlayerInfo[playerid][pMoney],
PlayerInfo[playerid][pAdminLevel],
PlayerInfo[playerid][pFraction],
PlayerInfo[playerid][pID]
);
mysql_tquery(db_handle, query);
return 1;
}
Шаг 3: Настройка глобального автосохранения (раз в 15 минут)
Чтобы защитить проект от потери прогресса игроков в случае внезапного отключения сервера (например, при краше мода или отключении питания дата-центра), необходимо настроить циклическое автосохранение.
Внимание: Никогда не сохраняйте всех игроков в одну и ту же секунду! Если на сервере играет 300 человек, и таймер одновременно вызовет 300 тяжелых SQL-запросов, фоновый поток MySQL перегрузится, что приведет к задержкам вызовов других запросов. Сохранение нужно распределять во времени.
Реализация "мягкого" распределенного автосохранения:
public OnGameModeInit()
{
SetTimer("GlobalAutoSave", 60000, true);
return 1;
}
forward GlobalAutoSave();
public GlobalAutoSave()
{
static current_chunk = 0;
for(new i = 0; i < MAX_PLAYERS; i++)
{
if(!IsPlayerConnected(i) || !PlayerInfo[i][pIsLoggedIn]) continue;
if(i % 15 == current_chunk)
{
SavePlayerAccount(i);
}
}
current_chunk++;
if(current_chunk >= 15) current_chunk = 0;
return 1;
}
Как это влияет на производительность хостинга?
Давайте посчитаем математику дисковой нагрузки:
| Архитектура мода | Кол-во запросов (200 онлайн, 1 час игры) | Нагрузка на I/O диска |
|---|---|---|
| Без кэшивания (UPDATE при каждом чихе, убийстве, получении $ пейдея) | ~ 15 000 — 40 000 запросов | Критическая. Вызывает микрофризы, лаги синхронизации, "забивает" канал. |
| С кэшированием в RAM (+ распределенное автосохранение раз в 15 минут) | ~ 800 — 1 000 запросов | Минимальная. Потребление ресурсов диска падает на 95-98%. Идельный плавный TPS. |
Итог: Кэширование данных в RAM — это единственный профессиональный способ создания модов под высокий онлайн. Позвольте оперативной памяти делать быструю и частую работу, а базу данных используйте исключительно как надежный долгосрочный архив.