NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Deri vonë, Odnoklassniki ruante rreth 50 TB të dhëna të përpunuara në kohë reale në SQL Server. Për një vëllim të tillë, është pothuajse e pamundur të sigurohet akses i shpejtë dhe i besueshëm, madje edhe tolerant ndaj dështimit të qendrës së të dhënave duke përdorur një SQL DBMS. Në mënyrë tipike, në raste të tilla, përdoret një nga depozitat NoSQL, por jo gjithçka mund të transferohet në NoSQL: disa subjekte kërkojnë garanci të transaksionit ACID.

Kjo na çoi në përdorimin e ruajtjes së NewSQL, domethënë një DBMS që siguron tolerancën e gabimeve, shkallëzueshmërinë dhe performancën e sistemeve NoSQL, por në të njëjtën kohë ruan garancitë ACID të njohura për sistemet klasike. Ka pak sisteme industriale që funksionojnë të kësaj klase të re, kështu që ne e kemi zbatuar vetë një sistem të tillë dhe e kemi vënë në funksion komercial.

Si funksionon dhe çfarë ndodhi - lexoni nën prerje.

Sot, audienca mujore e Odnoklassniki është më shumë se 70 milion vizitorë unikë. ne Jemi në pesëshen e parë rrjetet më të mëdha sociale në botë dhe ndër 8000 faqet në të cilat përdoruesit shpenzojnë më shumë kohë. Infrastruktura OK trajton ngarkesa shumë të larta: më shumë se një milion kërkesa HTTP/sek për front. Pjesë të një flote serverësh prej më shumë se 1 copë janë të vendosura afër njëra-tjetrës - në katër qendra të të dhënave në Moskë, gjë që lejon një vonesë rrjeti prej më pak se XNUMX ms midis tyre.

Ne kemi përdorur Cassandra që nga viti 2010, duke filluar me versionin 0.6. Sot ka disa dhjetëra grupime në funksion. Grupi më i shpejtë përpunon më shumë se 4 milionë operacione në sekondë dhe më i madhi ruan 260 TB.

Sidoqoftë, të gjitha këto janë grupe të zakonshme NoSQL që përdoren për ruajtje i koordinuar dobët të dhëna. Ne donim të zëvendësonim ruajtjen kryesore të qëndrueshme, Microsoft SQL Server, i cili është përdorur që nga themelimi i Odnoklassniki. Magazinimi përbëhej nga më shumë se 300 makina SQL Server Standard Edition, të cilat përmbanin 50 TB të dhëna - subjekte biznesi. Këto të dhëna modifikohen si pjesë e transaksioneve ACID dhe kërkojnë konsistencë të lartë.

Për të shpërndarë të dhënat nëpër nyjet e SQL Server, ne kemi përdorur si vertikale ashtu edhe horizontale ndarje (ndarje). Historikisht, ne përdorëm një skemë të thjeshtë të ndarjes së të dhënave: çdo entitet shoqërohej me një shenjë - një funksion i ID-së së entitetit. Subjektet me të njëjtën shenjë u vendosën në të njëjtin server SQL. Marrëdhënia master-detaje u zbatua në mënyrë që shenjat e regjistrimeve kryesore dhe të vogla të përputheshin gjithmonë dhe të ndodheshin në të njëjtin server. Në një rrjet social, pothuajse të gjitha regjistrimet gjenerohen në emër të përdoruesit, që do të thotë se të gjitha të dhënat e përdoruesit brenda një nënsistem funksional ruhen në një server. Kjo do të thotë, një transaksion biznesi përfshin pothuajse gjithmonë tabela nga një server SQL, i cili bëri të mundur sigurimin e konsistencës së të dhënave duke përdorur transaksionet lokale ACID, pa pasur nevojë të përdorni i ngadalshëm dhe jo i besueshëm transaksionet e shpërndara ACID.

Falë ndarjes dhe përshpejtimit të SQL:

  • Ne nuk përdorim kufizime të çelësave të huaj, pasi kur shpërndahet, ID e njësisë mund të jetë e vendosur në një server tjetër.
  • Ne nuk përdorim procedurat dhe aktivizuesit e ruajtur për shkak të ngarkesës shtesë në CPU DBMS.
  • Ne nuk përdorim JOIN për shkak të të gjitha sa më sipër dhe shumë leximeve të rastësishme nga disku.
  • Jashtë një transaksioni, ne përdorim nivelin e izolimit Read Uncommitted për të reduktuar bllokimet.
  • Ne kryejmë vetëm transaksione të shkurtra (mesatarisht më të shkurtra se 100 ms).
  • Ne nuk përdorim UPDATE dhe DELETE me shumë rreshta për shkak të numrit të madh të bllokimeve - ne përditësojmë vetëm një rekord në të njëjtën kohë.
  • Ne gjithmonë kryejmë pyetje vetëm në indekse - një pyetje me një plan skanimi të plotë të tabelës për ne do të thotë mbingarkim i bazës së të dhënave dhe shkaktim i dështimit të saj.

