NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Egészen a közelmúltig az Odnoklassniki körülbelül 50 TB valós időben feldolgozott adatot tárolt az SQL Serverben. Egy ilyen kötethez szinte lehetetlen gyors és megbízható, sőt adatközponti hibatűrő hozzáférést biztosítani egy SQL DBMS segítségével. Ilyen esetekben jellemzően az egyik NoSQL-tárolót használják, de nem lehet mindent átvinni a NoSQL-be: egyes entitások ACID tranzakciós garanciákat igényelnek.

Ez vezetett el minket a NewSQL tároló használatához, vagyis egy olyan DBMS-hez, amely biztosítja a NoSQL rendszerek hibatűrését, méretezhetőségét és teljesítményét, ugyanakkor megtartja a klasszikus rendszerekben megszokott ACID garanciákat. Kevés működő ipari rendszer van ebből az új osztályból, ezért mi magunk építettünk be egy ilyen rendszert és helyeztük üzembe.

Hogyan működik és mi történt - olvasható a vágás alatt.

Ma az Odnoklassniki havi közönsége több mint 70 millió egyedi látogató. Mi Benne vagyunk az első ötben a világ legnagyobb közösségi hálózata, és azon húsz webhely közé tartozik, amelyeken a felhasználók a legtöbb időt töltik. Az OK infrastruktúra nagyon nagy terhelést kezel: több mint egymillió HTTP-kérés/sec frontonként. A több mint 8000 darabból álló szerverflotta részei egymáshoz közel helyezkednek el – négy moszkvai adatközpontban, ami 1 ms-nál kisebb hálózati késleltetést tesz lehetővé köztük.

A Cassandrát 2010 óta használjuk, a 0.6-os verziótól kezdve. Ma több tucat klaszter működik. A leggyorsabb fürt több mint 4 millió műveletet dolgoz fel másodpercenként, a legnagyobb pedig 260 TB-ot tárol.

Ezek azonban mind hagyományos NoSQL-fürtök, amelyeket tárolásra használnak gyengén koordinált adat. Le akartuk cserélni a fő konzisztens tárolót, a Microsoft SQL Servert, amelyet az Odnoklassniki alapítása óta használtak. A tároló több mint 300 SQL Server Standard Edition gépből állt, amelyek 50 TB adatot tartalmaztak – üzleti entitásokat. Ezek az adatok az ACID-tranzakciók részeként módosulnak, és megkövetelik magas konzisztencia.

Az adatok SQL Server csomópontok közötti elosztásához függőleges és vízszintes egyaránt használtunk particionálás (szilánkos). Korábban egy egyszerű adatfelosztási sémát használtunk: minden entitáshoz volt társítva egy token – az entitásazonosító függvénye. Az azonos tokennel rendelkező entitások ugyanarra az SQL-kiszolgálóra kerültek. A fő-részlet kapcsolat úgy lett megvalósítva, hogy a fő- és a gyermekrekord tokenek mindig megegyezzenek, és ugyanazon a szerveren helyezkedjenek el. Egy közösségi hálózatban szinte minden rekord a felhasználó nevében jön létre – ami azt jelenti, hogy egy funkcionális alrendszeren belül minden felhasználói adat egy szerveren tárolódik. Azaz egy üzleti tranzakció szinte mindig egy SQL szerverről származó táblákat tartalmazott, ami lehetővé tette az adatok konzisztenciájának biztosítását a helyi ACID tranzakciók segítségével anélkül, lassú és megbízhatatlan elosztott ACID tranzakciók.

A felosztásnak és az SQL felgyorsításának köszönhetően:

  • Nem használunk idegen kulcs megszorításokat, mivel felosztáskor az entitásazonosító egy másik szerveren található.
  • A DBMS CPU további terhelése miatt nem használunk tárolt eljárásokat és triggereket.
  • Nem használunk JOIN-okat a fentiek miatt és a sok véletlenszerű olvasás miatt a lemezről.
  • Tranzakción kívül az El nem kötelezett olvasás elkülönítési szintet használjuk a holtpontok csökkentése érdekében.
  • Csak rövid (átlagosan 100 ms-nál rövidebb) tranzakciókat hajtunk végre.
  • Nem használunk többsoros UPDATE és DELETE funkciót a sok holtpont miatt – egyszerre csak egy rekordot frissítünk.
  • Mindig csak indexekre hajtunk végre lekérdezéseket – a teljes táblavizsgálati tervvel rendelkező lekérdezés számunkra az adatbázis túlterhelését és meghiúsulását jelenti.

