NewSQL = NoSQL + ACID

NewSQL = NoSQL + ACID
Tills nyligen fanns det i Odnoklassniki cirka 50 TB data som bearbetades i realtid och lagrades i SQL Server. För en sÄdan mÀngd Àr det praktiskt taget omöjligt att sÀkerstÀlla snabb, pÄlitlig och Àven felresistent Ätkomst till datacentret med hjÀlp av SQL-databaser. Vanligtvis anvÀnds en av NoSQL-lagringslösningarna i sÄdana fall, men allt kan inte överföras till NoSQL: vissa enheter krÀver ACID-transaktionsgarantier.

Detta ledde oss till att anvÀnda ett NewSQL-lagringssystem, det vill sÀga en databas som erbjuder felresistens, skalbarhet och snabbhet av NoSQL-system, men samtidigt behÄller de bekanta ACID-garantierna frÄn klassiska system. Det finns fÄ fungerande industrisystem av denna nya typ, sÄ vi utvecklade ett sÄdant system sjÀlva och tog det i kommersiell drift.

Hur det fungerar och vad vi Ă„stadkom — lĂ€s vidare nedan.

Idag har "Odnoklassniki" över 70 miljoner unika besökare varje mĂ„nad. Vi hör till de fem största sociala nĂ€tverken i vĂ€rlden och Ă€r bland de tjugo webbplatser dĂ€r anvĂ€ndare tillbringar mest tid. Infrastruktur för "OK" hanterar mycket höga belastningar: fler Ă€n en miljon HTTP-förfrĂ„gningar per sekund mot fronten. Delar av serverparken, som bestĂ„r av mer Ă€n 8000 servrar, Ă€r placerade nĂ€ra varandra — i fyra datacenter i Moskva, vilket möjliggör en nĂ€tverksfördröjning pĂ„ mindre Ă€n 1 ms mellan dem.

Vi har anvÀnt Cassandra sedan 2010, frÄn version 0.6. Idag anvÀnds flera tiotals kluster. Det snabbaste klustret hanterar mer Àn 4 miljoner operationer per sekund, och det största lagrar 260 TB.

Men allt detta Ă€r vanliga NoSQL-kluster som anvĂ€nds för lagring av svagt överensstĂ€mmande data. Vi ville dock ersĂ€tta det primĂ€ra konsistenta lagringssystemet, Microsoft SQL Server, som har anvĂ€nts sedan "Odnoklassniki" grundades. Lagringssystemet bestod av mer Ă€n 300 maskiner med SQL Server Standard Edition, som innehöll 50 TB data — affĂ€rsobjekt. Dessa data modifieras inom ramen för ACID-transaktioner och krĂ€ver hög överensstĂ€mmelse.

För att fördela data över noderna i SQL Server anvĂ€nde vi bĂ„de vertikal och horisontell partionering (sharding). Historiskt har vi anvĂ€nt ett enkelt datashardningsschema: varje entitet tilldelades en token – en funktion av entitetens ID. Entiteter med samma token placerades pĂ„ en SQL-server. Relationstypen master-detail realiserades sĂ„ att token för huvudentiteten och den hĂ€rledda posten alltid matchade och fanns pĂ„ samma server. I sociala nĂ€tverk genereras nĂ€stan alla inlĂ€gg av anvĂ€ndaren – vilket innebĂ€r att all anvĂ€ndardata inom ett funktionellt delsystem lagras pĂ„ en och samma server. Det betyder att i affĂ€rstransaktioner deltog nĂ€stan alltid tabeller frĂ„n en och samma SQL-server, vilket gjorde det möjligt att sĂ€kerstĂ€lla datakonsistens genom lokala ACID-transaktioner utan behov av att anvĂ€nda lĂ„ngsamma och opĂ„litliga distribuerade ACID-transaktioner.

