Egy massively multiplayer online stratégiai játék blogja a fejlesztés kezdetétől a játék indulásáig, és túl...

Utolsó kommentek

  • devDavid: Közérdekű közlemény #1 Készítettem egy kisebb adatbázis dump-ot, amivel könnyebb dolgozni (fejles... (2012.12.24. 14:11) Letölthető Zandagort
  • cu2: @devDavid: Lehet, hogy én voltam félreérthető, mert nem vettem rossz néven a(z egyébként jogos) kr... (2012.12.23. 17:42) Letölthető Zandagort
  • devDavid: @cu2: ne érts félre, nem kritizálni akarom, nagyon nagy dolog - szerintem - hogy egy ilyen projekt... (2012.12.23. 17:24) Letölthető Zandagort
  • Utolsó 20

Címkék

Gebasz Kezelő Rendszer

2012.06.25. 12:15 cu2

Ez most egy kocka bejegyzés lesz, akit az ilyen nem érdekel, nyugodtan hagyja ki.

Mint egy-kétszer utaltam rá, nem volt szerencsénk az elmúlt hónapokban. Több szerverhibába botlottunk, mint az előtte lévő 3 évben. Persze a 9-esek száma úgy működik, hogy egyrészt ahogy növekszik, az ár hatványozottan emelkedik, másrészt az egész "csak" valószínűség, vagyis a nagyobb biztonság csak hosszú távon és várható értékben jön össze. Garancia nincs rá, hogy nem esik egy tégla a fejemre, csak annyit mondhatok, ha mégis megtörténik, hogy nagyon-nagyon-nagyon nagy pechem volt. Ami viszont nem boldogít a másvilágon.

Van még egy harmadik tényező is: nem csak abban van bizonytalanság, hogy mikor következik be hiba és milyen jellegű. Hanem abban is, hogy ha már bekövetkezett, mi a tényleges ok. Ritkán mondja meg a gép egyértelműen, hogy ez és ez az alkatrész hibás, tessék cserélni. Inkább csak sejteni lehet, hogy inkább a memória, a vinyó vagy a táp, mint mondjuk a proci vagy az alaplap (legalábbis bizonyos esetekben, bizonyos jelek alapján). Ez azt jelenti, hogy ha az ember úgy dönt, lecseréli az X alkatrészt, akkor nem elég, hogy kifizet érte nem kevés pénzt (mert azért egy bizonyos 9-es számot muszáj alapból elérni, annak meg ára van), de még az is könnyen lehet, hogy nem ott volt a hiba.

Ezt így leírva (és gondolom olvasva) nem igazán látszik a fény az alagút végén. Pedig van. És pedig ott van, hogy a valódi cél nem feltétlenül az, hogy soha ne történjen hiba, hanem inkább az, hogy ha történik is, minél kisebb legyen az okozott kár (vö. R=W·K). Ami a játék esetén a kiesett idő, és a "visszatekerés" mértéke.

Annó, lassan 4 éve, amikor belevágtam a Zandagortba, hoztam két technológiai döntést:

  1. a játék PHP+MySQL+AJAX alapon megy
  2. az adatbázis nagyrészt MEMORY táblákat használ

Bár mindegyikbe bele lehetne kötni, igazából nem bántam meg egyiket sem. De most csak a másodikról essék szó, mivel az érinti a mostani témát.

A játékban két adatbázis van: az egyik tulajdonképpen egy hatalmas állapotleíró, ami megadja, hogy melyik bolygón milyen gyárak, erőforrások vannak, hol találhatók az egyes flották és mi az összetételük, egyszóval mindent, ami a játékbeli világot jellemzi. Ez persze folyamatosan változik, hiszen a gyárak termelnek, a flották mozognak, vagyis maga a világ is változik. A másik adatbázis egyfajta log vagyis napló, amiben eltárolódnak csaták, kémjelentések, toplisták, flottamozgások. Itt változás nincs, csak kiegészülés: vagyis csak újabb és újabb sorok íródnak hozzá, ami már le van írva, nem változik.

Egy ilyen logjellegű adatbázis tökéletesen jól működik MyISAM táblákkal. Minden, ami történik, folyamatosan és főleg sorfolytonosan kiíródik lemezre, így egyrészt nem vész el (szinte) semmi, másrészt az I/O szekvenciális vagyis gyors.