Ezek a lépések lehetővé tették számunkra, hogy szinte maximális teljesítményt préseljünk ki az SQL-kiszolgálókból. A problémák azonban egyre szaporodtak. Nézzük meg őket.

Problémák az SQL-lel

  • Mivel saját kezűleg írt shardingot használtunk, az új szilánkok hozzáadását manuálisan végezték el a rendszergazdák. Ez idő alatt a méretezhető adatreplikák nem voltak kiszolgálási kérések.
  • A táblában lévő rekordok számának növekedésével csökken a beillesztés és a módosítás sebessége, ha egy meglévő táblához indexeket adunk, a sebesség egy faktorral csökken, az indexek létrehozása és újralétrehozása leállással történik.
  • A kevés Windows for SQL Server éles környezetben megnehezíti az infrastruktúra kezelését

De a fő probléma az

hibatűrés

A klasszikus SQL szerver gyenge hibatűréssel rendelkezik. Tegyük fel, hogy csak egy adatbázis-kiszolgálója van, és az háromévente egyszer meghibásodik. Ez idő alatt az oldal 20 percig nem működik, ami elfogadható. Ha 64 szervere van, akkor az oldal háromhetente egyszer leáll. És ha 200 szervere van, akkor az oldal nem működik minden héten. Ez probléma.

Mit lehet tenni az SQL szerver hibatűrésének javítása érdekében? A Wikipédia építkezésre hív bennünket magasan elérhető klaszter: ahol bármelyik komponens meghibásodása esetén van egy tartalék.

Ehhez drága berendezésparkra van szükség: számos duplikáció, optikai szál, megosztott tárhely, és a tartalék felvétele sem működik megbízhatóan: az átkapcsolások körülbelül 10%-a a tartalék csomópont meghibásodásával végződik, mint egy vonat a fő csomópont mögött.

De egy ilyen magas rendelkezésre állású fürt fő hátránya a nulla elérhetőség, ha az adatközpont, amelyben található, meghibásodik. Az Odnoklassniki négy adatközponttal rendelkezik, és az egyik teljes meghibásodása esetén biztosítanunk kell a működést.

Erre használhatnánk Multi-Master az SQL Serverbe beépített replikáció. Ez a megoldás a szoftverköltség miatt jóval drágább, és jól ismert replikációs problémákkal küzd – kiszámíthatatlan tranzakciós késések a szinkron replikációnál és késések a replikációk alkalmazásában (és ennek eredményeként elveszett módosítások) aszinkron replikáció esetén. A hallgatólagos kézi konfliktusmegoldás ezt a lehetőséget teljesen alkalmazhatatlanná teszi számunkra.

Mindezek a problémák radikális megoldást igényeltek, és elkezdtük részletes elemzésüket. Itt meg kell ismerkednünk azzal, amivel az SQL Server főként foglalkozik - a tranzakciókkal.

Egyszerű tranzakció

Tekintsük egy alkalmazott SQL programozó szemszögéből a legegyszerűbb tranzakciót: fénykép felvételét egy albumba. Az albumokat és fényképeket különböző lemezeken tárolják. Az albumnak van nyilvános fotószámlálója. Ezután egy ilyen tranzakció a következő lépésekre oszlik:

  1. Kulcsonként blokkoljuk az albumot.
  2. Hozzon létre egy bejegyzést a fényképtáblázatban.
  3. Ha a fénykép nyilvános állapotú, akkor adjon hozzá egy nyilvános fotószámlálót az albumhoz, frissítse a rekordot, és hajtsa végre a tranzakciót.

Vagy pszeudokódban:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