Genom sharding och för att öka hastigheten pÄ SQL:

  • AnvĂ€nder inte Foreign key constraints, eftersom ID för entiteten vid sharding kan finnas pĂ„ en annan server.
  • AnvĂ€nder inte lagrade procedurer och triggers pĂ„ grund av ytterligare belastning pĂ„ DBMS CPU.
  • AnvĂ€nder inte JOINs pĂ„ grund av allt ovanstĂ„ende och mĂ„nga slumpmĂ€ssiga lĂ€sningar frĂ„n disk.
  • Utanför transaktionen anvĂ€nder vi isoleringsnivĂ„n Read Uncommitted för att minska deadlocks.
  • Genomför endast korta transaktioner (i genomsnitt kortare Ă€n 100 ms).
  • AnvĂ€nder inte flera rader UPDATE och DELETE pĂ„ grund av det stora antalet deadlocks – vi uppdaterar endast en post i taget.
  • FrĂ„gor utförs alltid endast efter index – en frĂ„ga med en fullstĂ€ndig tabellgenomsökningsplan innebĂ€r överbelastning av databasen och dess avbrott.

Dessa steg har gjort det möjligt att maximera prestandan pÄ SQL-servrarna. Men problemen blev allt fler. LÄt oss titta pÄ dem.

Problem med SQL

  • Eftersom vi anvĂ€nde egenutvecklad sharding utfördes tillĂ€gget av nya shards manuellt av administratörerna. Under hela denna tid skulle de skalbara datakopiorna inte hantera förfrĂ„gningar.
  • NĂ€r antalet poster i tabellen ökade minskade hastigheten för insĂ€ttning och modifiering, och nĂ€r index lades till i den befintliga tabellen minskade hastigheten mĂ„ngfalt; skapande och Ă„terskapande av index skedde med driftstopp.
  • NĂ€rvaron av en begrĂ€nsad mĂ€ngd Windows för SQL Server i produktion komplicerar hanteringen av infrastrukturen.

Men det största problemet Ă€r —

Fel-tolerans

Den klassiska SQL-servern har dÄlig redundans. Anta att du har en enda databasserver och den gÄr sönder var tredje Är. Under den tiden fungerar inte webbplatsen i 20 minuter, vilket Àr acceptabelt. Om du har 64 servrar, fungerar inte webbplatsen en gÄng var tredje vecka. Och om du har 200 servrar, fungerar inte webbplatsen varje vecka. Det Àr ett problem.

Vad kan göras för att öka redundansen hos SQL-servern? Wikipedia föreslÄr att vi bygger ett hög tillgÀngligt kluster: dÀr det finns en duplicering i hÀndelse av ett fel pÄ nÄgon av komponenterna.

Detta krÀver en park av dyr utrustning: omfattande duplicering, fiberoptik, lagring av gemensam data, och dessutom fungerar aktivering av reservsystem ofta osÀkert: cirka 10% av aktiveringar slutar med att reservnoden som kopplas samman med huvudnoden gÄr ner.

Men den största nackdelen med ett sÄdant hög tillgÀngligt kluster Àr att det Àr helt otillgÀngligt vid en datacenterstörning dÀr det Àr placerat. "VKontakte" har fyra datacenter, och vi mÄste sÀkerstÀlla drift Àven vid en fullstÀndig katastrof i nÄgot av dem.

För detta skulle vi kunna anvÀnda Multi-Master replikation, inbyggd i SQL Server. Denna lösning Àr mycket dyrare pÄ grund av mjukvarukostnaden och lider av vÀlkÀnda problem med replikation - oförutsÀgbara transaktionsfördröjningar vid synkron replikation och förseningar vid tillÀmpning av replikationer (och som en konsekvens, förlorade modifieringar) vid asynkron. Dessutom manuell lösning av konflikter gör detta alternativ helt oanvÀndbart för oss.

Alla dessa problem krÀvde en grundlÀggande lösning och vi började en detaljerad analys av dem. HÀr behöver vi bekanta oss med vad SQL Server i huvudsak gör - transaktioner.

En enkel transaktion

LÄt oss titta pÄ den enklaste transaktionen, sett frÄn en tillÀmpad SQL-programmerares perspektiv: att lÀgga till ett foto i ett album. Album och foton lagras i olika tabeller. Ett album har en rÀknare för offentliga foton. DÄ delas en sÄdan transaktion upp i följande steg:

  1. LÄs albumet genom nyckel.
  2. Skapa en post i fototabellen.
  3. Om fotot har offentlig status, ökar vi rÀknaren för offentliga foton i albumet, uppdaterar posten och Ätar transaktionen.

Eller i form av pseudokod:

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

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

