Archive for February, 2009

És amikor a bűz már az égig ér…

Saturday, February 28th, 2009

A fenti mondatrész az Ördög ügyvédje c. filmben van, az egyik kedvencemben.

A rosszindulatú, ártalmas hazugság mindig nagyon felháborít, így a következő is.

Kiderült, hogy cigány önkormányzati képviselő az egyik pécsi cigánygyilkos:
http://tenyek.tv2.hu/Belfold/TenyekCikkek/2009-02-27-pecs_granat

Erre azt mondja Mohácsi Viktória: attól, hogy valaki annak vallja magát, még nem cigány, és akkor is a rasszista indítékot kell erőltetni:

http://napkelte.wildom.hu/naptv/jsp/program/wmv.jsp?filename=/naptv_upload/visszanezo/200902/20090228-0630.wmv

Mindenáron, még a végén is azt mondja, hogy itt eltitkolják a dolgokat, és akkor is rasszista a motiváció, ha nem. Elképesztő.

Nyilván befigyelt nála a kognitív disszonancia, annak ellenére, hogy Verebes nem dörgölte az orra alá a korábban az ügyben tett nyilatkozatait, pedig az lett volna a hatásos, ha összevágva visszanézhette volna magát.

Kéne már a gyűlölettörvény az ilyenek ellen, akik csak azért is rasszizmust kiabálnak, és a nemzetközi sajtót is telekürtölik ezzel. Ez a gyűlöletkeltés szerintem, ráadásul nem csak hazai, de nemzetközi szinten is. A magyarság ellen.

Az ilyen hazug megélhetésiek miatt lettem Jobbik-párti, annak ellenére, hogy belül liberálisabb vagyok, mint az SZDSZ bármelyik tagja. Majd egyszer leírom a folyamat evolúcióját, de most inkább készülök a jövő heti konferenciára.

.NET teljesítményhangolási tapasztalatok 7. – Hatékony UNC fájlelérés

Thursday, February 26th, 2009

Hogyan közelítsük meg azt a problémát, hogy egy managed alkalmazásból naponta több milliószor el szeretnénk érni másik szerveren levő, viszonylag ritkán, pár percenként frissülő fájlokat, a lehető leghatékonyabb módon?

Először is érdemes tudni, hogy a workstation service, a windows fájok távoli elérésének kliense cache-el, az OS File Cache-ét használva, ami a helyi fájlokat is cache-eli. Ha azonban módosítják a megosztáson levő fájlt, kikapcsolja a cache-elése erre a fájlra, egészen addig, míg újra meg nem nyitjuk. Bővebben majd egy teljes cikkben is megemlékezek erről, amikor a Windows Cache részleteiről fogok írni (a technet site-on).

Szóval kapuk valamennyi teljesítménynövekedést az OS cache miatt, de mivel a fájl olvasása során a PInvoke-nak át kell mozgatni az adatokat a natív oldalról a managed oldalra, jelentős veszteségeket élünk meg azzal szemben, ha inprocess, managed memóriában tudnánk tartani a fájlok tartalmát, de kiütve őket onnan, ha megváltoznak.

Gondolom a legtöbben már hallottak a System.Web.Caching.CacheDependency típusról. Ezt alapvetően webalkalmazások használják, de 2.0 óta már egyéb appokban is használható. A segítségével bámulatosan egyszerűen lehet cache-elni a felolvasott fájlok tartalmát.