Azt látjuk, hogy az üzleti tranzakciók legáltalánosabb forgatókönyve az, hogy az adatbázisból adatokat olvasunk be az alkalmazásszerver memóriájába, valamit módosítunk, és az új értékeket visszamentjük az adatbázisba. Általában egy ilyen tranzakció során több entitást, több táblát frissítünk.

Tranzakció végrehajtása során előfordulhat, hogy ugyanazon adatok egy másik rendszerből egyidejűleg módosulnak. Például a Levélszemét-blokkoló úgy dönthet, hogy a felhasználó valamilyen módon gyanús, és ezért a felhasználó összes fotója ne legyen többé nyilvános, azokat moderálásra kell küldeni, ami azt jelenti, hogy a photo.status értéket módosítani kell, és kikapcsolni a megfelelő számlálókat. Nyilvánvalóan, ha ez a művelet az alkalmazás atomitása és a versengő módosítások elkülönítése nélkül történik, mint pl. SAV, akkor az eredmény nem az lesz, ami kell - vagy rossz értéket mutat a fotószámláló, vagy nem minden fotó kerül moderálásra.

Az Odnoklassniki teljes fennállása során sok hasonló kódot írtak, amelyek egy tranzakción belül különböző üzleti entitásokat manipulálnak. A NoSQL-re való migráció tapasztalatai alapján Végső következetesség Tudjuk, hogy a legnagyobb kihívást (és időbefektetést) a kód fejlesztése jelenti az adatok konzisztenciájának megőrzése érdekében. Ezért az új tárolóval szemben támasztott fő követelménynek a valós ACID-tranzakciók biztosítását tekintettük az alkalmazáslogikához.

További, nem kevésbé fontos követelmények a következők voltak:

  • Ha az adatközpont meghibásodik, az új tárolóra való olvasásnak és írásnak is elérhetőnek kell lennie.
  • A jelenlegi fejlesztési sebesség fenntartása. Vagyis egy új tárhellyel végzett munka során a kód mennyiségének megközelítőleg azonosnak kell lennie, nem kell semmit hozzáadni a tárolóhoz, nem kell algoritmusokat fejleszteni a konfliktusok feloldására, a másodlagos indexek karbantartására stb.
  • Az új tárhely sebességének meglehetősen nagynak kellett lennie mind az adatok olvasásakor, mind a tranzakciók feldolgozásakor, ami gyakorlatilag azt jelentette, hogy az akadémiailag szigorú, univerzális, de lassú megoldások, mint pl. kétfázisú commit.
  • Automatikus, menet közbeni méretezés.
  • Rendszeres olcsó szerverek használata, egzotikus hardver vásárlása nélkül.
  • Tárhelyfejlesztési lehetőség céges fejlesztők által. Más szóval, előnyben részesítették a szabadalmaztatott vagy nyílt forráskódú megoldásokat, lehetőleg Java nyelven.

Döntések

A lehetséges megoldásokat elemezve két lehetséges architektúraválasztáshoz jutottunk:

Az első az, hogy bármilyen SQL-kiszolgálót kell használni, és megvalósítani a szükséges hibatűrést, skálázási mechanizmust, feladatátvételi fürtöt, konfliktusfeloldást és elosztott, megbízható és gyors ACID-tranzakciókat. Ezt a lehetőséget nagyon nem triviálisnak és munkaigényesnek értékeltük.

A második lehetőség az, hogy egy kész NoSQL-tárolót veszünk, implementált skálázással, feladatátvételi fürttel, konfliktusfeloldással, valamint saját magunk implementáljuk a tranzakciókat és az SQL-t. Első pillantásra még az SQL megvalósítása is évekig tartó feladatnak tűnik, nem beszélve az ACID tranzakciókról. De aztán rájöttünk, hogy a gyakorlatban használt SQL szolgáltatáskészlet olyan messze van az ANSI SQL-től, mint Cassandra CQL messze az ANSI SQL-től. Még közelebbről megvizsgálva a CQL-t, rájöttünk, hogy nagyon közel van ahhoz, amire szükségünk volt.

Cassandra és CQL

Szóval, mi az érdekes Cassandrában, milyen képességekkel rendelkezik?