TX.commit();

Vi ser att det vanligaste scenariot för affÀrstransaktioner Àr att lÀsa data frÄn databasen till serverns minne, göra nÄgon förÀndring och spara de nya vÀrdena tillbaka i databasen. Vanligtvis uppdaterar vi flera entiteter och flera tabeller i sÄdan transaktion.

Vid utförandet av en transaktion kan konkurrerande modifieringar av samma data frĂ„n ett annat system intrĂ€ffa. Till exempel kan Antispam besluta att en anvĂ€ndare Ă€r misstĂ€nkt och att dĂ€rför alla bilder som anvĂ€ndaren har inte lĂ€ngre ska vara offentliga, de mĂ„ste skickas för granskning, vilket innebĂ€r att photo.status mĂ„ste Ă€ndras till ett annat vĂ€rde och motsvarande rĂ€kning mĂ„ste justeras. Det Ă€r uppenbart att om denna operation sker utan garantier för atomĂ€r tillĂ€mpning och isolering av konkurrerande modifieringar, som i ACID, sĂ„ kommer resultatet inte att vara som nödvĂ€ndigt — antingen kommer bildrĂ€knaren att visa ett felaktigt vĂ€rde eller sĂ„ kommer inte alla bilder att skickas för granskning.

LikasÄ finns det mycket kod som manipulera med olika affÀrsentiteter inom en och samma transaktion, skrivet under hela Odnoklassniki's existens. FrÄn vÄra erfarenheter av migrationer till NoSQL med Eventual Consistency , vet vi att de största svÄrigheterna (och tidskostnaderna) orsakas av behovet av att utveckla kod för att upprÀtthÄlla dataöverensstÀmmelse. DÀrför ansÄg vi att det viktigaste kravet pÄ det nya lagringssystemet var att sÀkerstÀlla verkliga ACID-transaktioner för affÀrslogik.

Andra, minst lika viktiga, krav var:

  • Vid en datacenterfel mĂ„ste bĂ„de lĂ€sning och skrivning till det nya lagringssystemet vara möjligt.
  • Att bevara den nuvarande utvecklingshastigheten. Det vill sĂ€ga, nĂ€r man arbetar med det nya lagringssystemet bör mĂ€ngden kod vara ungefĂ€r densamma, det ska inte behövas skriva nĂ„got ytterligare i lagringssystemet, eller utveckla algoritmer för konfliktlösning, upprĂ€tthĂ„llande av sekundĂ€ra index etc.
  • Hastigheten pĂ„ det nya lagringssystemet mĂ„ste vara tillrĂ€ckligt hög, bĂ„de vid datalĂ€sning och vid transaktionsbearbetning, vilket effektivt innebar att akademiskt strikta, universella men lĂ„ngsamma lösningar, som till exempel, tvĂ„fasĂ„taganden.
  • Automatisk skalering i realtid.
  • AnvĂ€ndning av vanliga billiga servrar, utan behov av att köpa exotisk hĂ„rdvara.
  • Möjlighet att utveckla lagring med hjĂ€lp av företagets utvecklare. Med andra ord prioriterades egna eller öppna lösningar, helst pĂ„ Java.

Lösningar, lösningar

Efter att ha analyserat möjliga lösningar kom vi fram till tvÄ möjliga val av arkitektur:

Det första — att ta vilken SQL-server som helst och implementera nödvĂ€ndig redundans, skalningsmekanism, redundanta kluster, konflikthantering och distribuerade, pĂ„litliga och snabba ACID-transaktioner. Vi bedömde detta alternativ som ganska komplext och arbetskrĂ€vande.

Det andra alternativet — att ta en fĂ€rdig NoSQL-lagring med implementerad skalning, redundanta kluster, konflikthantering och sjĂ€lva implementera transaktioner och SQL. Vid första anblicken verkar Ă€ven uppgiften att implementera SQL, för att inte tala om ACID-transaktioner, vara en uppgift för Ă„ratal. Men sedan insĂ„g vi att uppsĂ€ttningen av SQL-möjligheter som vi anvĂ€nder i praktiken ligger lĂ„ngt ifrĂ„n ANSI SQL pĂ„ samma sĂ€tt som Cassandra CQL Ă€r lĂ„ngt ifrĂ„n ANSI SQL. NĂ€r vi tittade nĂ€rmare pĂ„ CQL insĂ„g vi att det Ă€r tillrĂ€ckligt nĂ€ra det vi behöver.

