Optimierung des Daten-Cachings (PlayerInfo) im RAM
Einer der häufigsten Architekturfehler bei der Entwicklung von SA:MP/CRMP-Servern ist das direkte Senden von SQL-Schreibbefehlen bei jeder Statusänderung eines Spielers. Geldvergabe, Waffenbindung, Erfahrungspunkte oder Fraktionsrangänderungen — wenn Ihr Mod auf jedes dieser Ereignisse mit einer schweren UPDATE-Abfrage reagiert, wird der Server schnell zu laggen beginnen.
Bei einer Online-Zahl von 150–200 Spielern steigen die Transaktionen pro Sekunde schnell in die Hunderte. Das Festplatten-Subsystem des Hostings (selbst schnelle NVMe SSDs) wird durch die hohe Anzahl an E/A-Operationen (I/O) völlig überlastet. Das Resultat sind Server-Hänger und drastisch erhöhte Ping-Werte bei den Spielern. In diesem Artikel analysieren wir, wie Sie mithilfe der Struktur PlayerInfo ein effizientes Daten-Caching im Arbeitsspeicher (RAM) einrichten und die Hosting-Last minimieren.
Das Konzept des Cachings: RAM als primäre Quelle der Wahrheit
Der Arbeitsspeicher arbeitet tausende Male schneller als jedes physische Laufwerk. Ein sauberer Ansatz für die Mod-Architektur basiert auf einer einfachen Regel:
Spielerdaten sollten exakt einmal ausgelesen werden — beim Login, und nur beim Verlassen des Spielers oder über einen globalen Autosave-Timer zurückgeschrieben werden.
Die restliche Zeit, in der ein Spieler online ist, sollten sämtliche Manipulationen an Level, Geld oder Inventar ausschließlich im RAM des Servers verarbeitet werden – in einem speziellen Array (dem Cache).
Schritt 1: Variablen richtig strukturieren (Das PlayerInfo-Array)
Zur Erstellung eines Caches in Pawn wird ein Enumerator (enum) mit einem globalen mehrdimensionalen Array kombiniert, dessen Größe fest an die maximale Slotzahl des Servers (Konstante MAX_PLAYERS) gebunden ist.
enum E_PLAYER_DATA
{
pID,
pName[MAX_PLAYER_NAME],
pLevel,
pMoney,
pAdminLevel,
pFraction,
bool:pIsLoggedIn
}
new PlayerInfo[MAX_PLAYERS][E_PLAYER_DATA];
Beim Verbindungsaufbau sowie beim Verlassen des Servers muss dieser Cache unbedingt geleert werden, damit alte Daten nicht fälschlicherweise auf einen neuen Spieler mit derselben ID übertragen werden:
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;
}
Schritt 2: Erstellung der asynchronen Speicherfunktion
Die Speicherfunktion bündelt alle während des Spiels veränderten Parameter aus dem PlayerInfo-Array und sendet sie als Paket per asynchronem mysql_tquery an die Datenbank:
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;
}
Schritt 3: Einrichtung des globalen Autosaves (alle 15 Minuten)
Um das Projekt im Falle eines plötzlichen Serverabsturzes vor Datenverlusten zu schützen, ist die Einrichtung einer zyklischen automatischen Speicherung notwendig.
Achtung: Speichern Sie niemals alle Spieler in derselben Sekunde! Wenn 300 Personen online sind und ein Timer zeitgleich 300 schwere SQL-Befehle ausführt, wird die Warteschlange blockiert, was zu extremen Verzögerungen führt. Die Speichervorgänge müssen zeitlich gestaffelt werden.
Implementierung eines zeitlich gestaffelten Autosaves:
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;
}
Bewertung der Festplattenauslastung
Betrachten wir die mathematische Belastung des Laufwerk-Subsystems:
| Script-Architektur | Abfrageanzahl (200 Spieler, 1 Stunde Laufzeit) | Festplatten-E/A-Last (I/O) |
|---|---|---|
| Ohne Caching (UPDATE bei jeder Transaktion, Kill oder Payday-Check) | ~ 15.000 — 40.000 Abfragen | Kritisch. Verursacht Mikroruckler, Synchronisationsfehler und blockiert Kanäle. |
| Mit RAM-Caching (Sowie gestaffeltem 15-Minuten-Intervall) | ~ 800 — 1.000 Abfragen | Minimal. Die Laufwerksauslastung sinkt um 95–98 %. Führt zu absolut flüssigen TPS-Werten. |
Fazit: Daten-Caching im RAM ist die einzige professionelle Methode für Server mit hohen Spielerzahlen. Überlassen Sie dem schnellen Arbeitsspeicher die häufigen Zustandsänderungen und nutzen Sie die Datenbank rein als zuverlässiges Langzeitarchiv.