Először is, itt hozhat létre különféle adattípusokat támogató táblákat; az elsődleges kulcson megteheti a SELECT vagy UPDATE műveletet.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

A replika adatok konzisztenciájának biztosítása érdekében Cassandra használja határozatképességi megközelítés. A legegyszerűbb esetben ez azt jelenti, hogy amikor ugyanannak a sornak három replikáját helyezik el a fürt különböző csomópontjain, az írás akkor tekinthető sikeresnek, ha a csomópontok többsége (azaz háromból kettő) megerősítette az írási művelet sikerességét. . A soradatok akkor tekinthetők konzisztensnek, ha az olvasás során a csomópontok többségét lekérdezték és megerősítették. Így három replikával a teljes és azonnali adatkonzisztencia garantált, ha egy csomópont meghibásodik. Ez a megközelítés lehetővé tette számunkra egy még megbízhatóbb séma megvalósítását: mindig küldjön kéréseket mindhárom replikának, várva a két leggyorsabb válaszra. A harmadik replika kései válaszát ebben az esetben el kell vetni. A késve válaszoló csomópontnak komoly problémái lehetnek – fékek, szemétgyűjtés a JVM-ben, közvetlen memória-visszanyerés a Linux kernelben, hardverhiba, leválasztás a hálózatról. Ez azonban semmilyen módon nem befolyásolja az ügyfél műveleteit vagy adatait.

Azt a megközelítést hívjuk meg, amikor három csomóponttal kapcsolatba lépünk, és kettőtől választ kapunk spekuláció: még azelőtt elküldik a kérést extra replikákra, hogy az „leesne”.

A Cassandra másik előnye a Batchlog, egy olyan mechanizmus, amely biztosítja, hogy a végrehajtott módosítások egy részét vagy teljesen alkalmazzák, vagy egyáltalán nem alkalmazzák. Ez lehetővé teszi számunkra, hogy megoldjuk az A-t a ACID-ben - az atomitás a dobozból.

A Cassandra tranzakcióihoz legközelebb az ún.könnyű tranzakciók". De ezek távol állnak a „valódi” ACID-tranzakcióktól: valójában ez egy lehetőség CAS csak egy rekord adatain, konszenzussal a nehézsúlyú Paxos protokoll használatával. Ezért az ilyen tranzakciók sebessége alacsony.

Amit Cassandrában hiányoltunk

Tehát valódi ACID-tranzakciókat kellett végrehajtanunk Cassandrában. Ennek segítségével könnyedén megvalósíthatjuk a klasszikus DBMS két másik kényelmes funkcióját: a konzisztens gyors indexeket, amelyek lehetővé teszik, hogy ne csak az elsődleges kulcs alapján végezzünk adatkiválasztást, valamint a monoton automatikusan növekvő azonosítók rendszeres generátora.

Kúp

Így egy új DBMS született Kúp, amely három típusú szervercsomópontból áll:

  • Tárolás – (majdnem) szabványos Cassandra szerverek, amelyek az adatok helyi lemezeken való tárolásáért felelősek. Az adatok terhelésének és mennyiségének növekedésével ezek mennyisége könnyedén skálázható tízre és százra.
  • Tranzakciókoordinátorok – biztosítják a tranzakciók végrehajtását.
  • Az ügyfelek olyan alkalmazáskiszolgálók, amelyek üzleti műveleteket hajtanak végre és tranzakciókat kezdeményeznek. Több ezer ilyen ügyfél lehet.

NewSQL = NoSQL+ACID

Minden típusú szerver egy közös fürt része, a belső Cassandra üzenetprotokoll segítségével kommunikálnak egymással és pletyka a klaszterinformációk cseréjére. A Heartbeat segítségével a szerverek megismerik a kölcsönös hibákat, egyetlen adatsémát tartanak fenn – táblákat, azok szerkezetét és replikációját; particionálási séma, klaszter topológia stb.

Ügyfelek

NewSQL = NoSQL+ACID

A szabványos illesztőprogramok helyett a Fat Client módot használják. Egy ilyen csomópont nem tárol adatokat, hanem koordinátorként működhet a kérések végrehajtásában, vagyis maga az Ügyfél koordinátoraként működik a kérései között: lekérdezi a tárolóreplikákat és feloldja az ütközéseket. Ez nemcsak megbízhatóbb és gyorsabb, mint a szabványos illesztőprogram, amely kommunikációt igényel egy távoli koordinátorral, hanem lehetővé teszi a kérések továbbításának vezérlését is. Az ügyfélen megnyitott tranzakción kívül a kérelmek lerakatba kerülnek. Ha az ügyfél megnyitott egy tranzakciót, akkor a tranzakción belüli összes kérés elküldésre kerül a tranzakciókoordinátornak.
NewSQL = NoSQL+ACID

C*One tranzakciós koordinátor

A koordinátor olyasmi, amit a C*One-hoz a semmiből implementáltunk. Felelős a tranzakciók kezeléséért, a zárolásokért és a tranzakciók alkalmazási sorrendjéért.

A koordinátor minden egyes kiszolgált tranzakcióhoz időbélyeget generál: minden további tranzakció nagyobb, mint az előző tranzakció. Mivel Cassandra konfliktusfeloldó rendszere időbélyegeken alapul (két ütköző rekordból a legutolsó időbélyegű számít aktuálisnak), az ütközés mindig a következő tranzakció javára kerül feloldásra. Így megvalósítottuk Lamport óra - olcsó módja a konfliktusok megoldásának elosztott rendszerben.

Zárak

Az elszigeteltség biztosítása érdekében úgy döntöttünk, hogy a legegyszerűbb módszert alkalmazzuk - a rekord elsődleges kulcsán alapuló pesszimista zárakat. Más szóval, egy tranzakcióban egy rekordot először zárolni kell, csak ezután kell olvasni, módosítani és elmenteni. Csak a sikeres véglegesítés után lehet egy rekordot feloldani, hogy a versengő tranzakciók használhassák azt.

Az ilyen zárolás megvalósítása egyszerű nem elosztott környezetben. Egy elosztott rendszerben két fő lehetőség van: vagy megvalósítja az elosztott zárolást a fürtön, vagy elosztja a tranzakciókat úgy, hogy az ugyanazt a rekordot érintő tranzakciókat mindig ugyanaz a koordinátor szolgálja ki.

Mivel esetünkben az adatok már el vannak osztva a helyi tranzakciók csoportjai között az SQL-ben, úgy döntöttek, hogy helyi tranzakciós csoportokat rendelnek hozzá a koordinátorokhoz: az egyik koordinátor minden tranzakciót 0-tól 9-ig, a második pedig 10-től 19-ig terjedő tokenekkel hajt végre, stb. Ennek eredményeként a koordinátorpéldányok mindegyike a tranzakciócsoport mesterévé válik.

Ezután a zárakat egy banális HashMap formájában lehet megvalósítani a koordinátor memóriájában.

Koordinátor hibái

Mivel az egyik koordinátor kizárólag tranzakciók csoportját szolgálja ki, nagyon fontos, hogy gyorsan megállapítsuk a sikertelenség tényét, hogy a tranzakció második végrehajtási kísérlete időtúljon. Ahhoz, hogy ez gyors és megbízható legyen, egy teljesen csatlakoztatott kvórum hallásprotokollt használtunk:

Minden adatközpontban legalább két koordinátor csomópont található. Időnként minden koordinátor szívverési üzenetet küld a többi koordinátornak, és tájékoztatja őket a működéséről, valamint arról, hogy a klaszter melyik koordinátorától melyik szívverés üzenetet kapta utoljára.

NewSQL = NoSQL+ACID

Hasonló információkat kapva másoktól szívverési üzeneteik részeként, minden koordinátor maga dönti el, hogy melyik klasztercsomópont működik és melyik nem, a kvórum elve alapján: ha az X csomópont információt kapott a klaszter legtöbb csomópontjától a normál állapotról. üzenetek fogadása az Y csomóponttól, akkor az Y működik. És fordítva, amint a többség hiányzó üzeneteket jelent az Y csomópontból, akkor Y visszautasította. Érdekes, hogy ha a kvórum tájékoztatja az X csomópontot, hogy már nem kap üzeneteket tőle, akkor maga az X csomópont hibásnak tekinti magát.