Cassandra och CQL

SÄ, vad Àr intressant med Cassandra, vilka möjligheter har den?

För det första kan man skapa tabeller med stöd för olika datatyper, man kan göra SELECT eller UPDATE baserat pÄ primÀrnyckeln.

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

För att sĂ€kerstĂ€lla datakonsistens mellan repliker, anvĂ€nder Cassandra ett kvorumansats. I det enklaste fallet innebĂ€r detta att nĂ€r man placerar tre repliker av samma rad pĂ„ olika noder i klustret, anses upptagningen vara framgĂ„ngsrik om majoriteten av noderna (dvs. tvĂ„ av tre) har bekrĂ€ftat att denna skrivoperation var lyckad. Data i raden anses vara konsistenta om majoriteten av noderna har tillfrĂ„gats och bekrĂ€ftat detta vid avlĂ€sning. SĂ„ledes garanteras fullstĂ€ndig och omedelbar datakonsistens vid avbrott av en nod nĂ€r det finns tre repliker. Denna metod har gjort det möjligt för oss att implementera en Ă€nnu mer pĂ„litlig strategi: alltid skicka förfrĂ„gningar till alla tre repliker och vĂ€nta pĂ„ svar frĂ„n de tvĂ„ snabbaste. Det försenade svaret frĂ„n den tredje repliken avvisas i sĂ„dana fall. Den försenade noden kan ha allvarliga problem – fördröjningar, skrĂ€psamling i JVM, direkt minnesĂ„tervinning i linux-kĂ€rnan, hĂ„rdvarufel, nĂ€tverksavbrott. Men detta pĂ„verkar inte klientoperationen eller datan alls.

Metoden dÀr vi riktar oss till tre noder men fÄr svar frÄn tvÄ kallas speklulation: begÀran om extra repliker skickas innan den "det faller av".

En annan fördel med Cassandra Ă€r Batchlog – en mekanism som garanterar antingen fullstĂ€ndig tillĂ€mpning eller fullstĂ€ndig avslag av paketet med de Ă€ndringar du gör. Detta gör att vi kan lösa A i ACID – atomĂ€ritet direkt.

Det nÀrmaste vi kommer transaktioner i Cassandra Àr sÄ kalladelightweight transactions. Men de Àr lÄngt ifrÄn "verkliga" ACID-transaktioner: i sjÀlva verket Àr detta möjligheten att göra CAS pÄ data för endast en post, med anvÀndning av konsensus via det tunga protokollet Paxos. DÀrför Àr hastigheten pÄ sÄdana transaktioner lÄg.

Vad vi saknade i Cassandra

SÄ, vi behövde implementera riktiga ACID-transaktioner i Cassandra. Med hjÀlp av vilka vi enkelt skulle kunna uppnÄ tvÄ andra bekvÀma funktioner frÄn klassiska DBMS: konsistenta snabba index, vilket skulle lÄta oss hÀmta data inte bara efter primÀrnyckel och en vanlig generator av monoton autoinkrementella ID.

C*One

SÄ föddes en ny databas C*One, bestÄende av tre typer servernoder:

  • Lagringsenheter – (nĂ€stan) standard Cassandra-servrar som ansvarar för att lagra data pĂ„ lokala diskar. Allteftersom belastningen och datavolymerna vĂ€xer kan deras antal enkelt skalas upp till tiotals och hundratals.
  • Transaktionskoordinatorer - sĂ€kerstĂ€ller utförandet av transaktioner.
  • Kunder - applikationsservrar som genomför affĂ€rsprocesser och initierar transaktioner. Det kan finnas tusentals sĂ„dana kunder.

NewSQL = NoSQL + ACID

Servrar av alla typer ingÄr i en gemensam kluster, anvÀnder det interna meddelandeprotokollet Cassandra för att kommunicera med varandra och gossip för att utbyta klusterinformation. Med hjÀlp av Heartbeat fÄr servrarna kÀnnedom om ömsesidiga fel, upprÀtthÄller en gemensam dataskema - tabeller, deras struktur och replikering; partitionschema, kluster topologi, osv.