Këta hapa na lejuan të shtrydhnim performancën pothuajse maksimale nga serverët SQL. Megjithatë, problemet u bënë gjithnjë e më të shumta. Le t'i shikojmë ato.

Probleme me SQL

  • Meqenëse përdorëm ndarjen e shkruar vetë, shtimi i copëzave të reja u bë manualisht nga administratorët. Gjatë gjithë kësaj kohe, kopjet e shkallëzueshme të të dhënave nuk po shërbenin kërkesa.
  • Ndërsa numri i regjistrimeve në tabelë rritet, shpejtësia e futjes dhe modifikimit zvogëlohet; kur shtohen indekset në një tabelë ekzistuese, shpejtësia bie me një faktor; krijimi dhe rikrijimi i indekseve ndodh me kohën e ndërprerjes.
  • Pasja e një sasie të vogël Windows për SQL Server në prodhim e bën të vështirë menaxhimin e infrastrukturës

Por problemi kryesor është

toleranca ndaj gabimeve

Serveri klasik SQL ka tolerancë të dobët ndaj gabimeve. Le të themi se keni vetëm një server të bazës së të dhënave dhe ai dështon një herë në tre vjet. Gjatë kësaj kohe, faqja nuk funksionon për 20 minuta, gjë që është e pranueshme. Nëse keni 64 serverë, atëherë faqja nuk funksionon një herë në tre javë. Dhe nëse keni 200 serverë, atëherë faqja nuk funksionon çdo javë. Ky është problem.

Çfarë mund të bëhet për të përmirësuar tolerancën e gabimeve të një serveri SQL? Wikipedia na fton të ndërtojmë grup shumë i disponueshëm: ku në rast të dështimit të ndonjërit prej komponentëve ka një rezervë.

Kjo kërkon një flotë pajisjesh të shtrenjta: dublikime të shumta, fibër optike, ruajtje të përbashkët dhe përfshirja e një rezerve nuk funksionon me besueshmëri: rreth 10% e ndërrimeve përfundojnë me dështimin e nyjes rezervë si një tren pas nyjes kryesore.

Por disavantazhi kryesor i një grupi kaq shumë të disponueshëm është disponueshmëria zero nëse qendra e të dhënave në të cilën ndodhet dështon. Odnoklassniki ka katër qendra të dhënash dhe ne duhet të sigurojmë funksionimin në rast të një dështimi të plotë në njërën prej tyre.

Për këtë ne mund të përdornim Multi-Master replikimi i integruar në SQL Server. Kjo zgjidhje është shumë më e shtrenjtë për shkak të kostos së softuerit dhe vuan nga probleme të njohura me replikimin - vonesa të paparashikueshme të transaksioneve me riprodhim sinkron dhe vonesa në aplikimin e replikimeve (dhe, si rezultat, modifikimet e humbura) me replikimin asinkron. E nënkuptuara zgjidhje manuale e konfliktit e bën këtë opsion krejtësisht të pazbatueshëm për ne.

Të gjitha këto probleme kërkonin një zgjidhje radikale dhe ne filluam t'i analizojmë ato në detaje. Këtu duhet të njihemi me atë që bën kryesisht SQL Server - transaksionet.

Transaksion i thjeshtë

Le të shqyrtojmë transaksionin më të thjeshtë, nga këndvështrimi i një programuesi të aplikuar SQL: shtimi i një fotoje në një album. Albumet dhe fotografitë ruhen në pjata të ndryshme. Albumi ka një numërues fotografish publik. Pastaj një transaksion i tillë ndahet në hapat e mëposhtëm:

  1. Ne bllokojmë albumin me çelës.
  2. Krijoni një hyrje në tabelën e fotografive.
  3. Nëse fotografia ka një status publik, atëherë shtoni një numërues fotografish publik në album, përditësoni rekordin dhe kryeni transaksionin.

Ose në pseudokod:

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

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

TX.commit();

Shohim që skenari më i zakonshëm për një transaksion biznesi është leximi i të dhënave nga baza e të dhënave në kujtesën e serverit të aplikacionit, ndryshimi i diçkaje dhe ruajtja e vlerave të reja në bazën e të dhënave. Zakonisht në një transaksion të tillë përditësojmë disa entitete, disa tabela.

Gjatë ekzekutimit të një transaksioni, mund të ndodhë modifikimi i njëkohshëm i të njëjtave të dhëna nga një sistem tjetër. Për shembull, Antispam mund të vendosë që përdoruesi është disi i dyshimtë dhe për këtë arsye të gjitha fotot e përdoruesit nuk duhet të jenë më publike, ato duhet të dërgohen për moderim, që nënkupton ndryshimin e statusit të fotos në një vlerë tjetër dhe çaktivizimin e numëruesve përkatës. Natyrisht, nëse ky operacion ndodh pa garanci për atomicitetin e aplikimit dhe izolimin e modifikimeve konkurruese, si në ACID, atëherë rezultati nuk do të jetë ai që nevojitet - ose numëruesi i fotografive do të tregojë vlerën e gabuar, ose jo të gjitha fotot do të dërgohen për moderim.

Shumë kode të ngjashme, duke manipuluar subjekte të ndryshme biznesi brenda një transaksioni, janë shkruar gjatë gjithë ekzistencës së Odnoklassniki. Bazuar në përvojën e migrimeve në NoSQL nga Konsistenca eventuale Ne e dimë se sfida më e madhe (dhe investimi në kohë) vjen nga zhvillimi i kodit për të ruajtur konsistencën e të dhënave. Prandaj, ne e konsideruam kërkesën kryesore për ruajtjen e re të jetë ofrimi i transaksioneve reale të ACID për logjikën e aplikimit.

Kërkesa të tjera, jo më pak të rëndësishme, ishin:

  • Nëse qendra e të dhënave dështon, duhet të jenë të disponueshme si leximi ashtu edhe shkrimi në memorien e re.
  • Ruajtja e shpejtësisë aktuale të zhvillimit. Kjo do të thotë, kur punoni me një depo të re, sasia e kodit duhet të jetë afërsisht e njëjtë; nuk duhet të ketë nevojë të shtoni asgjë në depo, të zhvilloni algoritme për zgjidhjen e konflikteve, mbajtjen e indekseve dytësore, etj.
  • Shpejtësia e ruajtjes së re duhej të ishte mjaft e lartë, si gjatë leximit të të dhënave ashtu edhe gjatë përpunimit të transaksioneve, gjë që në mënyrë efektive nënkuptonte se zgjidhjet akademikisht rigoroze, universale, por të ngadalta, të tilla si, për shembull, nuk ishin të zbatueshme. kryerjet dyfazore.
  • Shkallëzimi automatik në fluturim.
  • Përdorimi i serverëve të rregullt të lirë, pa pasur nevojë të blini pajisje ekzotike.
  • Mundësia e zhvillimit të ruajtjes nga zhvilluesit e kompanisë. Me fjalë të tjera, përparësi iu dha zgjidhjeve të pronarit ose me burim të hapur, mundësisht në Java.

Vendime, vendime

Duke analizuar zgjidhjet e mundshme, arritëm në dy zgjedhje të mundshme të arkitekturës:

E para është të marrësh çdo server SQL dhe të zbatosh tolerancën e kërkuar të gabimeve, mekanizmin e shkallëzimit, grupin e dështimit, zgjidhjen e konflikteve dhe transaksionet e shpërndara, të besueshme dhe të shpejta ACID. Ne e vlerësuam këtë opsion si shumë jo të parëndësishëm dhe intensiv të punës.

Opsioni i dytë është të marrësh një memorie të gatshme NoSQL me shkallëzim të implementuar, një grup dështimi, zgjidhje konflikti dhe të zbatosh vetë transaksionet dhe SQL. Në pamje të parë, edhe detyra e zbatimit të SQL, për të mos përmendur transaksionet ACID, duket si një detyrë që do të marrë vite. Por më pas kuptuam se grupi i veçorive SQL që përdorim në praktikë është aq larg nga ANSI SQL sa Cassandra CQL larg ANSI SQL. Duke e parë edhe më nga afër CQL, kuptuam se ishte mjaft afër asaj që kishim nevojë.

Cassandra dhe CQL

Pra, çfarë është interesante për Cassandra, çfarë aftësish ka?

Së pari, këtu mund të krijoni tabela që mbështesin lloje të ndryshme të dhënash; mund të bëni SELECT ose UPDATE në çelësin primar.

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

Për të siguruar konsistencën e të dhënave të kopjeve, Cassandra përdor qasja e kuorumit. Në rastin më të thjeshtë, kjo do të thotë që kur tre kopje të të njëjtit rresht vendosen në nyje të ndryshme të grupit, shkrimi konsiderohet i suksesshëm nëse shumica e nyjeve (d.m.th., dy nga tre) konfirmojnë suksesin e këtij operacioni shkrimi. . Të dhënat e rreshtit konsiderohen të qëndrueshme nëse, gjatë leximit, shumica e nyjeve janë anketuar dhe konfirmuar ato. Kështu, me tre kopje, konsistenca e plotë dhe e menjëhershme e të dhënave garantohet nëse një nyje dështon. Kjo qasje na lejoi të zbatonim një skemë edhe më të besueshme: dërgoni gjithmonë kërkesa për të tre kopjet, duke pritur një përgjigje nga dy më të shpejtat. Përgjigja e vonë e kopjes së tretë është hedhur poshtë në këtë rast. Një nyje që është vonë në përgjigje mund të ketë probleme serioze - frenat, grumbullimi i mbeturinave në JVM, rikuperimi i drejtpërdrejtë i memories në kernelin Linux, dështimi i harduerit, shkëputja nga rrjeti. Megjithatë, kjo nuk ndikon në asnjë mënyrë operacionet ose të dhënat e klientit.

Qasja kur kontaktojmë tre nyje dhe marrim një përgjigje nga dy quhet spekulime: një kërkesë për kopje shtesë dërgohet edhe para se të "bie".

Një përfitim tjetër i Cassandra është Batchlog, një mekanizëm që siguron që një grup ndryshimesh që bëni ose të zbatohen plotësisht ose të mos zbatohen fare. Kjo na lejon të zgjidhim A në ACID - atomicitetin jashtë kutisë.

Gjëja më e afërt me transaksionet në Kasandra janë të ashtuquajturat "transaksione të lehta". Por ato janë larg transaksioneve "të vërteta" të ACID: në fakt, kjo është një mundësi për të bërë CAS-it mbi të dhënat nga vetëm një rekord, duke përdorur konsensusin duke përdorur protokollin e peshës së rëndë Paxos. Prandaj, shpejtësia e transaksioneve të tilla është e ulët.

Çfarë na mungonte në Kasandër

Pra, ne duhej të zbatonim transaksione reale ACID në Kasandra. Duke përdorur të cilat ne mund të zbatonim lehtësisht dy veçori të tjera të përshtatshme të DBMS klasike: indekse të qëndrueshme të shpejta, të cilat do të na lejonin të kryenim zgjedhje të të dhënave jo vetëm nga çelësi primar, dhe një gjenerues të rregullt të ID-ve monotonike që rriten automatikisht.

C*Një

Kështu lindi një DBMS e re C*Një, i përbërë nga tre lloje të nyjeve të serverit:

  • Ruajtja - (pothuajse) serverë standardë Cassandra përgjegjës për ruajtjen e të dhënave në disqe lokale. Ndërsa ngarkesa dhe vëllimi i të dhënave rritet, sasia e tyre mund të shkallëzohet lehtësisht në dhjetëra dhe qindra.
  • Koordinatorët e transaksioneve - sigurojnë ekzekutimin e transaksioneve.
  • Klientët janë serverë aplikacionesh që zbatojnë operacionet e biznesit dhe nisin transaksionet. Mund të ketë mijëra klientë të tillë.

NewSQL = NoSQL+ACID

Serverët e të gjitha llojeve janë pjesë e një grupi të përbashkët, përdorin protokollin e brendshëm të mesazheve Cassandra për të komunikuar me njëri-tjetrin dhe thashetheme për shkëmbimin e informacionit të grupit. Me Heartbeat, serverët mësojnë për dështimet e ndërsjella, mbajnë një skemë të vetme të dhënash - tabela, strukturën dhe përsëritjen e tyre; skema e ndarjes, topologjia e grupimeve etj.

klientët

NewSQL = NoSQL+ACID

Në vend të drejtuesve standardë, përdoret modaliteti Fat Client. Një nyje e tillë nuk ruan të dhëna, por mund të veprojë si koordinator për ekzekutimin e kërkesës, domethënë vetë Klienti vepron si koordinator i kërkesave të tij: ai kërkon kopje të ruajtjes dhe zgjidh konfliktet. Kjo nuk është vetëm më e besueshme dhe më e shpejtë se drejtuesi standard, i cili kërkon komunikim me një koordinator në distancë, por gjithashtu ju lejon të kontrolloni transmetimin e kërkesave. Jashtë një transaksioni të hapur për klientin, kërkesat dërgohen në depo. Nëse klienti ka hapur një transaksion, atëherë të gjitha kërkesat brenda transaksionit i dërgohen koordinatorit të transaksionit.
NewSQL = NoSQL+ACID

C*One Koordinator i Transaksionit

Koordinatori është diçka që ne e kemi zbatuar për C*One nga e para. Ai është përgjegjës për menaxhimin e transaksioneve, bllokimeve dhe rendit në të cilin zbatohen transaksionet.

Për çdo transaksion të kryer, koordinatori gjeneron një vulë kohore: çdo transaksion i mëpasshëm është më i madh se transaksioni i mëparshëm. Meqenëse sistemi i zgjidhjes së konfliktit të Cassandra bazohet në vulat kohore (të dy regjistrimeve konfliktuale, ai me vulën kohore më të fundit konsiderohet aktual), konflikti do të zgjidhet gjithmonë në favor të transaksionit pasues. Kështu e zbatuam Orë Lamport - një mënyrë e lirë për të zgjidhur konfliktet në një sistem të shpërndarë.

Brava

Për të siguruar izolimin, vendosëm të përdorim metodën më të thjeshtë - bravat pesimiste bazuar në çelësin kryesor të rekordit. Me fjalë të tjera, në një transaksion, një rekord fillimisht duhet të bllokohet, vetëm pastaj të lexohet, modifikohet dhe ruhet. Vetëm pas një kryerje të suksesshme mund të zhbllokohet një rekord në mënyrë që transaksionet konkurruese të mund ta përdorin atë.

Zbatimi i një bllokimi të tillë është i thjeshtë në një mjedis jo të shpërndarë. Në një sistem të shpërndarë, ekzistojnë dy opsione kryesore: ose zbatoni bllokimin e shpërndarë në grup, ose shpërndani transaksionet në mënyrë që transaksionet që përfshijnë të njëjtin rekord të shërbehen gjithmonë nga i njëjti koordinator.

Meqenëse në rastin tonë të dhënat tashmë janë shpërndarë midis grupeve të transaksioneve lokale në SQL, u vendos që të caktohen grupet e transaksioneve lokale te koordinatorët: një koordinator kryen të gjitha transaksionet me shenja nga 0 në 9, i dyti - me shenja nga 10 në 19, e kështu me radhë. Si rezultat, secili prej instancave të koordinatorit bëhet mjeshtër i grupit të transaksionit.

Pastaj bravat mund të zbatohen në formën e një HashMap banal në kujtesën e koordinatorit.

Dështimet e koordinatorit

Meqenëse një koordinator i shërben ekskluzivisht një grupi transaksionesh, është shumë e rëndësishme të përcaktohet shpejt fakti i dështimit të tij në mënyrë që përpjekja e dytë për të ekzekutuar transaksionin të përfundojë. Për ta bërë këtë të shpejtë dhe të besueshëm, ne përdorëm një protokoll të lidhur plotësisht të kuorumit të rrahjeve të dëgjimit:

Çdo qendër e të dhënave pret të paktën dy nyje koordinatore. Periodikisht, secili koordinator u dërgon një mesazh të rrahjeve të zemrës koordinatorëve të tjerë dhe i informon për funksionimin e tij, si dhe se cilat mesazhe të rrahjeve të zemrës ka marrë nga cilët koordinatorë në grup herën e fundit.

NewSQL = NoSQL+ACID

Duke marrë informacion të ngjashëm nga të tjerët si pjesë e mesazheve të tyre të rrahjeve të zemrës, secili koordinator vendos vetë se cilat nyje të grupimit funksionojnë dhe cilat jo, duke u udhëhequr nga parimi i kuorumit: nëse nyja X ka marrë informacion nga shumica e nyjeve në grup për normalen. marrja e mesazheve nga nyja Y, pastaj , Y funksionon. Dhe anasjelltas, sapo shumica raporton mungesën e mesazheve nga nyja Y, atëherë Y ka refuzuar. Është kurioze që nëse kuorumi informon nyjen X se nuk po merr më mesazhe prej saj, atëherë vetë nyja X do ta konsiderojë veten se ka dështuar.

Mesazhet e rrahjeve të zemrës dërgohen me frekuencë të lartë, rreth 20 herë në sekondë, me një periudhë prej 50 ms. Në Java, është e vështirë të garantosh përgjigjen e aplikacionit brenda 50 ms për shkak të gjatësisë së krahasueshme të pauzave të shkaktuara nga mbledhësi i mbeturinave. Ne ishim në gjendje ta arrinim këtë kohë përgjigjeje duke përdorur grumbulluesin e mbeturinave G1, i cili na lejon të specifikojmë një objektiv për kohëzgjatjen e pauzave të GC. Megjithatë, ndonjëherë, mjaft rrallë, pauzat e kolektorit kalojnë 50 ms, gjë që mund të çojë në një zbulim të rremë të defektit. Për të parandaluar që kjo të ndodhë, koordinatori nuk raporton një dështim të një nyje të largët kur mesazhi i parë i rrahjeve të zemrës nga ajo zhduket, vetëm nëse disa janë zhdukur me radhë. Kështu kemi arritur të zbulojmë një dështim të nyjes së koordinatorit në 200 Znj.

Por nuk është e mjaftueshme për të kuptuar shpejt se cila nyje ka pushuar së funksionuari. Duhet të bëjmë diçka për këtë.

Rezervimi

Skema klasike përfshin, në rast të një dështimi master, fillimin e një zgjedhjeje të re duke përdorur një nga në modë universale algoritme. Megjithatë, algoritme të tilla kanë probleme të njohura me konvergjencën kohore dhe kohëzgjatjen e vetë procesit zgjedhor. Ne ishim në gjendje të shmangnim vonesa të tilla shtesë duke përdorur një skemë zëvendësimi të koordinatorit në një rrjet plotësisht të lidhur:

NewSQL = NoSQL+ACID

Le të themi se duam të ekzekutojmë një transaksion në grupin 50. Le të përcaktojmë paraprakisht skemën e zëvendësimit, domethënë se cilat nyje do të ekzekutojnë transaksionet në grupin 50 në rast të dështimit të koordinatorit kryesor. Qëllimi ynë është të ruajmë funksionalitetin e sistemit në rast të dështimit të qendrës së të dhënave. Le të përcaktojmë që rezerva e parë do të jetë një nyje nga një qendër tjetër e të dhënave, dhe rezerva e dytë do të jetë një nyje nga një e tretë. Kjo skemë zgjidhet një herë dhe nuk ndryshon derisa topologjia e grupit të ndryshojë, domethënë derisa nyjet e reja të hyjnë në të (gjë që ndodh shumë rrallë). Procedura për zgjedhjen e një masteri të ri aktiv nëse i vjetri dështon do të jetë gjithmonë si vijon: rezerva e parë do të bëhet master aktiv dhe nëse ka ndërprerë funksionimin, rezerva e dytë do të bëhet master aktiv.

Kjo skemë është më e besueshme se algoritmi universal, pasi për të aktivizuar një master të ri mjafton të përcaktohet dështimi i atij të vjetër.

Por si do ta kuptojnë klientët se cili mjeshtër po punon tani? Është e pamundur të dërgosh informacion për mijëra klientë në 50 ms. Një situatë është e mundur kur një klient dërgon një kërkesë për të hapur një transaksion, duke mos ditur ende se ky master nuk funksionon më dhe kërkesa do të përfundojë. Për të parandaluar që kjo të ndodhë, klientët në mënyrë spekulative i dërgojnë një kërkesë për të hapur një transaksion masterit të grupit dhe të dy rezervave të tij menjëherë, por vetëm ai që është zotëruesi aktiv në këtë moment do t'i përgjigjet kësaj kërkese. Klienti do të bëjë të gjithë komunikimin e mëpasshëm brenda transaksionit vetëm me masterin aktiv.

Masters rezervë vendosin kërkesat e marra për transaksione që nuk janë të tyret në radhën e transaksioneve të palindura, ku ato ruhen për ca kohë. Nëse masteri aktiv vdes, masteri i ri përpunon kërkesat për të hapur transaksione nga radha e tij dhe i përgjigjet klientit. Nëse klienti ka hapur tashmë një transaksion me masterin e vjetër, atëherë përgjigja e dytë shpërfillet (dhe, padyshim, një transaksion i tillë nuk do të përfundojë dhe do të përsëritet nga klienti).

Si funksionon transaksioni