object o = HttpRuntime.Cache[cacheKey];
if (o != null)
{
string s = (string)o;
}
else
{
CacheDependency d = new CacheDependency(file);
string s = ProtectedRead(file);
HttpRuntime.Cache.Add(cacheKey, s, d,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
Console.WriteLine(“Content (re)cached”);
}

Az alábbi kód egy teljes, futtatható teszt, ami a demonstrálja a direkt fájlolvasás (implicit OS cache), és a kézi cache-elés közötti teljesítménykülönbséget:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Web;
using System.Web.Caching;

class Program
{
const string file = @”c:\temp\a.log”;
const string cacheKey = @”alog”;
const int loopLen = 50000;
private static HttpRuntime httpRuntime;
const int HRSharingViolation = -2147024864;

static void Main(string[] args)
{
httpRuntime = new HttpRuntime();

Thread t = new Thread(FileChanger);
t.IsBackground = true;
t.Start();

int cc = GetSumGCCount();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < loopLen; i++) { TestWithoutCaching(); ShowProgress(i); } w.Stop(); Console.WriteLine("Elapsed time: {0}, collections during cached test: {1}", w.Elapsed, GetSumGCCount() - cc); cc = GetSumGCCount(); w = Stopwatch.StartNew(); for (int i = 0; i < loopLen; i++) { TestWithCaching(); ShowProgress(i); } w.Stop(); Console.WriteLine("Elapsed time: {0}, collections during noncached test: {1}", w.Elapsed, GetSumGCCount() - cc); } private static void ShowProgress(int i) { //Console.WriteLine(i); //if (i % (loopLen / 100) == 0) Console.WriteLine("{0}%", i * 100 / loopLen); } static void FileChanger(object state) { while (true) { string s = ProtectedRead(file); ProtectedWrite(file, s); Console.WriteLine("File has been modified sucessfully."); Thread.Sleep(100); } } private static int GetSumGCCount() { GC.Collect(); GC.WaitForPendingFinalizers(); int cc = 0; for (int gen = 0; gen < GC.MaxGeneration; ++gen) { cc += GC.CollectionCount(gen); } return cc; } private static void TestWithoutCaching() { string s = ProtectedRead(file); for (int i = 0; i < s.Length; i++) { char c = s[i]; } } private static void TestWithCaching() { object o = HttpRuntime.Cache[cacheKey]; if (o != null) { string s = (string)o; for (int i = 0; i < s.Length; i++) { char c = s[i]; } } else { CacheDependency d = new CacheDependency(file); string s = ProtectedRead(file); HttpRuntime.Cache.Add(cacheKey, s, d, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); Console.WriteLine("Content (re)cached"); } } static string ProtectedRead(string file) { string s = null; bool retry = false; do { try { s = File.ReadAllText(file); break; } catch (IOException ex) { int hr = Marshal.GetHRForException(ex); if (hr == HRSharingViolation) { retry = true; Console.WriteLine("File is locked while reading, retrying..."); RandomSleep(); } else { throw; } } } while (retry); return s; } static void ProtectedWrite(string file, string content) { bool retry = false; do { try { File.WriteAllText(file, content); break; } catch (IOException ex) { int hr = Marshal.GetHRForException(ex); if (hr == HRSharingViolation) { retry = true; Console.WriteLine("File is locked while writing, retrying..."); RandomSleep(); } else { throw; } } } while (retry); } private static void RandomSleep() { int r = new Random().Next(30, 200); Thread.Sleep(r); } } [/source] Hogy realisztikus legyen a teszt egy külön szál írogatja is a kérdéses fájt, ami az egyszerűség kedvéért most egy lokális, 256ks állomány. Az író-olvasó folyamatok persze időnként összeakadnak, ezért a hibakezelő és ismétlő logika a kódban. Bár elég sok mesterséges késleltetés van a példában, így is eléggé marakodik a két oldal. Nem csak az időket mérem a kódban, hanem a szemétgyűjtések számát is a két esetben. A direkt fájlolvasás esetén minden egyes olvasáskor újabb és újabb buffert allokálunk, ami persze jelentős memóriakényszert okoz. Habár lehetne mondjuk valami thread-local buffert előre allokálni és újrahasznosítani, a legtöbben úgyis ezt a kevésbé hatékony kódot használják, így realisztikus a teszt. A cache-elős példa esetén egyszerűen csak kapunk egy referenciát a memóriában már benn levő fájltartalomra, így nem történik nagyméretű memóriafoglalás sem. Érdemes tudni, hogy a nagyobb memóriatartalmakat a CLR a Large Object Heap-en tárolja, amely kevésbé hatékony mint a sima heap, szóval érdemes ésszel élni vele.

Lássuk a program kimenetét, azaz az eredményeket:


File is locked while reading, retrying…
File has been modified sucessfully.
File has been modified sucessfully.
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while reading, retrying…
File has been modified sucessfully.
File is locked while reading, retrying…
File has been modified sucessfully.
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
Elapsed time: 00:00:01.2796481, collections during noncached test: 252
Content (re)cached
Elapsed time: 00:00:00.0826961, collections during cached test: 2

Látható, hogy a saját cache nagyon sokat gyorsít, ráadásul a memóriát is sokkal jobban kíméli. Valójában annyira gyors a cache-elős eset, hogy az író szálnak nincs is elég ideje módosítani a fájt, ami amiatt fontos, hogy lássuk, működik-e a cache invalidálás, azaz érzékeli-e a cache, hogy módosult a fájl. Ha az iterációk számát felvesszük a példában 5000-re, akkor már lesznek olyan esetek, amikor a CacheDependency észreveszi a fájváltozást:

File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
File is locked while reading, retrying...
File has been modified sucessfully.
Content (re)cached
Content (re)cached
File has been modified sucessfully.
File is locked while reading, retrying...
File has been modified sucessfully.
Content (re)cached
Content (re)cached
Elapsed time: 00:00:00.7075406, collections during cached test: 5

Remélem tanulságos volt, de ha benéztem valamit, és nem érvényes a teszt, szóljatok, javítom.

Snapshot elszigetelési szint a SQL Server 2005-től

Wednesday, February 25th, 2009

Eléggé elfeledett téma ez az SQL Serverben, pedig önmagában ez az új szolgáltatás eladta volna az SQL 2005-öt.
Update: korábban itt egy url volt a technetre, de onnan lekerült a cikk, ezért feltöltöttem ide:

Bevezető az elszigetelési szintekbe
Az SQL Servernek biztosítani kell a párhuzamosan futó adatmódosító és olvasó folyamatok békés egymás mellett élését. A párhuzamos műveletek egymásra hatását teljesen meg lehetne akadályozni, ám ez súlyosan visszavetné az adatbázis kiszolgálási sebességét. Ehhez egyszerűen csak be kellene rakni egy kapuőrt, aki addig nem enged be egy új tranzakciót, míg az éppen futó konklúzióra nem jutott, azaz committal vagy rollback-kel véget nem ért. Könnyen belátható, hogy ezzel kiirtanánk minden párhuzamosságot az adatbáziskezelőből, egyfelhasználósra degradálnánk.
A káosz és a sorosított tranzakciók között vannak kompromisszumos közbenső szintek, ezek az elszigetelési szintek, isolation level-ök. Könnyű elképzelni, hogy két író műveletet, amelyek ugyanarra az erőforrásra, pl. tábla sorra vonatkoznak, nem lehet párhuzamosítani. Így az elszigetelési szintek a párhuzamos író és olvasó folyamatok közötti áthatást tudják szabályozni. Egészen pontosan, az elszigetelési szintek a selectekre hatnak. Az insert, update, delete-re nem, ezt érdemes a fejünkbe vésni már az elején.

A kérdések a következők:
1. Láthat-e egy select olyan adatokat, amelyeket egy másik, nyitott tranzakció éppen módosít (dirty records)?
2. Elvárható-e, hogy egy tranzakcióban a kiolvasott adatokat ugyanazon selecttel újra kiolvasva pontosan ugyanúgy néznek ki a sorok, mint korábban, vagy megváltozhatnak a két olvasás között, azaz, megismételhető-e az olvasás (repeatable read)?
3. Mi van, ha az előbbi olvasások között új sorokat szúrnak be a táblába? Láthatóvá válnak-e ezek a szellem sorok a második olvasásra (phantom records)?

Az SQL Server mindhárom kérdésre ad válaszokat, már idétlen idők óta.

A READ UNCOMMITTED elszigetelési szinten futó select láthatja az éppen módosítás alatt álló sorokat, amelyeket lehet, hogy egy másodperc múlva már vissza is görgetnek.
A READ COMMITTED alapértelmezett szinten nem látunk piszkos rekordokat, de belefuthatunk a nem megismételhető olvasás és a fantom sorok problémájába.
A REPEATABLE READ szinten garantálják az olvasások megismételhetőségét, de fantom sorok még így is megjelenhetnek.
A SERIALIZABLE szint az összes olvasási anomáliára gyógyír, cserébe a párhuzamosság nagyon erősen lecsökken.
Ezen szinteket a zárolások idejének és módjának variálásával képes előállítani a szerver, a részletekről egy régebbi cikkemből érdemes tájékozódni.
http://soci.hu/publications.aspx
Jól látható, hogy az elszigetelési szintek megfelelő megoldást adnak a különböző élethelyzetekben fellépő párhuzamossági problémákra. Vagy mégsem? Ha így lenne, nem jött volna létre ez a cikk.

A Snapshot elszigetelési szint alapjai
Az előző szintek közös ideológiai és technológiai jellemzője, hogy zárolássokkal védik meg egymástól a párhuzamos folyamatokat. Ez elvileg korrekt megoldást ad, de időnként túlságosan lekorlátozza a szerver párhuzamosságát.
Jön egy folyamat, indít egy tranzakciót, amiben nekiáll módosítani egy tábla felét. A módosítás mondjuk 5 percig tart. Ez idő alatt READ COMMITTED vagy szigorúbb szinten senki nem tudja olvasni a tábla módosítás alatt álló részeit, mert zárolás alatt állnak. Miért nem lehet azt tenni, hogy a select visszakapja a sorok módosítás előtti állapotát?
Hasonlóan, ha fut egy nagyobb analitikai lekérdezés vagy riport, ami intenzíven olvassa a tábla tartalmát, akkor miért nem engedik módosítani közben a sorokat, úgy, hogy a select az adatok módosítás előtti állapotát lássa? Miért ne? Erre találták ki a snapshot elszigetelési szintet.
Snapshot esetén nem zárolássokkal dolgozik a szerver, hanem ún. sorverziózással. Amikor egy SNAPSHOT izolációs szinten futó folyamat adatokat kérdez le, nem rak a szerver csak olvasható (shared) zárakat a sorokra. Ehelyett, ha jön író folyamat, a módosított sorok módosítás előtt állapotát letárolja a tempdb-ben, az ún. version store-ban, verziótárban. Ha az olvasó folyamat vagy akár más folyamatok is újra a kérdéses adatokat select-álják vissza, a verziótárból szedi össze a szerver a módosított sorok korábbi állapotát. Ha többen is nekilátják módosítani ugyanazt a tartományt, akkor többféle verzió is keletkezik a tempdb-ben, amelyek láncolt listában tárolódnak, és lekérdezéskor ebből keresi ki a szerver a szükséges verziót.
Mit nyerünk és mit vesztünk, hogy áll a zárolás kontra sorverziózás vetélkedő? A verziózás nyilvánvaló (óriási) előnye, hogy az olvasók nem blokkolják az írókat és vica versa. Végül is ezért vezették be a szintet. Sajnos viszont az is nyilvánvaló, hogy a módosítások lassabbak lesznek, mivel verziózni kell minden módosított sort. A lekérdezések is lassabbak lesznek, mert ha vannak nyitott író tranzakciók, utána kell nézni a soroknak a tempdb-ben is, hátha van korábbi verziójuk.
Másfelől viszont verziózás esetén nem kell shared lockokat rakni az olvasott sorokra vagy lapokra, így ez a költsége meg kiesik. A kettő közötti döntéshez pontosabban meg kell ismernünk a snapshot elszigetelés belső működést, vágjunk hát bele a részletekbe.
Snapshot üzemmódok bekapcsolása
Kétféle módon használhatjuk a snapshot elszigetelési szintet, utasítás és tranzakció szinten is, fontos megérteni a pontos különbséget a kettő között.
Egyik snapshot üzemmód sincs alapban bekapcsolva az adatbázisokon, azaz az SQL 2005-ös és 2008-as adatbázisok is zárolással működnek, hasonlóan az SQL Server 2000-hez. Bekapcsolásuk ALTER DATABASE-zel lehetséges:

alter database SnapDB
set allow_snapshot_isolation on

illetve

alter database SnapDB
set read_committed_snapshot on

Bekapcsolás után erősen megnőhet a tempdb mérete és terhelése, erről hamarosan részletesen írok. Minden egyes tábla sora, amelyen update vagy delete műveletet futtatunk, kap egy 14 bájtos plusz adattagot, egy mutatót, azaz ennyivel lesz hosszabb minden módosított sor. Ettől laptörések (page split) következhetnek be, ami töredezetté teheti a táblát, így bekapcsolás és némi használat után érdemes megfigyelni az érintett táblák töredezettségi szintjét, de defragmentálni, ha indokolt.

Snapshot Isolation (SI)
Ha az allow_snapshot_isolation be van kapcsolva, akkor a tranzakciókban átválthatunk SNAPSHOT elszigetelési szintre, amitől tranzakció-szintű sorverziózást kapunk.
Alapállás:

create table Gyumolcs (id int, nev nvarchar(50))
insert Gyumolcs values(1, N'Alma')

“A” kapcsolaton végrehajtjuk a következőt:

begin tran
update Gyumolcs set nev = N'Körte' where id = 1	

Látható, a tranzakció nyitva maradt. “B” kapcsolaton:

set transaction isolation level snapshot
begin tran
select * from Gyumolcs
-- 1 Alma

Átváltottunk snapshot elszigetelési szintre, és felolvassuk a táblát. Az első megfigyelés, hogy nem blokkolódik az olvasó (B) tranzakció, hanem azonnal visszatér a sor módosítás előtti állapotával, azaz az Almával. Snapshot nélkül a select várakozna az első tranzakció végére, commit estén a módosítás utáni állapotot látnánk, rollbacknél az előttit.
Zárjuk le az első tranzakciót, véglegesítve a módosítást:

Commit	

Adjuk ki még egyszer a selectet a második, még nyitott tranzakcióban:

select * from Gyumolcs
-- 1 Alma

Kimenet: továbbra is Alma. Miért? SNAPSHOT szinten tranzakció-szintű sorverziózást kapunk, azaz garantáltan ugyanazt az adatok kapjuk vissza második olvasásra is, amit a tranzakció elején. REPEATABLE READ, sorverziózással.
Zárjuk le a második tranzakciót is egy committal. Ekkor nem történik adatbázisműveletet, hisz csak olvastunk ebben a tranzakcióban, de a verziótár tudja, hogy lehet takarítani a korábban eltárolt sort, nincs már senkinek szüksége rá. Ebből következik, hogy hosszú ideig nyitott tranzakciók miatt igen nagyra tud nőni a verziótár (a tempdb), így erre fokozottan kell ügyelni az adatelérő réteg tervezésénél.
Tovább vizsgálódva nézzük meg, mi a helyzet a fantomsorokkal?
A másodikon kapcsolaton:

set transaction isolation level snapshot
begin tran
select * from Gyumolcs
-- 1 Körte

Kimenet: Körte, az előző teszt módosításának végeredménye.

Az első kapcsolaton szúrjunk be egy új sort:

begin tran
insert Gyumolcs values(2, N'Barack')	
	select * from Gyumolcs
-- 1 Körte

A második kapcsolaton megismételve a selectet továbbra is csak a Körte sor látszik, az új Barack nem. Ez akkor is így marad, ha commitoljuk az első tranzakció beszúrását. Azaz látjuk, hogy a SNAPSHOT elszigetelési szint véd a fantom sorok ellen is. Ez azért nagy szám, mert ugyanazokat a tranzakcionális garanciákat kapjuk SNAPSHOT szinten, mint a zárolós működési módban a legszigorúbb SERIALIZABLE szinten, mégis, a folyamatok párhuzamosságát sokkal kevésbé korlátozva. Ennek nyilván a verziótár állandó frissítésével fizetjük meg az árát.
Rendben, a módosítások és a frissítések jól megférnek egymás mellett, és garantálja nekünk a szerver, hogy a lekérdezések az őket magába foglaló tranzakció kezdetéhez képesti legfrissebb adatot adja nekünk vissza. Ez adatelemzési szempontból óriási előny lehet, azonban a módosításokat kicsit megnehezíti. Miért?
Hajtsuk végre a következő scriptet az B kapcsolaton keresztül:

set transaction isolation level snapshot
begin tran
select * from Gyumolcs 
where id = 1
-- 1 Körte

Látjuk a Körte sort, eddig semmi meglepő.
A kapcsolaton módosítjuk B kapcsolaton leválogatott sort:

begin tran
update Gyumolcs set nev = N'Narancs' where id = 1	

A Körte most már Narancs, de még nincs véglegesítve a tranzakció. B folyamat újraolvashatná a sort, és látná az eredeti állapotot, az Körtét, ezt már tudjuk. De B most módosítani akarja ugyanazt a sort!

update Gyumolcs 
set nev = N'Narancs' where id = 1
-- Várakozik ...

Mit tehet ilyenkor a szerver? Olvasás esetén elővehette az eredeti verziót, de ha most is ezt tenné, akkor a tranzakció végén fejbe vágná a két tranzakció egymás módosítását, azaz a sor végső állapotát az döntené el, hogy melyik tranzakció végzett később. Ezt a problémát lost update-nek nevezzük, elveszett módosításnak. A cikk elején felsorol anomáliák között azért nem szerepelt ez, mert ez csak optimista, nem zárolós sémák esetén jelentkezik, a zárolós-pesszimista üzemmódnál nem.
No, az SQL Server úgy kezeli ezt az esetet, hogy megvárakoztatja a második módosító tranzakciót. Ha az első egy committal ezek után eldönti, mit akar, a második megkapja a magáét:

commit	

Msg 3960, Level 16, State 4, Line 1
Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation to access table ‘dbo.Gyumolcs’ directly or indirectly in database ‘SnapDB’ to update, delete, or insert the row that has been modified or deleted by another transaction. Retry the transaction or change the isolation level for the update/delete statement.

Logikus lépés, valahogyan tudatni kell a második tranzakcióval, hogy megelőzték. A tranzakciót már le se kell zárni, mert ezt megtette helyettünk a szerver. Sőt, nem is szabad, hisz nincs értelme, ezt is gyorsan megtapasztaljuk:

	commit

Msg 3902, Level 16, State 1, Line 1
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

Azaz az első tranzakció sikeresen módosított a sort. Mit tehet a vesztes tranzakció? Ott van a tipp a hibaüzenetben, újra lehet próbálkozni. Másodikra talán mi leszünk a gyorsabbak. Ha belegondolunk, a zárolós-pesszimista üzemmód esetén is vannak hasonló esetek, ott deadlockok esetén kell ismétlő logikát berakni az alkalmazásba, itt pedig a 3960-as hibát kell speciálisan lekezelni (az adatelérő rétegben, vagy egy TSQL TRY-CATCH-ben).
Ez a hiba valamelyest segít dönteni abban, hogy érdemes-e átváltanunk erre az új elszigetelési szintre. Ha kevés módosítási konfliktust élünk meg, azaz sok az olvasó folyamat, de kevés az módosító, akkor a SNAPSHOT jó választás, nagyon sokat segíthet a párhuzamos végrehajtásban, így a szerver áteresztő képességében (hány tranzakciót tud végrehajtani egységnyi idő alatt).
Viszont sok párhuzamos író folyamat esetén (pl. sok forrásból táplálkozó adatgyűjtő rendszer) a régi zárolós üzemmód a megfelelőbb. Ebben az esetben viszont a deadlockokat kell ismétléssel lekezelni, csak ezekből várhatóan kevesebb lesz, így erre az oldalra billen el a mérleg nyelve.

Read Committed Snapshot Isolation (RCSI)

RCSI esetén nem kell explicit átváltani SNAPSHOT szintre set transaction isolation level paranccsal, hanem az eddigi alapértelmezett READ COMMITTED szint működése alakul át verziózottá. Azaz anélkül, hogy a kisujjunkat is meg kellene mozgatni, máris élvezhetjük a megnövekedett párhuzamosságot (meg a megnövekedett tempdb-t :).
A SI és az RCSI között azonban alapvető különbség van: míg SI estén láttuk, hogy az olvasó tranzakció kezdetéhez képest kapunk vissza konzisztens adatokat, ismételt olvasási garanciával, addig RCSI esetén a select utasítás kezdetéhez képest kapunk vissza egységes adatokat. Azaz nincs ismételt olvasási és fantom sorok elleni garancia.
Nézzük meg az első példánkat, ezúttal RCSI szinten. Hogy teljesen tiszta legyen a kép, kapcsoljuk ki a sima SI-t:

alter database SnapDB
set allow_snapshot_isolation off

Majd váltsunk át az RCSI üzemmódba a következő paranccsal:

alter database SnapDB
set read_committed_snapshot on

A parancs csak akkor hajtódik végre, ha csakis ezt a parancsot végrehajtó kapcsolat van benn az adatbázisban. Ügyeljünk, hogy a Management Studio Object Browsere is nyitva tart egy kapcsolatot az adatbázisra, ha annak belseje ki van nyitva a fában. Zárjuk be a fa csomópontját, és felszabadul a kapcsolat.
Bekapcsolás után hajtsuk végre a korábbi tesztünket ezen a szinten:

Conn1:

1.
create table Gyumolcs (id int, nev nvarchar(50))
insert Gyumolcs values(1, N'Alma')	

Conn2:

2.
begin tran
select * from Gyumolcs --1 Alma

Conn1:

3.
begin tran
update Gyumolcs set nev = N'Körte' where id = 1	

Conn2:

4.
select * from Gyumolcs --1 Alma

Conn1:

5.
commit	

Conn2:

6.
select * from Gyumolcs -- 1 Körte
Commit

Látható, hogy a nyitott módosító tranzakció esetén (3) a módosítás előtt adatot kapjuk vissza (4), éppúgy, mint a SI esetén. Ha azonban a módosítás véglegesítésre került (5), az olvasó folyamat már a módosítás utáni állapotát látja a sornak (6). Ez az alapvető különbség az RCSI és az SI között.
RCSI esetén nincs konfliktus update esetén, a második módosító folyamat ugyanúgy blokkolódik, mint SI esetén, azonban az első író commitja után a második is sikeresen végrehajtja az update-jét.
Azaz RCSI esetén számíthatunk fejbe vágott módosításokra, nem megismételhető olvasásokra és fantom sorokra. Cserébe viszont jelentősen kevesebb adat kerül a verziótárba, hisz nem kell olyan hosszú ideig megtartani a módosítás előtti állapotokat. Megint valamit-valamiért.
A verziótár más felhasználásai
A tempdb-ben kialakított verziótár elsődlegesen a Snapshot szintek kedvéért került kialakításra. Ám ha már elkészült, más szolgáltatást is építettek rá.
A triggerek már eddig is egyfajta verziózott módon működtek, hisz pl. a deleted tábla a sorok módosítás előtti állapotát mutatta. Ezt SQL Server 2000-ben a tranzakciós napló visszaolvasásával állították elő. A megoldás nagy hátránya volt, hogy az egyirányú írásra optimalizált tranzakciós naplót időnként vissza kellett olvasni, ami miatt megnövekedett a HDD fejmozgások száma, így lecsökkent a tranzakciós log írási sebessége.
SQL 2005-től kezdve a triggerek inserted és deleted virtuális tábláit a verziótár segítségével szintetizálják. Akkor is, ha egyik snapshot üzemmód sincs bekapcsolva. Ettől megszűnik a logra gyakorolt kedvezőtlen hatás, de megnő a tempdb terhelése.
A verziótár másik jelentősége az online indexműveleteknél jön a képbe. SQL 2005-től az indexműveleteket (create, drop, rebuild) az érintett tábla zárolása nélkül is végrehajtható, ezek az online műveletek. Például:

alter index all on Production.Product
rebuild with (online = on);

Amikor elkezdődik az új index felépítése a szerver belül “átvált” SNAPSHOT izolációs szintre, így a meglévő index adatait a tranzakció, az index építés kezdetéhez képest konzisztens módon képes átmásolni. Miközben építi fel az új, párhuzamos indexfát közben más kapcsolatokon keresztül módosíthatják az alaptáblát és az indexet is. A módosítások egyszerre módosítják az eredeti indexet és az épülőt is. A verziótároló a tempdb-ben közben szépen rögzít minden változást, így a módosítások nem kerülnek duplán átvezetésre.
Ami számunkra ebből kívülről látható lényeg, hogy az indexművelet alatt is elérhető a tábla és az index.
A Multiple Active Resultset (MARS) nevű harmadik technológia az, ami még kihasználja a verziótárolót, ám ennek működését most nem részletezem.

Verziótár takarítás, monitorozás

Ha indokolatlanul nagynak tűnik a tempdb vagy valamelyik snapshot szint bekapcsolása után szeretnénk látni mennyire intenzíven használja a szerver a verziótárat, többféle úton is kutakodhatunk.
Ha arra vagyunk kíváncsiak hány sor van a verziótárban, futtassuk le a következő parancsot:
select count(*) from sys.dm_tran_version_store
SI tranzakciók esetén a tranzakció végén, RCSI tranzakcióknál a select lefutása után már nincs szükség a módosított sorok verziótörténetére. Ezért percenként lefut egy háttérszál a szerverben, amely kitörli a már nem szükséges sorokat.
Ha a tempdb kifutna a számára behatárolt helyből, akkor a takarító azonnal elindul, hátha fel tud szabadítani elég helyet, így nem kell megnövelni az adatbázist. Tisztára olyan ez, mint a .NET Framework Garbage Collectora, csak az a memóriát söprögeti.
A Version Generation Rate és a Version Cleanup Rate teljesítményszámlálók segítségével durván láthatjuk mekkora a sürgés-forgás a tárban.
Az Update conflict ratio túl nagy értéke azt sugallja, lehet, hogy érdemes visszatérni a zárolós üzemmódhoz, mert túl sok az író folyamat.
A verziótár halála egy idétlen tranzakció, amely – általában hibakezelési hiba miatt – túl hosszú ideig fut. Azaz elfelejtik lezárni a tranzakciót, de az adatbáziskapcsolatot nyitva hagyják. Egy ilyen tranzakció a zárolásos üzemmód esetén hosszú blokkolási láncokat, így akár az egész adatbázisra épülő alkalmazás leállását okozhatta. Most nem áll meg az élet, de lehet, hogy a tempdb nagyon nagyra nő, és belassul az adatbázis. A Longest Transaction Running Time nevű számlálóban meg lehet nézni, van-e valaki beragadva. Ha itt meglátunk mondjuk 1800 másodpercet, azaz fél órát, miközben tudjuk, hogy a tranzakcióink 1 percen belül lefutnak, akkor sejtjük, hogy valaki beragadt. A bűnöst a következő lekérdezéssel érhetjük tetten:

select elapsed_time_seconds, session_id 
from sys.dm_tran_active_snapshot_database_transactions
elapsed_time_seconds session_id
-------------------- -----------
848                  52

Az 52-es session (kapcsolat) már 848 másodperce tollászkodik, ha nem indokolt a munkája, kilőhető.
Használjuk vagy sem?

Zárásul nézzük meg, milyen szempontok alapján tudunk választani a kétféle snapshot elszigetelés között, illetve mire számítsunk, ha úgy általában átállunk a zárolós üzemmódról valamelyik verziós üzemmódra.

Általánosságban a RCSI-on érdemes először elgondolkodni, mert:
• Kevesebb helyet igényel a tempdb-ben mint a SI
• Használható elosztott tranzakciókban is, a SI nem
• Nem lesznek update konfliktusok
• Nem kell átírni az alkalmazásokat. Egyszerűen be kell kapcsolni az adatbázison RCSI-t, és máris minden alkalmazás élvezi az előnyeit.

Mikor érdemes ezek után az SI-t bevetni?
• Ha általában kevés a párhuzamosan futó módosító folyamat, így kicsi az update konfliktusok valószínűsége
• Olyan lekérdezéseket, riportokat kell futtatni, amelyeknek fontos az időpont-konzisztencia. Magyarul ha egy tranzakcióban többször is át kell gyúrni az adatokat és fontos, hogy közben az adathalmazunk stabil legyen, akkor ezt csak a SI tudja biztosítani, a RCSI nem. Például a tranzakcióban először aggregáló lekérdezéseket futtatunk egy táblán, aztán a részletes adatokat válogatjuk le. SI nélkül inkonzisztens lehetne a kimenet, azaz a részletes adatok nem vágnának egybe az aggregált eredményekkel.

Vessük most össze a verziós üzemmódokat a zárolós üzemmódokkal.

A verziózás előnyei a zárolással szemben:
• A select nem rak csak olvasható (shared) zárakat a táblára, így az írók és az olvasók nem akadályozzák egymást (ez a legfőbb érv a verziózás mellett)
• A selectek időben konzisztens képet adnak vissza (SI és RCSI esetén másképp, lásd korábban)
• Sokkal kevesebb zárat (lockot) kell nyilvántartani a szervernek, ami jelentős erőforrást takarít meg
• Kevesebb zár-eszkaláció történik (amikor sok sor vagy lap szintű zárat átkonvertál tábla szintű zárrá a szerver, hogy erőforrásokat szabadítson fel)
• Kisebb a deadlockok valószínűsége

Természetesen vannak hátrányai is a verziózásnak, másként automatikusan ezt használnánk. Ezek a következők:
• A selectek lelassulhatnak, ha hosszú verzió-láncokat kell végignézni sok nyitott tranzakció miatt
• A tempdb-ben helyet kell neki biztosítani
• Az adatmódosító műveletek verziókat fognak generálni a verziótárolóban, még akkor is, ha nincs közvetlenül rájuk szükség. Azaz lassulnak az adatmódosítások.
• Minden sor ami a verziózás bekapcsolása után módosult 14 bájttal hosszabb lesz (mutató a verziótár megfelelő bugyrára)
• Az update lassabb lesz a verziótárolás miatt
• SI esetén az update konfliktusba kerül párhuzamos folyamatok update-jeivel, amit le kell kezelni az alkalmazásnak
• Ügyelni kell a tempdb méretére, nehogy megszaladjon.

Összefoglalva elmondható, hogy a snapshot elszigetelési szintek megjelenése hatalmas fegyvertény, amellyel sok párhuzamosan működő rendszer működését lehet drámaian felgyorsítani, illetve nagyban megkönnyíti olyan alkalmazások átírását más adatbáziskezelő rendszerről, amely verziózással működik.

.NET teljesítményhangolási tapasztalatok 6.

Monday, February 23rd, 2009

A Connection Poolról még pár gondolat. Jó, hogy van ez a pool, meg gyorsít is, örülünk neki, főleg webalkalmazásokban. Olyan appokban viszont, ahol állandóan futnak a dolgaink, mint egy asztali alkalmazás esetén, nem biztos, hogy érdemes mohón nyitni-zárni a kapcsolatot.
A tőzsdei kereskedési algoritmusaim backtestje során sok százezer paraméterkombinációt néz végig a gép, próbálja meghatározni a legvalószínűbb nyerési esélyűt vagy legnagyobb profit/szórással rendelkezőt (ennél jóval bonyolultabb a dolog, de ez most nem tőzsdés bejegyzés lesz).
A DAL-t úgy írtam meg ahogy webalkalmazásokban megszoktam, így minden egyes trade mentésénél nyitottam-zártam a connectiont. Profilerrel megnézve kiderült, hogy a pool ellenére is a futási idő harmada az open/close-zal ment el. Emiatt készítettem kétféle SqlHelper osztályt, az egyik állandóan nyitott kapcsolattal működik, a másik az egyéb helyekre nyit-zár mind eddig.
Összegezve, a connection pool kiváló dolog pillanatokra futó alkalmazásokhoz, mint a webalkalmazások, de nem érdemes erőltetni, ha több százezerszer kell nyitni-zárni a kapcsolatot.

Kolompár Orbán: “Aki szeret dolgozni, az hazudik”

Saturday, February 21st, 2009

Akkor én egy retkes, büdös, hazug kutya vagyok, ám amint kidühöngtem magam, nekilátok dolgozni, és furcsa mód szeretem azt, amit csinálok. Ez van, perverz vagyok, kisebbség a társadalomban.

Mondják, hogy fejétől bűzlik a hal, ez nyilvánvalóan kiviláglik ebből az interjúból.

Webkiszolgálás sebességmérése logparserrel

Thursday, February 19th, 2009

Elég kevesen használják a logparsert, pedig szenzációs. Azt képzeld el, hogy szöveges adatokon tudsz sql lekérdezéseket futtatni. Pl. IIS logokon. Relációs adatbázis nélkül, és elég gyorsan. Van még kérdés? Nagyon jó, na. :)

Ha pl. szükséged van egy baseline-ra mielőtt nekiállnál optimalizálni a website-ot, jó lenne tudni az átlagos kiszolgálási időket mindenféle tartalomra, pl. aspxekre, akkor a logparserrel pillanatok alatt ki lehet nyerni statisztikákat.

Bővebben itt.

SQL BI konferencia március 3-án

Thursday, February 19th, 2009

(Sose tudom, kell pont ilyenkor a dátumba?)
Előadok a fenti konfon Reporting Services témában. Mivel az SQL Server BI része (OLAP, SSIS, SSRS) sokkal kevésbé ismertek mint a relációs motor, ezért ezen az Informatika Tisztán nem a technet vagy msdn konferenciákon megszokott itt a legújabb cucc, nézd milyen kúl megközelítés lesz, hanem a kezdő lökést szeretnénk megadni az érdeklődőknek.

Érdekes nekem ez a konferencia?

Ha már régóta érzed, hogy jó lenne érteni az Analysis Serviceshez (OLAP izék), vagy nem mertél még belefogni, mert olyan megfoghatatlan katyvasznak tűnik.
Ha tudod, hogy az Integration Services a DTS utódja, ahhoz még értettél (vagy azt se tudod mi volt az), de ezt még nem nézted meg, mert azt mondták az annyira más, hogy meg se merted nézni.
Ha hallottál róla, hogy nem menő már a Crystal Reports, vagy azt se tudod hogyan kellene belefogni az első riportba (amit egyébként szó szerint 5 perc alatt össze tudsz dobni), és érdekel a Reporting Services.
Ha hallottál valami adatbányászatról, de nem tudod, hogy melyik földkéreg tartalmaz adatokat, és érdekel, hogy ez tényleg valami használható technológia, vagy csak ráérős Microsoft Research kutatók gumicicája?

Szóval alapozást ígérünk, földközelbe hozzuk ezeket a kevésbé szem előtt lévő technológiákat. Gyere, várunk.

A magyar tőzsdepiac kintről szemlélve

Thursday, February 19th, 2009

Feliratkoztam egy-két angol nyelvű trading (tőzsde) blogra, és az egyikben látom, hogy egy táblázatban ki van emelve Magyarország és a környező országok. Kiemelték, mert akkora az államadósság, hogy garantáltan lehet fogadni pl. a Ft esésre vagy a hazai blue-chipek esésére. Kérdezi is valaki egy kommentben, hogy van-e kelet-európai ETF (Exchage Traded Fund, olyan több részvényt magába foglaló alap, amivel lehet kereskedni), amin lehetne egy jót shortolni.
Szomorú ez, kívülről végül is csak pár kis diagram vagyunk, ami szépen zuhan lefelé, elég biztosan lehet az esésre fogadni. De, hogy közben mi lesz itthon velünk, akik ennek részesei vagyunk, azt nem tudom.
Okos politikusaink szépen bevezetnek minket a mocsár közepére, ahol elsüllyedhetünk. Mi meg megyünk mint a birkák. :(

64 bites laptopon

Tuesday, February 17th, 2009

Pénteken megjött az új 64 bites laptopom, egy Dell Latitude E6500. 8 G RAM van benne. :)
Sajnos az ára kb. nettó 60e-rel drágább lesz, mint amikor megrendeltem, mert közben a Ft elszállt a fenébe. :(
Először felraktam rá Windows 7-et, ám se az SQL Server, se a VS nem akart rá felmenni, így feladtam a vele való harcot – egyelőre. Tudom, hogy másnak mennek ezek, de nem tudom, mit rontottam el.
Most már egy Windows 2008 van rajta, workstation-ösítve.
Az aero még nem megy rajta, csak a sima Vista theme. Nem tudom miért, de első körben ettől nem lesz kisebb a produktivitásom. :)
A multimédiás dolgokhoz átállítottam a kernel ütemezőjét (ennek működéséről majd írok a Winternals sorozatban), kíváncsi vagyok hd filmek hogyan fognak majd menni rajta. Egyelőre néha még egy winampos zenelejátszásnál is beszaggat.
Azt gondoltam 8 G mindenre elég lesz. Erre tegnap elindítottam egy lekérdezést a tőzsdei adatbázisomon, és elszállt a skype, eltűntek az ikonok, és még a task manager se indult el. Tisztára mint a Win 3.1-es időkben, amikor elfogytak a GDI handle-ök. :)
Kissé vissza kellett venni az SQL Server arczából, most már csak 6 G-t kap, ossza be.