A szívverésüzenetek magas frekvenciával, körülbelül 20-szor másodpercenként kerülnek elküldésre, 50 ms-os időtartammal. Java-ban nehéz garantálni az alkalmazás válaszát 50 ms-on belül, a szemétgyűjtő által okozott szünetek hasonló hosszúsága miatt. Ezt a válaszidőt a G1 szemétgyűjtővel tudtuk elérni, amely lehetővé teszi, hogy célt adjunk meg a GC szünetek időtartamára. Azonban néha, nagyon ritkán, a kollektor szünetei meghaladják az 50 ms-t, ami téves hibaészleléshez vezethet. Hogy ez ne forduljon elő, a koordinátor nem jelenti egy távoli csomópont meghibásodását, amikor az első szívverés üzenet eltűnik, csak akkor, ha több is eltűnt egymás után. Kisasszony.

De nem elég gyorsan megérteni, hogy melyik csomópont nem működik. Tennünk kell valamit ez ügyben.

Foglalás

A klasszikus séma azt jelenti, hogy a mester kudarca esetén új választást indítanak az egyik használatával divatos egyetemes algoritmusok. Az ilyen algoritmusoknak azonban jól ismert problémáik vannak az időbeli konvergenciával és magának a választási folyamat hosszával kapcsolatban. Az ilyen további késéseket sikerült elkerülnünk egy koordinátorcsere-sémával egy teljesen összekapcsolt hálózatban:

NewSQL = NoSQL+ACID

Tegyük fel, hogy az 50. csoportban szeretnénk végrehajtani egy tranzakciót. Határozzuk meg előre a helyettesítési sémát, vagyis azt, hogy a fő koordinátor meghibásodása esetén mely csomópontok hajtanak végre tranzakciókat az 50. csoportban. Célunk a rendszer működőképességének fenntartása adatközpont meghibásodása esetén. Határozzuk meg, hogy az első tartalék egy másik adatközpont csomópontja, a második tartalék pedig egy harmadik csomópont lesz. Ez a séma egyszer van kiválasztva, és addig nem változik, amíg a fürt topológiája meg nem változik, azaz amíg új csomópontok nem lépnek be (ami nagyon ritkán történik meg). Az új aktív mester kiválasztásának eljárása, ha a régi meghibásodik, mindig a következőképpen történik: az első tartalék lesz az aktív mester, és ha leállt, a második tartalék lesz az aktív mester.

Ez a séma megbízhatóbb, mint az univerzális algoritmus, mivel egy új mester aktiválásához elegendő meghatározni a régi meghibásodását.

De hogyan fogják az ügyfelek megérteni, melyik mester dolgozik most? Lehetetlen információt küldeni több ezer ügyfélnek 50 ms alatt. Előfordulhat olyan helyzet, amikor egy ügyfél kérelmet küld egy tranzakció megnyitására, még nem tudja, hogy ez a mester már nem működik, és a kérés időtúllépéssel jár. Ennek elkerülése érdekében az ügyfelek spekulatív módon egy tranzakció megnyitására irányuló kérést küldenek a csoportmesternek és mindkét tartalékának egyszerre, de erre csak az adott pillanatban aktív master válaszol. Az ügyfél minden további kommunikációt a tranzakción belül csak az aktív mesterrel folytat.

A biztonsági mentési mesterek a nem saját tranzakciókra vonatkozó beérkezett kéréseket a meg nem született tranzakciók sorába helyezik, ahol azokat egy ideig tárolják. Ha az aktív mester meghal, az új mester feldolgozza a tranzakciók megnyitására vonatkozó kéréseket a sorából, és válaszol az ügyfélnek. Ha az ügyfél már megnyitott egy tranzakciót a régi mesterrel, akkor a második választ figyelmen kívül hagyja (és nyilvánvalóan egy ilyen tranzakció nem fejeződik be, és az ügyfél megismétli).

Hogyan működik a tranzakció