Az állapotleíró adatbázishoz viszont a MEMORY táblák tűntek és tűnnek a legjobbnak. Ezek legnagyobb előnye, hogy kizárólag memóriában vannak az adatok, így a tipikus szűk keresztmetszet, az I/O-műveletek kimaradnak. Hátránya kettő is van, bár elsőre csak az egyik nyilvánvaló. Ha elszáll a gép (akár csak a MySQL-szerver is, akár csak egy másodpercre is), elveszik az összes adat (a táblák maguk megmaradnak, csak kiürülnek).

A kevésbé nyilvánvaló hátrány, nem a MyISAM-mal, hanem az InnoDB-vel szemben, hogy nincs soronkénti lock, csak egész táblás. Ez főleg akkor lehet probléma, ha se az INSERT/UPDATE-ek, se a SELECT-ek aránya nem kiemelkedően magas, vagyis alapvetően vegyesen történik írás és olvasás. A játék, szemben mondjuk egy tipikus weboldallal, ahol szinte alig van írás, pontosan ilyen (s7-re konkrétan: Com_select/sum(Com_%)=49%). Hiszen ha más nem, a bolygók gazdaságai percenként frissülnek. Ennek ellenére az a tapasztalat, hogy ez itt nem jelent gondot. Valószínűleg azért, mert az I/O-műveletek hiánya annyira meggyorsítja az írást/olvasást, hogy még a táblaszintű lock sem okoz annyi fennakadást, mint mondjuk egy sorszintű lock I/O-val együtt.

Visszatérve a nyilvánvaló hátrányra. Jön egy keményebb villám, a szervertermen végigfut az áramingadozás (na jó, ahogy Móricka elképzeli), a szerver újraindul. Vagy valami hülye rejtett bug a MySQL-ben, amitől ritkán ugyan, de behal az adatbázis, még ha utána egyből restartol is. Egy szimpla honlapon ezt észre sem venni, max 1-2 percig nem elérhető, aztán helyreáll a rend. Ez a helyzet akár a zandagort honlappal, akár a fórummal. (Nyilván más a helyzet, ha totál meghal a gép, de ez az egész cikk most nem erről szól.)

Ezzel szemben a játéknak elszáll a MEMORY táblákban tárolt állapotleírója, vagyis se kép, se hang, még a galaxis sem marad meg. Egészen addig, amíg valaki helyre nem állítja egy mentésből. És még utána is csak az az állapot jön vissza, ami a mentés idején volt.

Egészen mostanáig ez úgy nézett ki, hogy minden hajnalban készült egy teljes mentés, ezen kívül be volt kapcsolva a binary log, ami egy speciális log, ami rögzít minden egyes változást minden egyes táblában. Ez teljesen más, mint a fent említett log adatbázis, egyrészt mert tényleg minden változást rögzít (pl hogy X bolygón a kő mennyisége D időpontban Y-ra változott) (még ha nem is pont ebben a formában), másrészt mert a MySQL magától elkészíti.

Felmerülhet a kérdés, hogy ha van binary log, ami persze lemezre íródik, akkor mi van az I/O-sebességével. A válasz az, hogy a binary log szekvenciálisan íródik, ami gyors. Olyannyira gyors, hogy nem nyer vele sokat az ember, ha kikapcsolja.

Vagyis egy elszállás utáni helyreállítás kétféle lehet:

  1. Betöltöm az utolsó mentést, és kész. Ez viszonylag gyors. Már persze onnan számolva, hogy észreveszem (hogy szólt valaki), géphez kerülök, és elindítom a visszatöltést.
  2. Betöltöm az utolsó mentést, és a binary logból leszimulálom az onnantól az elszállásig történteket. Ez jóval lassabb, hiszen ha senki hozzá nem nyúlt a játékhoz a köztes időben, akkor is van a percenkénti bolygó/flotta/satöbbi léptetés. Percenként kb 10 percet lehet előretekerni, vagyis átlag esetben, amikor 12 órát kell újraszimulálni, olyan 70 perc alatt fut le a helyreállításnak ez a fázisa. Ami már nem olyan gyors. És sok esetben nem is megy gond nélkül, mert nem nehéz véletlenül elérni, hogy olyan is íródjon a binary log-ba, aminek nem kéne, és ami az újrajátszásnál duplikált bejegyzést eredményez.

Felmerült persze már korábban is, hogy jó lenne ezt az egész helyreállítást valahogy feljavítani: elsősorban automatizálni, másodsorban felgyorsítani. De ha az embernek épp ezer dolga van, amik látszólag mind fontosabbak, mint egy ilyen (jó esetben) ritkán használt komponens, akkor könnyű sosem belevágni. Az elmúlt időszakban megszaporodott hibák arra voltak jók, hogy átrendezzék a prioritásokat, így most végre belevágtam, és elkészült a GKR vagyis a Gebasz Kezelő Rendszer.

Ez 3 shell szkript, amiből az egyik percenként indul cron-ból, a másik kettőt pedig ez hívja meg.

gkr

Az elején néhány apróság (ezeket nem kommentelem, mert érthetőek, és nem befolyásolják az egész szkript megértését):

#!/bin/sh
server_prefix="$1"
mysql_password="$2"
cd "/home/web2/mmog_$server_prefix"
date=$(date +"%Y-%m-%d_%H%M%S")
min=$(date +"%M")

Egy fájl, ami a rendszer aktuális állapotát tartja nyilván:

server_status=$(cat www/up/index.html)

Ez a http://s8.zandagort.hu/up/ helyen bárki számára elérhető. Ami nem csak arra jó, hogy bármikor bárhonnan látni lehessen, mi a helyzet, hanem hogy a másik szerveren futó Zandagort honlap is le tudja kérni, és ez alapján kiírni valami bocsánatkérő hibaüzenetet.

A szkript kimenete egy fájlba íródik, hogy utólag meg lehessen nézni, mi történt:

echo "$date gkr: $server_prefix $server_status"

Ha a helyreállítás rendben zajlik, akkor a szkript kilép, nehogy mondjuk újra elindítsa a helyreállítást (vagy esetleg elkezdje lementeni a félig helyreállított állapotot). Ha viszont túl sokáig "fut", akkor valószínű, hogy elszállt közben, és jobb újraindítani:

if [ "$server_status" = "RESTORING....." ]; then
	echo "RESTART gkr_restore"
	./gkr_restore "$1" "$2"
	exit
fi

if [ $(echo "$server_status" | cut -c-9) = "RESTORING" ]; then
	echo "$server_prefix $server_status"
	echo -n "." >> www/up/index.html
	exit
fi

A várakozás azt jelzi, hogy a helyreállító (gkr_restore) már megpróbált elindulni, de még a MySQL szerver sem fut. Ekkor a rendszer várakozik és újrapróbálkozik. Illetve még 15 percenként küld egy (sürgető) emailt, hogy valami komolyabb baj van. Mondjuk ezen még érdemes lehet finomítani. Egyrészt, hogy esetleg megpróbálja újraindítani a MySQL-t, másrészt nem biztos, hogy annyira hasznos ennyi emailt küldeni. De első körben (nekem) az is elég, hogy a rendszer egy sima, zökkenőmentes restart után helyreállítja az adatbázist.

if [ "$server_status" = "WAITING" ]; then
	echo "$server_prefix WAITING"
	if [ "$((min%15))" -eq 0 ]; then
		echo "Server is totally down, still waiting." | mail -s "Zandagort $server_prefix is totally down" "admin@email.com"
	fi
	echo "START gkr_restore"
	./gkr_restore "$1" "$2"
	exit
fi