Kunder

NewSQL = NoSQL + ACID

IstÀllet för standarddrivrutiner anvÀnds lÀget Fat Client. En sÄdan nod lagrar inte data men kan agera som koordinator för frÄgeutförande, det vill sÀga kunden sjÀlv utför funktionen att koordinera sina frÄgor: frÄgar lagringskopior och löser konflikter. Detta Àr inte bara mer pÄlitligt och snabbare Àn standarddrivrutinen som krÀver kommunikation med en fjÀrrkoordinator, utan tillÄter ocksÄ hantering av frÄgor. Utanför en öppen kundtransaktion dirigeras frÄgor till lagringssystemen. Om kunden har öppnat en transaktion dirigeras alla frÄgor inom transaktionen till transaktionskoordinatore.
NewSQL = NoSQL + ACID

Transaktionskoordinator C*One

Koordinatorn Àr det vi har implementerat för C*One frÄn grunden. Den ansvarar för hantering av transaktioner, lÄs och ordning för tillÀmpning av transaktioner.

För varje behandlad transaktion genererar koordinatorn en tidsstÀmpel: varje efterföljande Àr större Àn den föregÄende transaktionen. Eftersom Cassandra-systemets konfliktlösning Àr baserad pÄ tidsstÀmplar (av tvÄ konfliktande poster anses den med den senaste tidsstÀmpeln som aktuell), kommer konflikten alltid att lösas till förmÄn för den efterföljande transaktionen. PÄ sÄ sÀtt har vi implementerat Lamport-klockor - en kostnadseffektiv metod för konfliktlösning i ett distribuerat system.

LÄs

För att sÀkerstÀlla isolering beslutade vi att anvÀnda den enklaste metoden - pessimistiska lÄs baserade pÄ den primÀra nyckeln av posten. Med andra ord, i en transaktion mÄste posten först lÄsas, dÀrefter lÀsas, modifieras och sparas. Endast efter en framgÄngsrik committ kan posten lÄsas upp sÄ att konkurrerande transaktioner kan anvÀnda den.

Implementeringen av sÄdan lÄsning Àr enkel i en icke-distribuerad miljö. I ett distribuerat system finns det tvÄ huvudsakliga vÀgar: antingen implementera en distribuerad lÄsning pÄ klustret, eller fördela transaktioner sÄ att transaktioner som involverar en och samma post alltid hanteras av samma koordinator.

Eftersom vÄra data redan Àr distribuerade i grupper av lokala transaktioner i SQL, beslutades det att koppla koordinatorer till grupper av lokala transaktioner: en koordinator utför alla transaktioner med en token frÄn 0 till 9, den andra med en token frÄn 10 till 19, och sÄ vidare. Som ett resultat blir varje instans av koordinatorn mÀstare över gruppen av transaktioner.

DÄ kan lÄsningar implementeras som en enkel HashMap i minnet av koordinatorn.

Koordinatorers fel

Eftersom en koordinator exklusivt hanterar en grupp av transaktioner Àr det mycket viktigt att snabbt kunna faststÀlla om den har slutat fungera, sÄ att ett försök att verkstÀlla transaktionen görs inom timeout. För att detta ska vara snabbt och pÄlitligt tillÀmpade vi en fullt sammanlÀnkad kvorum heartbeat-protokoll:

I varje datacenter finns det minst tvÄ noder av koordinatorn. Regelbundet skickar varje koordinator ett heartbeat-meddelande till de andra koordinatorerna och informerar dem om sin funktion, samt om vilket heartbeat-meddelande frÄn vilka koordinatorer i klustret han fÄtt senast.

NewSQL = NoSQL + ACID

Genom att ta emot liknande information frÄn de andra i sina heartbeat-meddelanden avgör varje koordinator för sig sjÀlv vilka noder i klustret som fungerar, och vilka som inte gör det, utifrÄn kvorumprincipen: om nod X har fÄtt information om normal mottagning av meddelanden frÄn nod Y frÄn majoriteten av noderna i klustret, betyder det att Y fungerar. Och vice versa, sÄ snart majoriteten meddelar om att de inte mottar meddelanden frÄn nod Y, betyder det att Y har slutat fungera. Det Àr intressant att om kvorumet meddelar nod X att den inte lÀngre fÄr nÄgra meddelanden frÄn den, kommer sjÀlva nod X att betrakta sig som felaktig.

