Aktív/aktív SQL cluster

(Ezt a cikket küldöm Tonesznak meg mindenkinek, aki szereti:)

Mint említettem, mostanság éppen próbálok egy aktív/aktív SQL clustert építeni, de mivel sokaknak ismeretlen a téma cégen belül is, kicsit úgy érzem magam, mint Gallilei: hol szép, hol kevésbé szép szóval próbálnak jobb belátásra bírni. Úgyhogy gondoltam, hogy ahelyett, hogy engedek, inkább leírom, hogy hogyan is működik a Microsoft eme remek találmánya.

Kezdjük az elején, a cluster fogalmánál. A Microsoft cluster egy failover cluster, azaz hibatűrésre van kitalálva. Nem várhatunk tőle nagyobb teljesítményt, párhuzamosítást, satöbbit. Egy adott feladatot (tipikusan valamilyen hálózati szolgáltatás nyújtása) egy adott pillanatban csak egy gép végez. Az SQL Server külön fel van okosítva arra, hogy ő hogyan is fusson egy clusteren, úgyhogy már egy “sima” aktív-passzív cluster is okozhat zavart arra fel nem készített fejekben. Ahhoz, hogy SQL clustert építsünk, először is kell telepítenünk két Windows 2003 Enterprise Editiont (a 2008-as clusteringhez még hülye vagyok egyelőre, elég sokat változott) két azonos hardverre (izé, működik különböző vasakkal is, csak problémáink lesznek a supporttal, meg esetleg a clusterrel is). Kell továbbá plusz egy IP cím és egy hozzá tartozó hálózati név, valamint egy megosztott diszk (a quorum, mely általában a Q: betűt szokja kapni), melyet mindkét gép (avagy node) lát lokális diszkként (SAN, DAS, vagy csak egyszerűen iSCSI). Nem kötelező, de ajánlott még egy heartbeat hálózati kapcsolat a gépek között, ez két node-os clustereknél gyakran egy keresztkábellel megoldható (ha a távolság elég kicsi a fizikai gépek között). Ha ezek mind megvannak, akkor az első node-on (CLNODE1) elindítjuk a cluadmin.exe nevű kütyüt, és építünk egy új clustert, megadva a cluster nevét (MYCLUSTER), IP-jét (172.18.1.104). Majd a második node-on is elindítjuk ezt, és hozzáadjuk őt az új clusterhez. Ezzel elértük az alsó, szürke keretben látható állapotot: van egy Microsoft failover clusterünk, mely ebben a pillanatban még semmilyen gyakorlati hasznot nem hajt. (Több, mint két node-os clustert is lehet építeni, a második node-on végrehajtott lépések ismételgetésével.) Amikor módosítunk a resource-okon vagy a node-okon, érdemes mindig kipróbálni, hogy mindegyik node át tudja-e venni azt, amit át kell tudnia venni, azaz működik-e a failover funkció.

Építsünk rá egy SQL clustert is. Ehhez hasonló dolgok kellenek, mint az előbb: diszk, IP cím + hálózati név. Plusz SQL telepítő. SQL 2005-től apró, de a pénztárnál jelentősnek tűnő öröm, hogy nemcsak az Enterprise Edition clusterezhető, hanem a Standard Edition is, igaz utóbbi csak két node-ra, de úgyis ez a leggyakoribb. A telepítés megkezdése előtt hozzunk létre egy új resource groupot a cluadminban, és tegyük bele az SQL-nek szánt diszkeket. Teszteljük le, hogy mindkét node tudja-e birtokolni a diszkeket (erről Andor tudna mesélni). Fontos tudni, hogy az SQL Server csak ezekre a diszkekre enged majd adatbázis fájlokat tenni. Continue reading ‘Aktív/aktív SQL cluster’ »

GO vagy nem GO

A go egy sokak által kedvelt japán stratégiai táblajáték, erről szeretnék most néhány gondolatot megosztani a nagyérdeművel, de mivel ennek semmi köze az SQL-hez, ezért inkább a T-SQL-ben található GO-ról fogok írni.
A GO – mint azt definíciójából is megtudhatjuk – a batch separator. Ennyi. Mi is az a batch separator? Szoktuk látni SQL scriptekben, hogy tele van szórva az egész GO-val, minden második-harmadik utasítás előtt ott ül egy GO, és már mi magunk is betesszük mindenhova, hiszen bajt nem okoz, nélküle viszont néha nem fut le a lefutnivaló.
Mivel még mindig nem tudtunk meg semmi hasznosat, fordítsuk le a definíciót: a GO elválasztja a feldolgozási egységeket. Na, most már tudjuk, mire való! Adjunk oda az SQL Servernek egy scriptet:

/* elofeltetel1:
create table tabla1 (a int identity(1,1), b int, c int)
create table tabla2 (a int, b int, c int)
*/
/* elofeltetel2:
create procedure spTablaolvaso
as select a,b,c from tabla1
*/

select a, b, c from tabla1
select a, b, c from tabla2
spTablaolvaso

Ez a script látványosan el fog bukni, mondván, hogy a spTablaolvaso egy ismeretlen dolog számára. Írjuk át a scriptet:

select a, b, c from tabla1
GO
select a, b, c from tabla2
GO
spTablaolvaso

Ez már le fog futni (feltéve, hogy léteznek az objektumok). Mi a különbség? A GO-nak köszönhetően az SQL Server a második scriptet három részben dolgozza fel, gyakorlatilag úgy, mintha az SSMS-ben soronként kijelölve futtatnánk le az első scriptet (ami abban az esetben szintén működik). Mivel a spTablaolvaso egy tárolt eljárás, vagy az első utasításnak kell lennie a batchben, vagy meg kell, hogy előzze az exec(ute) utasítás. Azaz az első script GO nélkül is kiválóan futna így:

select a, b, c from tabla1
select a, b, c from tabla2
exec spTablaolvaso

Ez eddig leginkább kultúrtörténeti érdekességnek tűnik, amivel lehet villantani a kockakocsmában, de van némi gyakorlati folyománya is. Vegyünk egy másik scriptet:

update tabla1
set azonosito = NULL
delete from tabla where a = 1
insert into tabla2 values(1, 2, 3)

Az update el fog bukni, mert az azonosito oszlop egy identity mező, ami nem nullázható. A delete és insert így meg sem történik, hiszen az első hibánknál megáll a végrehajtás. Tegyünk ide is egy GO-t:

update tabla1
set azonosito = NULL
delete from tabla where a = 1
GO
insert into tabla2 values(1, 2, 3)

Ebben az esetben az update pont ugyanakkora hátast fog dobni, el sem jutunk a delete-ig megint, de az insert megtörténik, mivel az első hibánál megállt a végrehajtás – az adott batchen belül. És aztán fogta a következő batchet, amiben egy insert volt, és azt lefuttatta. Így lehet nagy scriptekben sok hibát generálni, amiket utána nehéz helyrerakni, mert minden batchet külön kell ellenőrizni. Az efféle problémákat egyébként általában ki tudjuk védeni explicit tranzakció használatával.
A másik érdekes eredmény, hogy nem lehet változókat használni GO-kon át:

declare @a int
select @a = 1
GO
print @a

Ez is pirosat ír. De ezen nem lepődtünk meg. Ez pont az a történet, mintha az utolsó sort magában lefuttatnánk: nem ismeri a batch a @a változót, mert nem benne lett deklarálva.
Vannak olyan helyzetek tehát, ahol lehet használni GO-t, és vannak, ahol nem. És vannak, ahol muszáj. Vannak olyan utasítások például, amelyeknek elsőként kell szerepelniük a batchben. Ekkor hasznos elé tenni egy GO-t, hiszen így azt a kulimunkát, hogy kijelöljünk és üssük az F5-öt, megteszi helyettünk a szerver. Ilyen például a CREATE TRIGGER.
Legközelebb talán írok a japán stratégiai játékról is.

AMD dual/quad core bug és SQL

(English version here)
A több core-os AMD CPU-kba annak idején egy kiváló bug került: a core-ok órája nem volt rendesen összeszinkronizálva, és ez mindenféle érdekes dolgot eredményezhetett. 2008 számomra ezen bugok töredékének felfedezéséről (is) szólt. A következőket sikerült begyűjtenem:

  1. Ping esetén negatív round-trip time.
  2. SQL Servernél tonna hiba az errorlogba
  3. A legfrissebb: find használatakor az mtime 0-val nem találja meg a még friss fájlokat (ez mondjuk egy RedHat-on ütött be, bocs az offtopicért, de ez szebb, mint a másik kettő).

Engem az SQL Serveres érdekelt leginkább. Ez konkrétan így néz ki az errorlogban:

Date 11/25/2008 9:57:47 AM
Log SQL Server (Current – 11/25/2008 12:49:00 PM)

Source Server

Message The time stamp counter of CPU on scheduler id 3 is not synchronized with other CPUs.

Az AMD csinált egy hotfixet a bugra, de alapvetően ők nem szervert, hanem desktopot akartak javítani. Miért kell egy desktopba dual-core? Leginkább játszani (bár a GTA4 már quad-core-t akar, két magon szaggat :). Ennek megfelelően a csoda hotfixben, melynek neve AMD Dual-Core Optimizer Version 1.1.4 a clock driftinget a Gaming Mode bekapcsolásával lehet minimalizálni. Sajnos teljesen nem javította meg a problémát, de harmincad részére csökkentette az előfordulását.
Az ötlet amúgy elég hülyén hangzik, hogy gamer patch-csel javítsak élesüzemű SQL-t, de ha a spanyol MSFT SQL support team megtehette, akkor én is :)

Tranzakciós log ürítése és nem ürítése

Mostanság SQL DBA-t keresek, és szakmai tesztekkel és interjúkkal is töltöm rengeteg szabadidőmet. Alapvetően megerősítve látom azon véleményemet, hogy az informatika vallásközeli tudomány, és sokan transzcendens vonásokat találnak a szerverek működésében, és babonás eszközökkel közelítik meg őket. Az egyik ilyen misztikus terület a tranzakciós log szerepe és használata.
Mindenekelőtt szeretném leszögezni a következőt: a tranzakció teljesülését a diszkre fogja kiírni rögtön az SQL szerver, nem a memóriában írja a tranzakciós logot, mert annak ugye nem volna sok értelme, ha a commit tényét egy illékony tárban rögzítené. De ez részletkérdés pillanatnyi motivációm szempontjából, nézzük inkább meg, hogy mikor szabadítja fel az SQL a tranzakciós logot. Három feltételnek kell tejesülnie:

  1. Az adatbázis recovery modellje simple vagy (full és bulk-logged esetén) az adott naplórész le lett backupolva.
  2. A tranzakciós log szóban forgó része nem tartalmaz aktív tranzakciót, azaz minden olyan tranzakció, ami a log írásakor aktív volt, már véget ért (ha rollback lett ,akkor a rollback is véget ért).
  3. Az adatbázis nincs replikálva vagy ha igen, akkor a Log Reader Agent már elolvasta az adott részt

Ezeknek a feltételeknek egyszerre teljesülniük kell. Azaz ha leállítom a Log Reader Agentet, akkor elkezd nőni a tranlog az égig, hogy ne rontsa el a replikációt. Hasonlóan elkezd nőni a log, ha sikerült egy tranzakicónak nyitva maradnia az adatbázisban, és lassan az egész tranlog aktívvá válik miatta.
Tehát ha megnő a log, érdemes a fenti pontokat ellenőrizni, körülbelül ebben a sorrendben (recovery model/utolsó log backup; legrégebbi nyitott tranzakció; replikáció esetén LRA).
Ja, még egy utolsó: a TRUNCATE LOG parancsok margójára: ha valaki rendszeresen kiadja a truncate log parancsot, akkor gondolkozzon el azon, hogy miért van full recovery model beállítva, és gyorsan állítsa át simple-re. Köszi.

Többszintű replikáció költöztetése

Pár hónapja találkoztam egy érdekes problémával: adott egy többszintű MSSQL tranzakcionális replikáció, azaz a subscriber továbbpublisholja az adatokat valahova, ahonnan ezt még tovább publikálják. Hogy mi ebben a ráció, azt nem akarom firtatni, a lényeg, hogy azért van benne, például nem látják egymást a gépek, és az eleje és a vége között van párezer mérföld távolság. Ebben a helyzetben a második publishert kellett kicserélni, mivel upgrade-elték az SQL-t 2005-re. A replikált adatmennyiség elég nagy volt, WAN-on napokig tartott egy újrainicializálás, úgyhogy valami frappáns megoldás után néztem… A végeredmény sokak szerint merész lett, de senki nem tudott belekötni, úgyhogy ezt javasoltam végső megoldásnak.
Az “adatforrás” szervert nevezzük A-nak, az ő subscriberét B-nek, aki publikálja az A-tól kapott adatokat C-nek, és ne is menjünk tovább, ennyi elég a történet szempontjából, hiszen B-t cseréljük ki BB-re. A legegyszerűbb felállásban új publikációkat kellett volna létrehozni BB-n, amire C-nek fel kell iratkozni, és a kezdeti incializálás duplán fáj: a hosszú snapshot applikáció mellett a már a C-n lévő adatokat onnan előbb el kell tüntetni: vagy delete, ami többgigás tábláknál elég csúnya hatással van az adatbázis használhatóságára, ráadásul továbbreplikálódik D-re, ahol ugyanezt a hatást váltja ki; vagy drop-create table, ami gyorsabb, de ehhez a C-n lévő publikációt el kell dobni és újra létrehozni majd, ami egy vertikálisan és horizontálisan is filterezett gigásznál szintén elég kalandos tud lenni. Continue reading ‘Többszintű replikáció költöztetése’ »