Ezután nézi meg, hogy mi a helyzet az adatbázissal. Ez szintén egy kívülről is elérhető rész (http://s8.zandagort.hu/up/up.php), ami egyszerűen az egyik egysoros MEMORY táblából kér le egy sort. Ez kb a létező leggyorsabb művelet (az egysorosság miatt), főleg, hogy ezt a táblát csak ritkán használja a játék. Viszont ha elszállt az adatbázis, akkor ez is kiürül.

is_it_up=$(wget -q -O - "http://$server_prefix.zandagort.com/up/up.php")

Ha bármi gond van, akkor indul a helyreállító szkript.

if [ "$is_it_up" != "UP" ]; then
	echo "$server_prefix DOWN"
	echo "START gkr_restore"
	./gkr_restore "$1" "$2"
	exit
fi

És végül, ha a rendszerrel minden rendben, akkor 15 percenként készül egy mentés:

if [ "$((min%15))" -eq 0 ]; then
	echo "START gkr_backup"
	./gkr_backup "$1" "$2"
fi

Ez az egyik legfontosabb újítás az eddigiekhez képest, amikor csak naponta egyszer készült mentés. Így a maximum aktív játékidő, ami kieshet, az 15 perc. Ez egyrészt elég kevés ahhoz, hogy ne nagyon fájjon, másrészt szükségtelenné teszi a binary logból való újraszimulálást, így a helyreállítás nagyon gyorsan megvan (kb 1 perc). Maga a mentés olyan 5-10 másodpercig tart, ami alatt ugyan lockolva vannak a táblák (hogy konzisztens legyen a dump), de ez 15 percenként számolva nem sok idő (kb 1%).

gkr_backup

A mentést készítő szkript hasonló apróságokkal kezdődik:

#!/bin/sh
server_prefix="$1"
mysql_password="$2"
cd "/home/web2/mmog_$server_prefix"
server_database="mmog""$server_prefix"
server_admin="mmog""$server_prefix""admin"
date=$(date +"%Y-%m-%d_%H%M%S")

echo "$date gkr_backup: $server_prefix $date"

Utána jön maga a mentés:

mysqldump -u "$server_admin" --password="$mysql_password" --net_buffer_length=4096 "$server_database" | gzip --fast > dump/mmog_teljes_dump_$date.sql.gz

A net_buffer_length-et azért kell beállítani, mert a mysqldump alapból ún. extended insert-eket használ, vagyis egy INSERT nem egy, hanem egy csomó sort beszúr. Ami azért jó, mert a dump kisebb lesz (ami nem csak helyet, hanem I/O-t is spórol), a visszatöltése pedig gyorsabb. A probléma csak az, hogy alapból irgalmatlan hosszú sorokat generál, konkrétan annyira hosszúakat, hogy a MySQL szerver simán nem bírja beolvasni (persze lehetne azon is állítani).

A tömörítés a méret miatt fontos. Egy dump olyan 100-200 mega, ami egy nap alatt már 10-20 giga. De itt sem annyira a tárhely a probléma, inkább az I/O. A gzip a fast paraméterrel gyorsabb, mintha a tömörítetlen dumpot írnánk ki lemezre.

Mentés után leellenőrizzük, hogy megvan-e az adatbázis:

is_it_up=$(wget -q -O - "http://$server_prefix.zandagort.com/up/up.php")

Itt is elég csak egy táblába beleolvasni, mert ha végig rendben futott a MySQL, akkor minden oké, ha meg elszállt, akkor az összes tábla tartalma elszállt.

Ha pont mentés közben történt valami, akkor a dumpot töröljük, mert hibás mentéseket ne őrizgessünk. Így lehet legbiztosabban elkerülni, hogy a helyreállítás ne hibás mentésből történjen:

if [ "$is_it_up" != "UP" ]; then
	echo "$server_prefix backup mmog_teljes_dump_$date.sql.gz deleted"
	rm dump/mmog_teljes_dump_$date.sql.gz
else
	echo "$server_prefix backup mmog_teljes_dump_$date.sql.gz stored"
fi

gkr_restore

Nem meglepő módon a helyreállítás is a szokásos apróságokkal indul:

#!/bin/sh
server_prefix="$1"
mysql_password="$2"
cd "/home/web2/mmog_$server_prefix"
server_database="mmog""$server_prefix"
server_admin="mmog""$server_prefix""admin"
last_intact_dump=$(ls -t dump | head -1)
date=$(date +"%Y-%m-%d_%H%M%S")

echo "$date gkr_restore: $server_prefix $last_intact_dump"

Egyetlen extra van benne, a last_intact_dump, ami a dump fájlok közül a legutóbbi. Ezért fontos, hogy a hibás mentés törlődjön.

Bebillentjük a közös flag fájlt helyreállítás üzemmódba (innen tudja pl a percenként induló gkr szkript, hogy ne csináljon semmit), és küldünk egy emailt:

echo -n "RESTORING" > www/up/index.html
echo "Server is down, trying to restore." | mail -s "Zandagort $server_prefix is down" "admin@email.com"

Ezután jön maga a helyreállítás:

gunzip -c "dump/$last_intact_dump" | mysql -u "$server_admin" --password="$mysql_password" --default-character-set=utf8 "$server_database"

Ha nem sikerült, akkor átváltunk várakozó üzemmódba (vagyis az alap gkr szkript percenként elindítja a helyreállíót):

is_it_up=$(wget -q -O - "http://$server_prefix.zandagort.com/up/up.php")
if [ "$is_it_up" != "UP" ]; then
	echo "gkr_restore: unsuccessful"
	echo -n "WAITING" > www/up/index.html
	echo "Server is totally down, waiting." | mail -s "Zandagort $server_prefix is totally down" "admin@email.com"
	exit
fi

Ha sikerült, visszabillentjük a flag-et UP-ba:

echo "gkr_restore: successful"
echo "Server successfully restored." | mail -s "Zandagort $server_prefix is up again" "admin@email.com"
echo -n "UP" > www/up/index.html

 

Ennyi. Teszt körülmények között jól szuperál, hogy éles helyzetben milyen, az majd elválik. Jelenleg csak az s8 alá raktam be, bár a 15 percenkénti mentés már az s7-re is megy.

5 komment

Címkék: szerver gebasz

A bejegyzés trackback címe:

http://zandagort.blog.hu/api/trackback/id/tr134609761

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Csavez 2012.06.25. 14:05:46

cu2: Nem gondolkodtál el még azon hogy ne saját szerveren hanem valami bérelt virtuális gépen futtasd a játékot?

cu2 · http://zandagames.com/ 2012.06.25. 16:09:47

@Csavez: Ez felemás dolog. Persze van előnye, hogy egy csomó felelősséget/feladatot át lehet hárítani másra, viszont ugyanezért kiszolgáltatottabb is az ember (így is utálom, amikor a blog.hu-n néha nem lehet kommentelni).

De hogy kicsit konkrétabb (mármint Zanda-specifikusabb) legyek:
- Ha virtuális szerver alatt az ilyen egy gépen kapok 10% cpu kvótát dolgot értjük, az kevés.
- Ha meg az amazonos cloud-os megoldást, az szerintem inkább akkor jó, ha nagyon kiszámíthatatlan a terhelés, és fontos, hogy jól skálázzon a rendszer. A Zanda egyrészt (sajnos) még messze van a skálázási problémáktól, másrészt nem látom okát, hogy miért ne lenne jó mondjuk 2000-3000 aktív játékosonként külön szervert indítani. Lehet, hogy ez nem Eve-szintű élmény, ahol több 10ezren lehetnek online egyszerre egyetlen közös világban, de gyanítom, hogy ott sok olyan probléma is felmerül, ami önmagában nem old meg egy cloud.

Maro · http://bytepawn.com 2012.06.25. 20:40:45

AWS-en nem eri meg allando jelleggel EC2 szervert futtatni, mert sokkal tobbre jon ki, mint ha dedikalt szervert berelsz (legutoljara 2-3x volt amikor utana szamoltam), es meg lassabb is lesz. Adatbazisos dolgokat kulonosen nem eri meg, mert az EC2 instanciak diszkjei valosagban halozaton keresztul bemappelt valamik, amiknek eleg gyengus az I/O teljesitmenyuk. Raadasul mivel egy EC2 instancia ujrainditas utan elveszti a default diszkjet, EBS-en keresztul kell trukkozni a perzisztens adatokkal, ezert persze kulon fizetsz. Szoval Cucunak rengeteg tobbletmunkaval jarna, es semmit se nyere vele. (Kivetel a tapasztalot, ami akar erhet is valamit adott esetben.) Raadasul az AWS-en is rendszeresen vannak outage-ok, a legutolso mult heten volt es jo par oraig tartott (kb. elromlott valami alkatresz a datacenter generatoraban, majd a backupokban is, es szevasz). AWS-t akkor eri meg hasznalni, ha nagyok sok gepet futtatsz, gyorsan skalazol, mert akkor a cegedben komplett reszlegeket es hardverparkot nem kell fenntartanod, hanem havi dijert kapod az Amazontol (pl. ezt csinalja a Netflix). Kisebb cegnek mint a Cucu max. tesztelesre eri meg hasznalni, hogy emiatt ne kelljen kulon hardvert fenntartani; mi ezt csinaltuk, de eleg macera volt, ugyhogy vegul megis vettunk dedikalt teszt hardvert...

cu2 · http://zandagames.com/ 2012.06.27. 14:02:13

És ma reggelt megvolt az első éles "teszt". Hogy a GKR tényleg jól szuperál, az onnan is látszik, hogy egyetlen ember szólt csak, hogy pár percig nem ment a játék. Máskor ilyen esetben kapok pár tucat emailt, fórumbejegyzést, facebook üzit. Király!

Üröm az örömben, hogy valami továbbra sem stabil a szerveren, különben nem lett volna spontán restart.

legyes 2012.06.27. 14:53:27

Valami watchdog indítja újra (pl. iLo), vagy csak a minden előjel nélküli WTF stílusú "megállt a nemmegy"?