Heartbeat-meddelanden skickas med hög frekvens, ungefÀr 20 gÄnger per sekund, med en period pÄ 50 ms. I Java Àr det svÄrt att garantera appens svar inom 50 ms pÄ grund av jÀmförbar lÀngd pÄ pauser orsakade av skrÀpsamlaren. Vi lyckades uppnÄ sÄdan svarstid med hjÀlp av G1-skrÀpsamlaren, som gör det möjligt att stÀlla in mÄl för lÀngden av GC-pausar. Men ibland, ganska sÀllan, överskrider skrÀpsamlarens pauser 50 ms, vilket kan leda till falska felregistreringar. För att undvika detta rapporterar koordinatorsystemet inte om en fjÀrrnods fel om den första heartbeat-meddelandet försvinner, utan bara om flera försvinner i rad. PÄ sÄ sÀtt lyckades vi upptÀcka fel för koordinatorsystemet inom 200 ms.

Men det Àr inte tillrÀckligt att snabbt förstÄ vilken nod som har slutat fungera. NÄgot mÄste göras Ät detta.

Reservkraft

Den klassiska schemat föreslÄr att vid en masters fel, starta valet av en ny med hjÀlp av en av de trendy universella algoritmerna. Men sÄdana algoritmer har vÀlkÀnda problem med konvergens över tid och lÀngden av sjÀlva valprocessen. Dessa ytterligare fördröjningar lyckades vi undvika genom att anvÀnda en schema för att ersÀtta koordinatorer i ett fullstÀndigt anslutet nÀtverk:

NewSQL = NoSQL + ACID

Anta att vi vill utföra en transaktion i grupp 50. LÄt oss i förvÀg definiera ersÀttningsschemat, det vill sÀga vilka noder som kommer att utföra transaktioner för grupp 50 i hÀndelse av att huvudkoordinatorn misslyckas. VÄrt mÄl Àr att bevara systemets funktionalitet vid ett datacenters fel. Vi definierar att den första reservnoden kommer att vara en nod frÄn ett annat datacenter, och den andra reservnoden kommer att vara en frÄn det tredje. Detta schema vÀljs en gÄng och Àndras inte förrÀn klusterets topologi Àndras, det vill sÀga tills nya noder ansluts (vilket intrÀffar mycket sÀllan). Ordningen för att vÀlja en ny aktiv master vid det gamla felet kommer alltid att vara: den aktiva mastern blir den första reservnoden, och om den ocksÄ har slutat fungera - blir den andra reservnoden aktiv.

Ett sÄdant schema Àr mer pÄlitligt Àn en universell algoritm, eftersom det för att aktivera en ny master bara krÀver att man faststÀller faktumet av den gamla masters fel.

Men hur förstÄr kunderna vilken av mÀstarna som arbetar just nu? Det Àr omöjligt att skicka information till tusentals kunder pÄ 50 ms. Det kan hÀnda att kunden skickar en begÀran om att öppna en transaktion, utan att veta att denne mÀstare redan har slutat fungera, och begÀran kommer att hÀnga pÄ en timeout. För att förhindra detta skickar kunderna spekulativt en begÀran om att öppna en transaktion direkt till gruppens mÀstare och bÄda hans reserver, men bara den som Àr den aktiva mÀstaren just nu kommer att svara pÄ denna begÀran. All efterföljande kommunikation inom transaktionen kommer kunden endast att ha med den aktiva mÀstaren.

ReservmÀstarna som fÄr begÀrningar pÄ transaktioner som inte tillhör dem sjÀlva placerar dessa i kön för ej skapade transaktioner, dÀr de lagras under en viss tid. Om den aktiva mÀstaren dör, tar den nya mÀstaren hand om begÀrningarna om att öppna transaktioner frÄn sin kö och svarar kunden. Om kunden redan har lyckats öppna en transaktion med den gamla mÀstaren, ignoreras det andra svaret (och uppenbarligen kommer en sÄdan transaktion inte att slutföras och kommer att upprepas av kunden).

Hur en transaktion fungerar