Tegyük fel, hogy egy ügyfél kérést küldött a koordinátornak, hogy nyissa meg a tranzakciót egy ilyen és egy ilyen entitás számára ilyen és ilyen elsődleges kulccsal. A koordinátor zárolja ezt az entitást, és a zárolási táblázatba helyezi a memóriában. Ha szükséges, a koordinátor kiolvassa ezt az entitást a tárolóból, és a kapott adatokat tranzakciós állapotban tárolja a koordinátor memóriájában.

NewSQL = NoSQL+ACID

Amikor egy ügyfél egy tranzakcióban módosítani akarja az adatokat, kérelmet küld a koordinátornak az entitás módosítására, és a koordinátor elhelyezi az új adatokat a tranzakció állapottáblájában a memóriában. Ezzel befejeződik a felvétel – nem készül felvétel a tárolóra.

NewSQL = NoSQL+ACID

Amikor egy ügyfél egy aktív tranzakció részeként saját megváltozott adatait kéri, a koordinátor a következőképpen jár el:

  • ha az azonosító már benne van a tranzakcióban, akkor a rendszer a memóriából veszi az adatokat;
  • ha nincs azonosító a memóriában, akkor a hiányzó adatokat kiolvassa a tároló csomópontokból, kombinálva a már memóriában lévőkkel, és az eredményt a kliens megkapja.

Így a kliens el tudja olvasni a saját változásait, de a többi kliens nem látja ezeket a változtatásokat, mert csak a koordinátor memóriájában tárolódnak, még nincsenek a Cassandra csomópontokban.

NewSQL = NoSQL+ACID

Amikor az ügyfél elküldi a véglegesítést, a szolgáltatás memóriájában lévő állapotot a koordinátor naplózott kötegben menti, és naplózott kötegként küldi el a Cassandra tárolóba. Az üzletek mindent megtesznek annak érdekében, hogy ez a csomag atomosan (teljesen) kerüljön alkalmazásra, és választ küldenek a koordinátornak, aki feloldja a zárakat és visszaigazolja a tranzakció sikerességét az ügyfélnek.

NewSQL = NoSQL+ACID

A visszaállításhoz pedig a koordinátornak csak a tranzakció állapota által elfoglalt memóriát kell felszabadítania.

A fenti fejlesztések eredményeként bevezettük az ACID elveket:

  • Atomos állapot. Ez a garancia arra, hogy egyetlen tranzakció sem kerül rögzítésre részben a rendszerben, vagy annak minden részművelete elkészül, vagy egyik sem. Ezt az elvet ragaszkodjuk a Cassandra naplózott tételeihez.
  • Következetesség. Minden sikeres tranzakció értelemszerűen csak érvényes eredményeket rögzít. Ha egy tranzakció megnyitása és a műveletek egy részének végrehajtása után kiderül, hogy az eredmény érvénytelen, visszaállításra kerül sor.
  • Elkülönítés. Amikor egy tranzakciót végrehajtanak, az egyidejű tranzakciók nem befolyásolhatják annak eredményét. A versengő tranzakciókat a koordinátor pesszimista zárolásával izoláljuk. A tranzakción kívüli olvasások esetében az elkülönítési elvet a Read Committed szinten figyeljük meg.
  • stabilitás. Az alacsonyabb szintű problémáktól – rendszerleállás, hardverhiba – függetlenül a sikeresen befejezett tranzakciók által végrehajtott változtatásokat a műveletek folytatásakor meg kell őrizni.

Olvasás indexek szerint

Vegyünk egy egyszerű táblázatot:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

Van rajta azonosító (elsődleges kulcs), tulajdonosa és módosítási dátuma. Nagyon egyszerű kérést kell benyújtania - válassza ki a tulajdonos adatait az „utolsó nap” változási dátumával.

SELECT *
WHERE owner=?
AND modified>?

Egy ilyen lekérdezés gyors feldolgozása érdekében egy klasszikus SQL DBMS-ben oszloponkénti indexet kell felépíteni (tulajdonos, módosított). Ezt nagyon egyszerűen megtehetjük, hiszen most már SAV garanciáink vannak!

Indexek C*One-ban

Van egy forrástábla fényképekkel, amelyben a rekordazonosító az elsődleges kulcs.