Update: raktam be két fotót, az egyiken a gép van, a másikon a mesteremberek a processzor vízhűtésén fáradoznak. :)

Design Patterns tanfolyam – újra, kibővítve .NET 3.5-tel

Thursday, February 12th, 2009

Marcival újra összeállunk pár akcióra, ennek első megnyilvánulása, hogy újra lesz Design Patterns tanfolyam.
Már írom át az anyagot, a 3.5-ös .NET Fw. tele van szebbnél-szebb design példákkal, illetve az utóbbi 2 év gyakorlati programozása során jó pár dolog tovább formálódott, tisztult a fejemben, ezeket is beépítem az anyagba.

A referenciáim között megtekinthető, hogy volt, amikor cégek szinte összes programozója részt vett a tanfolyamon, felismerve a dolog hasznát a fejlesztési folyamatok minőségére.

És végül, aki bemásolja a következő kódot a jelentkezési lapjára, 20% kedvezményt kap a tanfolyam árából:

public class DP: Course, ISupportDiscount
{
public HappyStudent GiveMeThisDirtyGoodCourseCheaply() { }
public string Author { get { return “soci”; } }
public string Trainer { get { return “soci”; } }
public DateTime ActionTime { get { return DateTime.Parse(“2009.03.09”); } }
}

Szeretettel várok mindenkit.

.NET teljesítményhangolási tapasztalatok 5.

Thursday, February 12th, 2009

Szeretjük a connection poolt, de nem mindig.

Tudjuk, hogy manapság nem illik sokáig nyitva tartani az adatbázis kapcsolatot, hanem bemegyünk, kihozzuk a szükséges adatokat, majd lezárjuk a kapcsolatot. Régen (n > 10 év) ez nem volt okos dolog, mert lassú volt a belépés-kilépés. Ezért találták ki a Connection Poolt, amit OLEDB-ben még Session Poolnak hívtak. Egykutya.
Ez arról szól, hogy a kliens oldali adatelérő komponens, pl. az ADO.NET SqlClient providere (System.Data.SqlClient névtér és kölkei) újrahasznosítja a lezárt kapcsolatot. Azaz mi SqlConnection.Close-t mondunk, ő viszont a háttérben nem zárja le a kapcsolatot az adatbázis felé. Ha ezek után újra meg akarjuk nyitni egy kapcsolatot az adatbázis felé, nem hoz létre új kapcsolatot a provider, hanem visszaad egy használtat a poolból. Miért jó ez? Nyilván egy tényleges kapcsolat kiépítése igen költséges, TCP csatorna vagy Named Pipe session felépítése, autentikáció, ecetera. Ezt nagyon sokszor megússzuk, hála a poolnak. A pool 4-8 perc közötti véletlenszerű időközönként azért lezárogatja a használt kapcsolatokat, ott ne zápuljanak.
Nyilvánvaló, hogy felhasználónként egyedi poolok vannak, így egy sysadmin által eldobott kapcsolatot nem adja oda nekem, lópicinek a provider, ez csúnya luk lenne.
Lehet azért így is aljaskodni az új bérlőkkel. Mi van, ha becsatlakozok, kiadok egy use másik adatbázist, majd röhögve lezárom a kapcsolatot? És ha egy-ket set opciót átírok, pl. SET LANGUAGE urdi? Mit lát a következő hívó, aki megkapja a használt kapcsolatot? Hát amit beállítottunk az előző lépésben. Azért ez durván hangzik, ugye? Szerencsére a helyzet az, hogy alapban tiszta környezetet kapunk, köszönhetően annak, hogy a provider pool manager-e végrehajt egy sp_reset_connection hívást mielőtt visszaadná nekünk a használt kapcsolatot. Hogy ez pontosan mit csinál, azt egyszer már leírtam, tessék megnézni a listát.
Azaz bár nem kell minden egyes kéréshez új kapcsolatot megnyitni, azért egy sp hívás csak történik pluszban a háttérben. SQL Profilerben látszik, hogy ez nagyon gyors, de azért ez mégis csak egy hálózati körülfordulás.
És most jön a trükk. Ha teljesen biztosak vagyunk benne, hogy nem tolunk ki a következő hívóval aki megkapja a használt kapcsolatunkat, akkor kikapcsolhatjuk a takarítást. A connection stringbe ezt kell beírni:

Connection Reset=false;

Nézzük meg az alábbi kódot:

SqlConnection conn = new SqlConnection(“data source=.;initial catalog=ATS;Integrated security=true;”);
SqlCommand cmd = new SqlCommand(“sp_who”, conn);
cmd.CommandType = CommandType.StoredProcedure;

for (int i = 0; i < 3; i++) { conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); } [/source] Az SQL Profilerben ez látszik: [source='C'] EventClass TextData SPID Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 [/source] Hoppá, ez nem az, amit vártunk! Csak 1 logint az elején és 1 logoutot a végén, közben meg az sp_resetconnectiont és az sp_who-nkat. Nem működik a pooling? De. Csibész a profiler. "The Audit Login event class indicates that a user has successfully logged in to Microsoft SQL Server. Events in this class are fired by new connections or by connections that are reused from a connection pool." Ha bekapcsoljuk a profilerben az Event Subclasst, kicsit tisztul a kép: [source='C'] EventClass TextData SPID EventSubClass Audit Login -- network protocol: LPC 1 - Nonpooled RPC:Completed exec sp_who 52 Audit Logout 52 2 - Pooled RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC 2 - Pooled RPC:Completed exec sp_who 52 Audit Logout 52 2 - Pooled RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC 2 - Pooled RPC:Completed exec sp_who 52 Audit Logout 52 1 - Nonpooled [/source] Szóval van pooling, csak a profiler ravaszkodik. Már kivert a víz. Viszont valami ebben zavar. A pooling ugye a kliensen van. Honnan tudja a szerver egy új parancs beérkeztekor az egy új parancsnak számít a kliens részéről a még nyitott kapcsolaton, vagy csak a poolból visszakapott kapcsolaton hajtanak végre egy új parancsot? Szerintem ő mesterségesen szintetizálja a poolos login-logoutokat, az sp_reset_connection-ök beérkeztekor veszi őket körbe login-logout párossal, a feeling kedvéért. De ez csak tipp. Kapcsoljuk ki a connection resetet: [source='C#'] SqlConnection conn = new SqlConnection( "data source=.;initial catalog=ATS;Integrated security=true;connection reset=false;"); [/source] Mit látunk a profilerben? Semmi nem változott! .NET fw. 2.0 felett nem lehet kikapcsolni az sp_reset_connection-t! Ezért nincs már a legújabb doksiban benne ez a beállítás.

Foly. köv.

Silverlight 2.0 webalkalmazások fejlesztése tanfolyam

Tuesday, February 10th, 2009

Régi jó viszonyunk örömére meghívtak a fenti tanfolyamra a NetAcademiába, márciusban megtekintem. Köszönet érte.
A Silverlight nekem egy olyan technológia, amiről tudom, hogy jó lenne tudni, de vagyok annyira lusta, hogy leüljek egy könyv elé megtanulni.
Kipróbálom milyen egy tanfolyam a másik oldalról. :)

.NET teljesítményhangolási tapasztalatok 4.

Thursday, February 5th, 2009

Óvakodj az enumtól, mert lassú lészen az – áll a .NET bibliában.

Ártatlan kis jószágnak néz ki ez az enum, mégis sokszor láttam már, hogy miatta lassul be egy rendszer. Mi a lassú rajta? Minden. A Parsolás pl. De ez még érthető is. De hogy a ToString() is tetű, az már kevésbé. Sajnos azonban reflectiont használ ezekhez a műveletekhez, ami köztudottan lassú.
Szerencsére elég könnyű segíteni a baján. Ha Parsolni kell, azaz egy string alapján kell egy enum értéket megszülni, akkor ezt egyszerűen meg lehet oldani egy Dictionary-vel, ami string kulcsokkal és az konkrét enum értékekkel van feltöltve.
Most a másik oldalt mutatom meg, a turbó ToString()-et.
Egyszerű demó enumunk:

public enum TradeDirection
{
Short,
Long
}

Ebből tehát ha van egy példányunk amin ToString()-et hívunk, az lassú lesz. Hozzunk létre egy Dictionary-t, ami segít az enum-string asszociációban.

private static Dictionary tradeDirectionNames = new Dictionary();

Ezt kellene felölteni értékekkel. A C# generikus dolgaival szépen meg lehet ezt általánosan is fogalmazni, így mindenféle enumra működni fog:

tradeDirectionNames.FillEnumCache();

public static void FillEnumCache(this Dictionary enumNames)
{
foreach (T enumValue in Enum.GetValues(typeof(T)))
{
enumNames.Add(enumValue, enumValue.ToString());
}
}

Mivel extension methodként írtam meg olyan, mintha a Dictionary tudná ezt a funkciót, a T típusparamétert meg kitalálja a compiler, így nem kell kiírjam a teljes alakot:

tradeDirectionNames.FillEnumCache();

Hogy jogos-e erre az extension method az objektumorientált szempontból erősen vitatható, de nekem így kézre esett.

A generizálhatóság viszont szenzációs. Ezért imádom a C#-ot.

Miután a fenti megoldással gyorsítottam a rendszeren kiderült (profilerrel, mi mással), hogy egy összetett típusban ami két enumból állt lassú volt a GetHashCode implementációm, ami így nézett ki:

public override int GetHashCode()
{
return dir.GetHashCode() + zone.GetHashCode();
}

A dir és a zone egy-egy enum.

A gyorsításhoz így írtam át:

public override int GetHashCode()
{
return (int)dir << 4 + (int)zone; } [/source] Ez nem csak gyorsabb, de jobban is szór, mint az előző.

Microsoft Application Request Routing for IIS 7

Wednesday, February 4th, 2009

Érdekes kis apróságra akadtam. A fenti cucc egy alkalmazásszintű router, amivel IIS-ekből álló webfarm gépeire lehet szelektíven ráirányítani a terhelést.
Tehát nem azt csinálja, mint az NLBS, hogy IP szinten dönt, hanem általunk megírt logika alapján osztja szét a terhelést a webszerverek között, ami adott esetben igen hasznos lehet.
Még nem látom át hogyan lehet ezzel kiküszöbölni a single point of failure-t, de rajta fogom tartani a szemem.

# HTTP based routing decisions
Unlike hardware load balancers that make the routing decisions at the IP level, Application Request Routing makes the routing decisions at the application level. Working with URL rewrite module, powerful routing rules can be written based on HTTP headers and server variables.
# Load balance algorithms
A user selected load balance algorithm is applied to determine which content server is most appropriate to service the HTTP requests. Six algorithms are provided.
# Health monitoring
Both live traffic and specific URL test are used to determine the health of content servers. A set of configuration parameters are provided to define the meaning of server health.
# Client affinity
Using a cookie, Application Request Routing can affinitize all requests from a client to a content server. It differentiates the clients behind NAT, so each client is treated independently. This feature requires that the clients accept cookies.
# Host name affinity
“Host name affinity” is a specific feature for shared hosters. It changes the deployment topology to minimize and streamline administration and to create additional business opportunities.
# Multiple server groups
Application Request Routing can manage multiple server groups, which are logical groupings of content servers in an environment. This feature allows Application Request Routing to be used in pilot management and A/B testing scenarios.
# Management and monitoring via UI
All configuration settings and aggregated runtime statistics of Application Request Routing are managed and viewable via IIS Manager.
# Failed Request Tracing Rules
Specific traces have been added to quickly troubleshoot and diagnose Application Request Routing.

.NET teljesítményhangolási tapasztalatok 3.

Wednesday, February 4th, 2009

Ne emelj (raise) kivételeket (exception), mert az erőforrásigényes (lassú).

A továbbiakban tartózkodok a zárójelektől. :)

Kivétel, benne van a nevében, csak kivételes, nem várt helyzetekben kell használni. Ennek ellenére vannak programozók, akik úgy hiszik nincs kúlabb kommunikációs forma a hívó és a hívott között, mint kivételt hányni az arczába. Cool biztos, és piszok lassú is.

Megint csak egy third-party gyártó vezérlőit használó appban láttam profilerrel, hogy az exceptionök kezelése igen sok időt elvitt a form felépüléséből. Csak az nem volt világos, miért kell ezerszámra exceptionöket dobni a vezérlőnek? Az egyik konstruktora ugyanis dobálta a NullReferenceException-öket, amit aztán elkaptak belül, de ettől még lassú lett a program.
Még profiler se kell az ilyen sunyi kivételdobálókat elcsípni, mert az Output Window tele van az ő exception-jeikkel, 1254 egy form open során.
Javasoltam a szerző cégnek, javítsák ki, mert nem normális, hogy egy library ennyi exceptiont dobjon és nyeljen el. Ez meglepően sok időt elvitt, és nagyon sok ágon megjelent a profilerben a nyoma.

A gyártó átírta a kódját, és láss csodát, kb. 3x gyorsabban töltődött be a form. 10 mp helyett 3mp nagyon nagy különbség ám pszichológiailag.

Konklúzió: ésszel azokkal az exceptionökkel, csak arra használjuk őket, amire tervezték.

Ehhez kapcsolódik még, hogy néha egy rosszul megírt API kényszerít minket felesleges exceptionkezelésre. A .NET Fw. 1.x-ben még csak olyan pl. Int32.Parse(string) volt, ami exceptiont dobott, ha nem volt megfelelő a kapott sztring. Képzelj el mondjuk egy csv olvasó programot, ami milliónyi sort olvas fel, egyes oszlopokat számmá alakítva. Ha mondjuk a sorok 10%-a hibás, százezer exceptiont kell elkapnunk. Meglesz az ára.

.NET teljesítményhangolási tapasztalatok 2.

Tuesday, February 3rd, 2009

Regexek használata esetén lehetőség van a regex előfordítására, amely során a konkrét regex kifejezésre generálnak egy kiértékelő assemblyt, ami aztán gyorsabban képes a regexet lefuttatni egy adott bemeneti szövegen, mint a nem előfordított megfelelője. Az előfordítás költsége igen magas, amely csakis akkor éri meg, ha utána nagyon sokszor kell a regexet különböző szövegekre lefuttatni. Ha csak egyszer használjuk fel a regexet és csak rövid bemenetekre, akkor sokkal de sokkal többet veszítünk az előfordítással, mint nyerünk.

Az egyik third-party vezérlőben volt az alábbi kód:

Regex regex = new Regex(@”\w+|[^A-Za-z0-9_]”, RegexOptions.Compiled |
RegexOptions.IgnoreCase);

A RegexOptions.Compiled ebben a felhasználásban (egysoros textbox) nagyon sok időt elvitt, indokolatlanul.

Az előfordítás további problémás jellemzője, hogy sokféle regex esetén minden egyes regexhez létrejön és betöltődik egy dinamikus assembly a memóriába, amely csak az appdoman unload esetén (Windows appnál ez a legtöbb esetben csak az app leállításakor, kevesen használnak saját appdomaineket) esik ki belőle. Sok regex esetén ez memóriaszivárgásként észlelhető, amely során a private memory fogy (mivel a generált majd jittelt kód nem osztható meg).

Mindezek ellenére senkit nem akarok lebeszélni az előfordításról, mert tetemes gyorsulás érhető el vele, ha kevés fajta regexet kell lefuttatni hosszú bemenetre.