Antag att kunden har skickat en begÀran till koordinatorn om att öppna en transaktion för en viss entitet med en viss primÀrnyckel. Koordinatorn lÄser denna entitet och placerar den i lÄstables i minnet. Om det behövs, lÀser koordinatorn in denna entitet frÄn lagringssystemet och sparar de erhÄllna data i transaktionens tillstÄnd i minnet hos koordinatorn.

NewSQL = NoSQL + ACID

NĂ€r kunden vill Ă€ndra data i transaktionen skickar hen en begĂ€ran om att modifiera entiteten till koordinatorn, som dĂ„ placerar de nya uppgifterna i transaktionens tillstĂ„ndstabell i minnet. HĂ€r slutförs skrivningen — ingen skrivning till lagringssystemet görs.

NewSQL = NoSQL + ACID

NÀr kunden begÀr sina Àndrade data inom en aktiv transaktion, agerar koordinatorn sÄ hÀr:

  • om ID redan finns i transaktionen, tas data frĂ„n minnet;
  • om ID inte finns i minnet, lĂ€ses de saknade uppgifterna in frĂ„n nodlagringssystemen, kombineras med de redan befintliga i minnet, och resultatet ges till kunden.

PÄ sÄ sÀtt kan kunden lÀsa sina egna Àndringar, medan andra kunder inte ser dessa förÀndringar, eftersom de endast lagras i koordinatorns minne och Ànnu inte finns i Cassandra-noderna.

NewSQL = NoSQL + ACID

NÀr en klient skickar en commit, sparas det tillstÄnd som fanns i tjÀnstens minne av koordinatören i den registrerade batchen, och i form av registrerad batch skickas det till Cassandra-lagringarna. Lagringarna gör allt som krÀvs för att denna batch ska tillÀmpas atomÀrt (fullstÀndigt) och returnerar svaret till koordinatören, som sedan frigör lÄsningarna och bekrÀftar transaktionens framgÄng för klienten.

NewSQL = NoSQL + ACID

För att Ängra behöver koordinatören bara frigöra minnet som upptas av transaktionens tillstÄnd.

Som ett resultat av ovanstÄende förbÀttringar har vi implementerat ACID-principerna:

  • AtomĂ€rt. Detta Ă€r en garanti för att ingen transaktion kommer att registreras i systemet delvis; antingen kommer alla underoperationer att utföras eller ingen av dem. Denna princip upprĂ€tthĂ„lls av den registrerade batchen i Cassandra.
  • Konsistens. Varje framgĂ„ngsrik transaktion registrerar per definition endast giltiga resultat. Om det efter att ha öppnat transaktionen och utfört en del operationer upptĂ€cks att resultatet Ă€r ogiltigt, utförs en Ă„ngring.
  • Isolering. Vid utförande av en transaktion fĂ„r parallella transaktioner inte pĂ„verka dess resultat. Konkurrerande transaktioner isoleras med hjĂ€lp av pessimistiska lĂ„s pĂ„ koordinatören. För lĂ€sningar utanför transaktionen följs isoleringsprincipen pĂ„ nivĂ„n Read Committed.
  • BestĂ€ndighet. Oavsett problem pĂ„ lĂ€gre nivĂ„er — strömavbrott, maskinvarufel — ska Ă€ndringar som gjorts av en framgĂ„ngsrikt avslutad transaktion förbli kvar efter att verksamheten Ă„terupptagit.

LĂ€sning via index

LÄt oss ta en enkel tabell:

CREATE TABLE photos (
id bigint primary key,
e Àgare bigint,
Ă€ndrad timestamp,

)

Den har ett ID (primĂ€rnyckel), Ă€gare och datum för Ă€ndring. Vi behöver göra en mycket enkel frĂ„ga — hĂ€mta data per Ă€gare med Ă€ndringsdatum "de senaste 24 timmarna".

SELECT *
WHERE owner=?
AND modified>?

För att en sÄdan frÄga ska fungera snabbt, mÄste en klassisk SQL-databas skapa ett index pÄ kolumnerna (owner, modified). Detta kan vi göra ganska enkelt, eftersom vi nu har garantier för ACID!

Index i C*One

Det finns en ursprunglig tabell med foton, dÀr postens ID Àr primÀrnyckeln.