Le të themi se një klient i dërgoi një kërkesë koordinatorit për të hapur një transaksion për një entitet të tillë me një çelës primar. Koordinatori e kyç këtë entitet dhe e vendos atë në tabelën e kyçjes në memorie. Nëse është e nevojshme, koordinatori lexon këtë ent nga ruajtja dhe ruan të dhënat që rezultojnë në një gjendje transaksioni në kujtesën e koordinatorit.

NewSQL = NoSQL+ACID

Kur një klient dëshiron të ndryshojë të dhënat në një transaksion, ai i dërgon një kërkesë koordinatorit për të modifikuar entitetin dhe koordinatori vendos të dhënat e reja në tabelën e statusit të transaksionit në memorie. Kjo përfundon regjistrimin - nuk bëhet asnjë regjistrim në ruajtje.

NewSQL = NoSQL+ACID

Kur një klient kërkon të dhënat e tij të ndryshuara si pjesë e një transaksioni aktiv, koordinatori vepron si më poshtë:

  • nëse ID është tashmë në transaksion, atëherë të dhënat merren nga memoria;
  • nëse nuk ka ID në memorie, atëherë të dhënat që mungojnë lexohen nga nyjet e ruajtjes, të kombinuara me ato tashmë në memorie dhe rezultati i jepet klientit.

Kështu, klienti mund të lexojë ndryshimet e veta, por klientët e tjerë nuk i shohin këto ndryshime, sepse ato ruhen vetëm në kujtesën e koordinatorit; ato nuk janë ende në nyjet Cassandra.

NewSQL = NoSQL+ACID

Kur klienti dërgon commit, gjendja që ishte në kujtesën e shërbimit ruhet nga koordinatori në një grup të regjistruar dhe dërgohet si një grup i regjistruar në ruajtjen e Cassandra. Dyqanet bëjnë gjithçka që është e nevojshme për të siguruar që kjo paketë të zbatohet në mënyrë atomike (plotësisht) dhe i kthejnë një përgjigje koordinatorit, i cili i lëshon bravat dhe i konfirmon klientit suksesin e transaksionit.

NewSQL = NoSQL+ACID

Dhe për të rikthyer, koordinatori duhet vetëm të çlirojë memorien e zënë nga gjendja e transaksionit.

Si rezultat i përmirësimeve të mësipërme, ne zbatuam parimet ACID:

  • Atomiciteti. Kjo është një garanci që asnjë transaksion nuk do të regjistrohet pjesërisht në sistem; ose të gjitha nënoperacionet e tij do të përfundojnë, ose asnjë nuk do të përfundojë. Ne i përmbahemi këtij parimi përmes grupit të regjistruar në Cassandra.
  • Konsistenca. Çdo transaksion i suksesshëm, sipas përkufizimit, regjistron vetëm rezultate të vlefshme. Nëse, pas hapjes së një transaksioni dhe kryerjes së një pjese të operacioneve, zbulohet se rezultati është i pavlefshëm, kryhet një rikthim.
  • Izolim. Kur një transaksion ekzekutohet, transaksionet e njëkohshme nuk duhet të ndikojnë në rezultatin e tij. Transaksionet konkurruese izolohen duke përdorur brava pesimiste për koordinatorin. Për leximet jashtë një transaksioni, parimi i izolimit respektohet në nivelin Read Committed.
  • stabilitet. Pavarësisht nga problemet në nivele më të ulëta - ndërprerja e sistemit, dështimi i harduerit - ndryshimet e bëra nga një transaksion i përfunduar me sukses duhet të mbeten të ruajtura kur të rifillojnë operacionet.

Leximi sipas indekseve

Le të marrim një tabelë të thjeshtë:

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

Ai ka një ID (çelësin kryesor), pronarin dhe datën e modifikimit. Ju duhet të bëni një kërkesë shumë të thjeshtë - zgjidhni të dhënat për pronarin me datën e ndryshimit "për ditën e fundit".

SELECT *
WHERE owner=?
AND modified>?

Në mënyrë që një pyetje e tillë të përpunohet shpejt, në një DBMS klasik SQL ju duhet të ndërtoni një indeks sipas kolonave (pronari, modifikuar). Këtë mund ta bëjmë shumë lehtë, pasi tani kemi garanci ACID!

Indekset në C*One

Ekziston një tabelë burimi me fotografi në të cilën ID-ja e regjistrimit është çelësi kryesor.

NewSQL = NoSQL+ACID

Për një indeks, C*One krijon një tabelë të re që është një kopje e origjinalit. Çelësi është i njëjtë me shprehjen e indeksit dhe përfshin gjithashtu çelësin kryesor të regjistrimit nga tabela burimore:

NewSQL = NoSQL+ACID

Tani pyetja për "pronarin për ditën e fundit" mund të rishkruhet si një përzgjedhje nga një tabelë tjetër:

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

Konsistenca e të dhënave në fotot e tabelës burimore dhe tabelës së indeksit i1 ruhet automatikisht nga koordinatori. Bazuar vetëm në skemën e të dhënave, kur merret një ndryshim, koordinatori gjeneron dhe ruan një ndryshim jo vetëm në tabelën kryesore, por edhe në kopje. Nuk kryhen veprime shtesë në tabelën e indeksit, regjistrat nuk lexohen dhe nuk përdoren bravë. Kjo do të thotë, shtimi i indekseve nuk konsumon pothuajse asnjë burim dhe praktikisht nuk ka asnjë efekt në shpejtësinë e aplikimit të modifikimeve.

Duke përdorur ACID, ne ishim në gjendje të implementonim indekse të ngjashme me SQL. Ato janë të qëndrueshme, të shkallëzueshme, të shpejta, të kompozueshme dhe të integruara në gjuhën e pyetjeve CQL. Asnjë ndryshim në kodin e aplikacionit nuk kërkohet për të mbështetur indekset. Gjithçka është aq e thjeshtë sa në SQL. Dhe më e rëndësishmja, indekset nuk ndikojnë në shpejtësinë e ekzekutimit të modifikimeve në tabelën origjinale të transaksioneve.

Cfare ndodhi

Ne zhvilluam C*One tre vjet më parë dhe e hodhëm në funksionim komercial.

Çfarë morëm në fund? Le ta vlerësojmë këtë duke përdorur shembullin e nënsistemit të përpunimit dhe ruajtjes së fotografive, një nga llojet më të rëndësishme të të dhënave në një rrjet social. Nuk po flasim për vetë trupat e fotografive, por për të gjitha llojet e meta-informacioneve. Tani Odnoklassniki ka rreth 20 miliardë regjistrime të tilla, sistemi përpunon 80 mijë kërkesa leximi në sekondë, deri në 8 mijë transaksione ACID në sekondë të shoqëruara me modifikimin e të dhënave.

Kur përdorëm SQL me faktor replikimi = 1 (por në RAID 10), metainformacioni i fotografisë u ruajt në një grup shumë të disponueshëm prej 32 makinerish që ekzekutojnë Microsoft SQL Server (plus 11 kopje rezervë). 10 serverë u ndanë gjithashtu për ruajtjen e kopjeve rezervë. Gjithsej 50 makina të shtrenjta. Në të njëjtën kohë, sistemi funksiononte me ngarkesë nominale, pa rezervë.

Pas migrimit në sistemin e ri, ne morëm faktorin e riprodhimit = 3 - një kopje në secilën qendër të të dhënave. Sistemi përbëhet nga 63 nyje ruajtëse Cassandra dhe 6 makina koordinatore, për një total prej 69 serverësh. Por këto makina janë shumë më të lira, kostoja e tyre totale është rreth 30% e kostos së një sistemi SQL. Në të njëjtën kohë, ngarkesa mbahet në 30%.

Me futjen e C*One, vonesa gjithashtu u ul: në SQL, një operacion shkrimi zgjati rreth 4,5 ms. Në C*One - rreth 1,6 ms. Kohëzgjatja e transaksionit është mesatarisht më pak se 40 ms, angazhimi përfundon në 2 ms, kohëzgjatja e leximit dhe e shkrimit është mesatarisht 2 ms. Përqindja e 99-të - vetëm 3-3,1 ms, numri i ndërprerjeve është ulur me 100 herë - të gjitha për shkak të përdorimit të gjerë të spekulimeve.

Deri tani, shumica e nyjeve të SQL Server janë çmontuar; produkte të reja po zhvillohen vetëm duke përdorur C*One. Ne përshtatëm C*One për të punuar në renë tonë një-re, i cili bëri të mundur përshpejtimin e vendosjes së grupimeve të reja, thjeshtimin e konfigurimit dhe automatizimin e funksionimit. Pa kodin burimor, bërja e kësaj do të ishte shumë më e vështirë dhe e rëndë.

Tani po punojmë për transferimin e objekteve tona të tjera të ruajtjes në cloud - por kjo është një histori krejtësisht e ndryshme.

Burimi: www.habr.com

Shto një koment