NewSQL = NoSQL+ACID

Egy indexhez a C*One új táblát hoz létre, amely az eredeti másolata. A kulcs megegyezik az indexkifejezéssel, és tartalmazza a rekord elsődleges kulcsát is a forrástáblából:

NewSQL = NoSQL+ACID

Most a „Tulajdonos az utolsó napra” lekérdezés átírható egy másik táblázatból:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

A forrástábla fotók és az i1 indextábla adatainak konzisztenciáját a koordinátor automatikusan fenntartja. Pusztán az adatséma alapján, amikor változás érkezik, a koordinátor nem csak a főtáblában generálja és tárolja a változást, hanem másolatokban is. Nem hajtanak végre további műveleteket az indextáblán, a naplókat nem olvassa be, és nem használ zárolást. Vagyis az indexek hozzáadása szinte semmilyen erőforrást nem fogyaszt, és gyakorlatilag nincs hatással a módosítások alkalmazásának sebességére.

Az ACID segítségével SQL-szerű indexeket tudtunk megvalósítani. Konzisztensek, méretezhetők, gyorsak, összeállíthatók és beépülnek a CQL lekérdező nyelvbe. Az indexek támogatásához nincs szükség az alkalmazáskód módosítására. Minden olyan egyszerű, mint az SQL-ben. És ami a legfontosabb, az indexek nem befolyásolják az eredeti tranzakciós tábla módosításainak végrehajtási sebességét.

Mi történt

Három éve fejlesztettük ki a C*One-t, és indítottuk kereskedelmi forgalomba.

Mit kaptunk végül? Értékeljük ezt a fotófeldolgozó és -tárolási alrendszer példáján, amely a közösségi hálózatok egyik legfontosabb adattípusa. Nem magukról a fényképek testéről beszélünk, hanem mindenféle metainformációról. Jelenleg az Odnoklassniki körülbelül 20 milliárd ilyen rekordtal rendelkezik, a rendszer másodpercenként 80 ezer olvasási kérést dolgoz fel, az adatok módosításához kapcsolódó másodpercenként akár 8 ezer ACID-tranzakciót.

Amikor az SQL-t 1-es replikációs tényezővel használtuk (de a RAID 10-ben), a fénykép metainformációit egy 32, Microsoft SQL Servert futtató gépből álló, magas rendelkezésre állású fürtön tároltuk (plusz 11 biztonsági másolat). 10 szervert is kiosztottak a biztonsági mentések tárolására. Összesen 50 drága autó. Ugyanakkor a rendszer névleges terheléssel, tartalék nélkül működött.

Az új rendszerre való átállás után a replikációs faktor = 3 – minden adatközpontban egy példányt kaptunk. A rendszer 63 Cassandra tároló csomópontból és 6 koordinátor gépből áll, összesen 69 szerverrel. De ezek a gépek jóval olcsóbbak, összköltségük körülbelül 30%-a egy SQL rendszer költségének. Ugyanakkor a terhelést 30% -on tartják.

A C*One bevezetésével a késleltetés is csökkent: SQL-ben körülbelül 4,5 ms-ig tartott egy írási művelet. C*One esetén körülbelül 1,6 ms. A tranzakció időtartama átlagosan kevesebb, mint 40 ms, a véglegesítés 2 ms alatt fejeződik be, az olvasási és írási időtartam átlagosan 2 ms. 99. percentilis - mindössze 3-3,1 ms, az időtúllépések száma 100-szorosára csökkent - mindez a spekuláció széles körben elterjedt használatának köszönhető.

Mára az SQL Server csomópontjainak nagy részét leállították, új termékek fejlesztése csak a C*One segítségével történik. A C*One-t a felhőnkben való működésre adaptáltuk egyfelhő, amely lehetővé tette az új klaszterek telepítésének felgyorsítását, a konfiguráció egyszerűsítését és a működés automatizálását. A forráskód nélkül ez sokkal nehezebb és körülményesebb lenne.

Most azon dolgozunk, hogy a többi tárolóhelyünket a felhőbe helyezzük át – de ez egy teljesen más történet.

Forrás: will.com

Hozzászólás