Firebird lassulás - folyamatos update ugyanarra a rekordra

Ebben fórum bejegyzésben található egy alszál, amely arról szól, hogy LMoon kolléga találkozott a Firebird-del kapcsolatban egy olyan megmagyarázhatatlan lassulással, amely miatt át kellett írniuk az egész programot MySQL alapokra.

A lent leírt dolgok sok év FB fejlesztés és viszonylag testes adatbázisokon szerzett tapasztalatok alapján kristályosodtak ki.
Ha kisebb projektekben dolgozunk, akkor is érdemes figyelembe venni őket, hátha egyszer elérjük azt a műveleti és adatmennyiséget, ahol ez már probléma lehet.

Az FB tranzakció kezeléséről bővebben nem kívánnék most értekezni, de azt jó tudni, hogy minden induló tranzakció kap egy szigorúan monoton növekvő sorszámot (ezt tárolja az FB a Transaction Inventory Page-en (TIP)), és minden tranzakció, mindaddig, amíg commit(ez a preferált, erre van kihegyezve a motor)/vagy rollback nem lesz a vége aktív tranzakcióként rekordverziókat generál minden (más) tranzakción keletkező insert/update/delete művelettel kapcsolatban. Ezért javasolt minden olyan tranzakciót, amin csak adatlekérés van READ ONLY (read commited) módban indítani, mert ezek a tranzakciók nem gyártanak rekordverziókat. (A GTT táblák read only tranzakción is írhatóak!) Ha igazán ügyesen akarjuk szervezni a programunkat, akkor egy globális read only read commited tranzakcióra akasztjuk az összes lekérdező jellegű utasításunkat, és ezt a tranzakciót soha nem commitoljuk, ezzel is erőforrást megtakarítva (a tranzakció indítása is erőforrást vesz el szerver oldalon, még ha nem is sokat, ráadásul feleslegesen "pörgetjük" a TIP maximális értékét, ami előbb-utóbb a garbage collector elindítását eredményezi).
Az írási műveleteket végző tranzakciókat pedig a lehető leggyorsabban le kell zárni.
Ha így szervezzük a programunkat, akkor vélhetőleg nem lesz problémánk a rekordverziók miatti lassulással.
(Jó tudni, hogy a rekordverziók három módon szűnhetnek meg. Vagy egy őket olvasó select utasítás érzékelheti, hogy már nincs rájuk szükség, és takaríthatja ki őket (ezért lehet látszólag érthetetlen módon lassú egy select, ami másodszorra már pillanatok alatt lefut), vagy a garbage collector vagy egy gbak mentés fogja ezt megtenni. A gbak alapértelmezetten tehát takarít, ha azt szeretnénk, hogy a mentésünk minél előbb lefusson, akkor -g kapcsolóval indítsuk.)

(Találkoztam olyan esettel, hogy a fejlesztő kolléga aktív tranzakcióként production adatbázison hagyott egy select-et, majd napokkal később az ügyfél telefonált, hogy nem érti miért, ilyen még nem volt, de lassul a rendszere, nézzünk már rá. A tranzakció lezárásával gyorsan helyreállt a régi rend. Firebird 2.5 alatt javaslom a mon$transaction táblában kutakodást, nagyon szépen meg lehet találni a régi, de még aktív tranzakciókat, amik a lassulást okozhatják.
Sajnos nincsenek már meg, hogy ebben az esetben a nyitva maradt aktív tranzakció számához képest a TIP hogy állt, így csak becslésekbe tudok bocsátkozni. Úgy tippelem, hogy 90-100k tranzakció után következhetett be a lassulás, de ez nyilván csak attól függ, hogy milyen műveletek történnek ezeken a tranzakciókon, mennyire voltak adatmanipulálók, és mennyire csak lekérdezők.)

A fenti, gondos tranzakció tervezés mellett is találkoztam egy olyan esettel, ami lassulást okozott.
Ha egy rekordon sok-sok update fut, miközben ezek rekordverziókat gyártanak (mert nincs commit az update-ek közt, vagy van, de van olyan nem read only read commited tranzakció, ami megköveteli a verziózást), és ezeket a rekordverziókat figyelembe vevő select-ek futnak, akkor a firebird az alap rekordból kiindulva, követve a rekordverziókat minden selectnél a rekordra vonatkozóan VÉGIGOLVASSA az összes megelőző változatot. Ráadásul, ha a sok update azt okozza, hogy a módosulások másik adatlapra kerülnek, akkor ennek további lassulás lesz az eredménye (erről a 70%-30%-os tárolásról a hivatkozott fórum bejegyzésben írtam.)

Készítettem egy teszt alkalmazást egy nagyobb adatbázison, ami a következőt teszi:
- egy milliós tábla egyetlen rekordjának egyetlen mezőjét update-eli 3000x (különböző értékekre, mert az FB figyeli, hogy történt-e változás és csak akkor készít rekordverziót, ha igen!)
- a táblán egy trigger egy másik tábla egy rekordjából kiolvassa, hogy hány módosulás volt eddig az eredeti tábla rekordjaira. A kiolvasott értéket megnöveli eggyel és update-eli a segédtábla érintett rekordját
Ebben a tesztben két tábla két mezője folyamatosan változik, és a segédtábla mezőjét még ráadásul mindig újra és újra ki is olvassa a motor. Ez utóbbi fogja a lassulást okozni.
Íme egy táblázat, amely megmutatja, hogy 3000 ismétlődő update esetén milyen sebességlassulás következhet be:

Count
Egy műveletre jutó idő
101
1,36367801980198E-08
501
5,59760109780439E-08
1001
1,8258362008362E-07
1501
3,89902102302169E-07
2001
6,78133155644398E-07
2501
1,0472107453315E-06
3000
1,49621141975309E-06

Látható tehát, hogy a 3000. update már 2 nagyságrenddel lassabban fut le, mint az első!

Összefoglalva: lehetőleg kerüljük az azonos rekordra történő masszív update-eket egy tranzakción belül, mert jelentős lassulást okozhat még egy tranzakciós környezet esetén is, nem beszélve a többfelhasználós (több aktív tranzakciós) használatról.

Nem tudom, hogy LMoon-éknál ez volt-e a lassulás oka, de ha már többen érdeklődtek, gondoltam leírom egy kicsit a tapasztalatomat a firebird tranzakciókkal kapcsolatban. Remélem hasznos post lett belőle. :)

Közben eszembe jutott még valami, amivel gondok lehetnek. Hyper Threadinges processzoroknál a HT-t ajánlott kikapcsolni. Bár, amikor mi először belefutottunk, és túrtuk a netet, akkor egységesen azt írták, hogy adatbázis szerverekben kimondottan ajánlott kikapcsolni a HT-t, mert a megosztott cache folyamatos ürítése extrémül lassít. (Nem tudom, hogy ez valóban általános, vagy Firebird specifikus, azóta nem ajánlunk HT-s gépeket szervernek, és nincs vele gond.)

Hozzászólások

Köszi.
Oracle példákkal akartam jönni az eredeti topikban, de az elmúlt pár évben szinte mindent elfelejtettem belőle. Pedig sok hasonlóságot vélek felfedezni a két rendszer közt.

Nagyon hangzatos bigdata -ban van tapasztalatod?:)

// Happy debugging, suckers
#define true (rand() > 10)

Nincs. Amiben én mozgok Firebird-ben napi szinten:
7-8 gigás adatbázis, 600-650 tábla, 1000-1100 tárolt, egyenként 4-5 millió rekord a legnagyobb 5-6 táblában, max. 80 konkurens user/db és 60-80e tranzakció/nap.

Ha ennél nagyobbnak ígérkezik az ügyfél, akkor már Oracle-t ajánlunk több ok miatt, de nem azért, mert úgy tapasztaltuk volna, hogy nem bírná el az FB. Teszteket csináltunk jóval nagyobb adatbázisokra, jól teljesített, de nálunk nincs aktív ügyfél, aki a fentinél jóval nagyobb adatbázissal rendelkezne.

Dmitry-től egy prezentáció bigdata témakörben:
http://www.slideshare.net/ibsurgeon/firebirds-big-databases-in-english

Adatmódosító, vagy lekérdező logika fut ezeken?

Ezzel kapcsolatban egy probléma ugrik be, amit említettek a legutóbbi konferencián. A TIP előjeles integer-ként tárolja a tranzakció azonosítót. Ezért 2.7 milliárd tranzakció után csak gbak mentés/visszatöltés segít, ezzel lehet nullázni a TIP futó sorszámát. Egy olyan weboldalt említettek, ami ezt 3 havonta éri el. Firebird 3.0-ban ezt nem előjeles integer-re változtatják, de nem akarják 4 byte-ról 8 byte-ra emelni, mert ez egy minden rekordon megtalálható adat, és úgy gondolják, hogy túl "drága" lenne a többi felhasználónak ez az emelés.

Ha jól számolok, akkor 10 óra alatt kifutna Nálad a counter. Így vélhetően nem az FB a Neked megfelelő adatbázis-motor.

Hacsak ezek nem lekérdező select-ek és nem megoldható az, hogy egy tranzakción dolgozzon az összes. (Vagy legalábbis csoportba rendezni őket, hogy ne induljon ennyi.)

(Szerk.: ahogy fentebb írtam, a read only, read commited tranzakciók a "végtelenségig" nyitva maradhatnak, azok nem okoznak verziózást a módosuló rekordokon, de látják az összes, már commit-tal jóváhagyott új/módosult adatot.
Mi ezt alkalmazzuk, kliens-szerver alkalmazásoknál, azokon belül is ott, ahol nem fontos a snapshot-os adatlekérés.)

Nem FB-ben gondolkodom, hanem általános kérdés volt. A kulcsok változóak, naponta 2-3 millió új rekord kerülne be, illetve update (vektorral) a számosságot tekintve ebből lenne lényegesen több. Ez a legtöbb adatbázisrendszernek már feladja a leckét:)
Vannak esélyes befutók, viszont mindnek megvan a saját maga gyermekbetegsége, de ezt majd az idő eldönti:)

// Happy debugging, suckers
#define true (rand() > 10)

Szia!
Cassandra es Apache Accumulo volt a finaleban, hw-t es kornyezetet nem irhatok, de az osszegzett tapasztalatok:
1. Cassandra:
kb. 70k (update/insert)/sec amit sikerult belole kicsikarni, ami nem lenne rossz, viszont uzemeltetesi szempontbol nem a legjobb. 2.x-es verzioval 500G adatot buktunk, mert azt mondta hogy o most megall es nem csinal semmit tovabb. 1.x ag stabilabbnak tunt, ott nem jott elo ez, viszont volt mas problema vele.
2. Accumulo:
200k (update/insert)/sec, stabilnak tunik, hadoop alapokon nyugszik (hdfs) viszont meg keves tapaszalat gyult vele ossze

// Happy debugging, suckers
#define true (rand() > 10)