NewSQL = NoSQL + ACID

För C*One skapar en ny tabell som Àr en kopia av den ursprungliga. Nyckeln överensstÀmmer med det indexuttryck som dessutom inkluderar den primÀra nyckeln för posten frÄn den ursprungliga tabellen:

NewSQL = NoSQL + ACID

Nu kan frÄgan om "Àgaren under de senaste 24 timmarna" skrivas om som en select frÄn en annan tabell:

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

Konsistensen mellan den ursprungliga tabellen photos och indexet i1 upprÀtthÄlls automatiskt av koordinatorn. UtifrÄn bara datamodellen, vid Àndringar, genererar koordinatorn och sparar förÀndringar inte bara för huvudtabellen utan Àven för kopiorna. Inga ytterligare ÄtgÀrder görs med indextabellen, loggar lÀses inte, och lÄs anvÀnds inte. Det betyder att tillÀgg av index i stort sett inte förbrukar resurser och i praktiken inte pÄverkar hastigheten pÄ tillÀmpningen av modifieringar.

Med hjÀlp av ACID har vi lyckats implementera index "som i SQL". De har konsistens, kan skalas, fungerar snabbt, kan vara sammansatta och integreras i CQL-frÄgesprÄket. Inga Àndringar behöver göras i applikationskoden för att stödja index. Allt Àr enkelt, som i SQL. Och kanske viktigast av allt, index pÄverkar inte hastigheten pÄ exekveringen av modifieringar i den ursprungliga transaktionstabellen.

Vad som blev resultatet

Vi utvecklade C*One för tre Är sedan och lanserade det i produktionsdrift.

Vad har vi fÄtt som resultat? LÄt oss bedöma detta baserat pÄ delsystemet för hantering och lagring av foton, en av de mest kritiska datatyperna i sociala nÀtverk. Det handlar inte om sjÀlva fotona, utan om all möjlig metadata. Just nu finns det cirka 20 miljarder sÄdana poster i "Odnoklassniki", systemet hanterar 80 000 lÀsförfrÄgningar per sekund, och upp till 8 000 ACID-transaktioner per sekund relaterade till datamodifiering.

NÀr vi anvÀnde SQL med replication factor = 1 (men i RAID 10), lagrades metadata för foton i en högtillgÀnglig kluster av 32 maskiner med Microsoft SQL Server (plus 11 reservmaskiner). Dessutom avsattes 10 servrar för lagring av sÀkerhetskopior. Totalt 50 kostsamma maskiner. Systemet fungerade under nominell belastning, utan marginal.

Efter migreringen till det nya systemet har vi fĂ„tt en replikationsfaktor = 3 – en kopia i varje datacenter. Systemet bestĂ„r av 63 noder i Cassandra-lagring och 6 koordinatormaskiner, totalt 69 servrar. Dessa maskiner Ă€r dock betydligt billigare, deras totala kostnad uppgĂ„r till cirka 30 % av kostnaden för systemet pĂ„ SQL. Samtidigt hĂ„lls belastningen pĂ„ en nivĂ„ av 30 %.

Med implementeringen av C*One har ocksĂ„ latensen minskat: i SQL tog en skrivoperation cirka 4,5 ms. I C*One – cirka 1,6 ms. Transaktionstiden Ă€r i genomsnitt mindre Ă€n 40 ms, commit utförs pĂ„ 2 ms, och lĂ€s- och skrivtiden ligger i genomsnitt pĂ„ 2 ms. 99:e percentilen ligger pĂ„ endast 3-3,1 ms, antalet timeouts har minskat med 100 gĂ„nger – allt tack vare det omfattande anvĂ€ndningen av spekulationer.

Vid denna tidpunkt har en stor del av SQL Server-noderna tagits ur drift, nya produkter utvecklas endast med hjÀlp av C*One. Vi har anpassat C*One för att fungera i vÄrt moln. one-cloud, vilket har gjort det möjligt att pÄskynda distributionsprocessen av nya kluster, förenkla konfigurationen och automatisera driften. Utan kÀllkoden skulle detta varit betydligt svÄrare och mer klumpigt.

Just nu arbetar vi med att migrera vĂ„ra andra lagringslösningar till molnet – men det Ă€r en helt annan historia.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster