NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Tills nyligen lagrade Odnoklassniki cirka 50 TB data som bearbetades i realtid i SQL Server. För en sådan volym är det nästan omöjligt att tillhandahålla snabb och tillförlitlig och till och med datacenterfeltolerant åtkomst med hjälp av en SQL DBMS. I sådana fall används vanligtvis en av NoSQL-lagringarna, men allt kan inte överföras till NoSQL: vissa enheter kräver ACID-transaktionsgarantier.

Detta ledde oss till användningen av NewSQL-lagring, det vill säga ett DBMS som ger feltolerans, skalbarhet och prestanda för NoSQL-system, men samtidigt bibehåller de ACID-garantier som är bekanta med klassiska system. Det finns få fungerande industrisystem av denna nya klass, så vi implementerade ett sådant system själva och satte det i kommersiell drift.

Hur det fungerar och vad som hände - läs under klippet.

Idag är Odnoklassnikis månatliga publik mer än 70 miljoner unika besökare. Vi Vi är bland de fem bästa största sociala nätverk i världen, och bland de tjugo sajter som användare spenderar mest tid på. OK-infrastrukturen hanterar mycket höga belastningar: mer än en miljon HTTP-förfrågningar/sekund per front. Delar av en serverflotta på mer än 8000 1 stycken är placerade nära varandra - i fyra Moskva-datacenter, vilket möjliggör en nätverkslatens på mindre än XNUMX ms mellan dem.

Vi har använt Cassandra sedan 2010, från och med version 0.6. Idag finns flera dussin kluster i drift. Det snabbaste klustret bearbetar mer än 4 miljoner operationer per sekund, och det största lagrar 260 TB.

Dessa är dock alla vanliga NoSQL-kluster som används för lagring svagt koordinerad data. Vi ville ersätta den huvudsakliga konsekventa lagringen, Microsoft SQL Server, som har använts sedan grundandet av Odnoklassniki. Lagringen bestod av mer än 300 SQL Server Standard Edition-maskiner, som innehöll 50 TB data - affärsenheter. Dessa data modifieras som en del av ACID-transaktioner och kräver hög konsistens.

För att distribuera data över SQL Server-noder använde vi både vertikala och horisontella partitionering (skärning). Historiskt sett använde vi ett enkelt datadelningsschema: varje enhet var associerad med en token - en funktion av enhets-ID:t. Entiteter med samma token placerades på samma SQL-server. Huvud-detalj-relationen implementerades så att tokens för huvud- och underordnade poster alltid matchade och var placerade på samma server. I ett socialt nätverk genereras nästan alla poster på uppdrag av användaren – vilket innebär att all användardata inom ett funktionellt delsystem lagras på en server. Det vill säga, en affärstransaktion involverade nästan alltid tabeller från en SQL-server, vilket gjorde det möjligt att säkerställa datakonsistens med hjälp av lokala ACID-transaktioner, utan att behöva använda långsam och opålitlig distribuerade ACID-transaktioner.

Tack vare sharding och för att påskynda SQL:

  • Vi använder inte begränsningar för främmande nyckel, eftersom entitets-ID:t kan finnas på en annan server vid sönderdelning.
  • Vi använder inte lagrade procedurer och triggers på grund av den extra belastningen på DBMS CPU.
  • Vi använder inte JOINs på grund av allt ovan och många slumpmässiga läsningar från disk.
  • Utanför en transaktion använder vi isoleringsnivån Read Uncommitted för att minska dödläget.
  • Vi utför endast korta transaktioner (i genomsnitt kortare än 100 ms).
  • Vi använder inte multi-rad UPDATE och DELETE på grund av det stora antalet dödlägen - vi uppdaterar endast en post åt gången.
  • Vi utför alltid frågor endast på index - en fråga med en fullständig tabellskanningsplan för oss innebär att databasen överbelastas och att den misslyckas.

Dessa steg tillät oss att pressa nästan maximal prestanda ur SQL-servrar. Problemen blev dock fler och fler. Låt oss titta på dem.

Problem med SQL

  • Eftersom vi använde egenskriven sharding, gjordes lägga till nya shards manuellt av administratörer. Hela denna tid var skalbara datarepliker inte serviceförfrågningar.
  • När antalet poster i tabellen växer minskar insättnings- och modifieringshastigheten; när du lägger till index i en befintlig tabell sjunker hastigheten med en faktor; skapande och återskapande av index sker med stilleståndstid.
  • Att ha en liten mängd Windows för SQL Server i produktion gör det svårt att hantera infrastrukturen

Men huvudproblemet är

feltolerans

Den klassiska SQL-servern har dålig feltolerans. Låt oss säga att du bara har en databasserver, och den misslyckas vart tredje år. Under denna tid är sidan nere i 20 minuter, vilket är acceptabelt. Om du har 64 servrar är sidan nere en gång var tredje vecka. Och om du har 200 servrar så fungerar inte sidan varje vecka. Det är problem.

Vad kan göras för att förbättra feltoleransen för en SQL-server? Wikipedia uppmanar oss att bygga mycket tillgängligt kluster: där i händelse av fel på någon av komponenterna finns det en backup.

Detta kräver en flotta av dyr utrustning: många dupliceringar, optisk fiber, delad lagring och införandet av en reserv fungerar inte tillförlitligt: ​​cirka 10 % av omkopplingarna slutar med att backupnoden misslyckas som ett tåg bakom huvudnoden.

Men den största nackdelen med ett så högt tillgängligt kluster är noll tillgänglighet om datacentret där det är beläget misslyckas. Odnoklassniki har fyra datacenter, och vi måste säkerställa driften i händelse av ett fullständigt fel i ett av dem.

För detta skulle vi kunna använda Multi-Master replikering inbyggd i SQL Server. Denna lösning är mycket dyrare på grund av kostnaden för programvara och lider av välkända problem med replikering - oförutsägbara transaktionsförseningar med synkron replikering och förseningar vid tillämpning av replikeringar (och, som ett resultat, förlorade modifieringar) med asynkron replikering. Det underförstådda manuell konfliktlösning gör detta alternativ helt otillämpligt för oss.

Alla dessa problem krävde en radikal lösning och vi började analysera dem i detalj. Här behöver vi sätta oss in i vad SQL Server främst gör – transaktioner.

Enkel transaktion

Låt oss överväga den enklaste transaktionen, ur en tillämpad SQL-programmerares synvinkel: att lägga till ett foto i ett album. Album och fotografier lagras i separata surfplattor. Albumet har en offentlig fotodisk. Sedan är en sådan transaktion uppdelad i följande steg:

  1. Vi blockerar albumet med nyckel.
  2. Skapa en post i fototabellen.
  3. Om fotot har en offentlig status lägger du till en offentlig fotoräknare i albumet, uppdaterar posten och genomför transaktionen.

Eller i 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 en affärstransaktion är att läsa data från databasen in i minnet på applikationsservern, ändra något och spara de nya värdena tillbaka till databasen. Vanligtvis i en sådan transaktion uppdaterar vi flera enheter, flera tabeller.

När en transaktion utförs kan samtidig modifiering av samma data från ett annat system ske. Till exempel kan Antispam besluta att användaren på något sätt är misstänksam och därför ska alla användarens foton inte längre vara offentliga, de måste skickas för moderering, vilket innebär att photo.status ändras till något annat värde och att motsvarande räknare stängs av. Uppenbarligen, om denna operation sker utan garantier för atomicitet för tillämpning och isolering av konkurrerande modifieringar, som i SYRA, då blir resultatet inte vad som behövs - antingen visar fotoräknaren fel värde eller så skickas inte alla bilder för moderering.

En hel del liknande kod, som manipulerar olika affärsenheter inom en transaktion, har skrivits under hela Odnoklassnikis existens. Baserat på erfarenheten av migrationer till NoSQL från Eventuell konsistens Vi vet att den största utmaningen (och tidsinvesteringen) kommer från att utveckla kod för att upprätthålla datakonsistens. Därför ansåg vi att huvudkravet för den nya lagringen var tillhandahållande av riktiga ACID-transaktioner för applikationslogik.

Andra, inte mindre viktiga, krav var:

  • Om datacentret misslyckas måste både läsning och skrivning till den nya lagringen vara tillgänglig.
  • Upprätthålla nuvarande utvecklingshastighet. Det vill säga, när man arbetar med ett nytt arkiv bör mängden kod vara ungefär densamma; det ska inte finnas något behov av att lägga till något i arkivet, utveckla algoritmer för att lösa konflikter, underhålla sekundära index, etc.
  • Hastigheten på den nya lagringen måste vara ganska hög, både vid läsning av data och vid bearbetning av transaktioner, vilket i praktiken innebar att akademiskt rigorösa, universella men långsamma lösningar, som t.ex. tvåfas commits.
  • Automatisk skalning i farten.
  • Använder vanliga billiga servrar, utan att behöva köpa exotisk hårdvara.
  • Möjlighet till lagringsutveckling av företagsutvecklare. Med andra ord prioriterades proprietära eller öppen källkodslösningar, helst i Java.

Beslut beslut

Genom att analysera möjliga lösningar kom vi fram till två möjliga arkitekturval:

Det första är att ta vilken SQL-server som helst och implementera den feltolerans som krävs, skalningsmekanism, failover-kluster, konfliktlösning och distribuerade, pålitliga och snabba ACID-transaktioner. Vi bedömde det här alternativet som mycket icke-trivialt och arbetskrävande.

Det andra alternativet är att ta en färdig NoSQL-lagring med implementerad skalning, ett failover-kluster, konfliktlösning och implementera transaktioner och SQL själv. Vid första anblicken ser till och med uppgiften att implementera SQL, för att inte tala om ACID-transaktioner, ut som en uppgift som kommer att ta år. Men sedan insåg vi att SQL-funktionsuppsättningen vi använder i praktiken är så långt ifrån ANSI SQL som Cassandra CQL långt ifrån ANSI SQL. När vi tittade ännu närmare på CQL insåg vi att det var ganska nära vad vi behövde.

Cassandra och CQL

Så, vad är intressant med Cassandra, vilka möjligheter har den?

För det första kan du här skapa tabeller som stöder olika datatyper; du kan göra SELECT eller UPDATE 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 replikdatakonsistens använder Cassandra kvorummetod. I det enklaste fallet betyder detta att när tre repliker av samma rad placeras på olika noder i klustret, anses skrivningen vara lyckad om majoriteten av noderna (det vill säga två av tre) bekräftade framgången för denna skrivoperation . Raddata anses vara konsekvent om, vid läsning, majoriteten av noderna tillfrågades och bekräftade dem. Således, med tre repliker, garanteras fullständig och omedelbar datakonsistens om en nod misslyckas. Detta tillvägagångssätt gjorde det möjligt för oss att implementera ett ännu mer tillförlitligt schema: skicka alltid förfrågningar till alla tre replikerna och vänta på svar från de två snabbaste. Det sena svaret från den tredje repliken kasseras i detta fall. En nod som är sen med att svara kan ha allvarliga problem - bromsar, sophämtning i JVM, direkt minnesåtervinning i Linux-kärnan, hårdvarufel, frånkoppling från nätverket. Detta påverkar dock inte kundens verksamhet eller data på något sätt.

Tillvägagångssättet när vi kontaktar tre noder och får svar från två kallas spekulation: en begäran om extra repliker skickas redan innan den "faller av".

En annan fördel med Cassandra är Batchlog, en mekanism som säkerställer att en grupp ändringar du gör antingen tillämpas helt eller inte alls. Detta gör att vi kan lösa A i ACID - atomicitet direkt.

Det som ligger närmast transaktioner i Cassandra är de så kallade "lätta transaktioner". Men de är långt ifrån "riktiga" ACID-transaktioner: i själva verket är detta en möjlighet att göra CAS på data från endast en post, med hjälp av konsensus med det tunga Paxos-protokollet. Därför är hastigheten på sådana transaktioner låg.

Vad vi saknade i Cassandra

Så vi var tvungna att implementera riktiga ACID-transaktioner i Cassandra. Med hjälp av det kunde vi enkelt implementera två andra bekväma funktioner i klassisk DBMS: konsekventa snabba index, som skulle tillåta oss att utföra dataval inte bara med primärnyckeln, och en vanlig generator av monotona auto-inkrementerande ID:n.

Kon

Därmed föddes ett nytt DBMS Kon, bestående av tre typer av servernoder:

  • Lagring – (nästan) standard Cassandra-servrar som ansvarar för att lagra data på lokala diskar. När belastningen och volymen av data växer, kan deras kvantitet lätt skalas till tiotals och hundratals.
  • Transaktionssamordnare - se till att transaktioner genomförs.
  • Klienter är applikationsservrar som implementerar affärsverksamhet och initierar transaktioner. Det kan finnas tusentals sådana kunder.

NewSQL = NoSQL+ACID

Servrar av alla typer är en del av ett gemensamt kluster, använder det interna Cassandra-meddelandeprotokollet för att kommunicera med varandra och skvaller för utbyte av klusterinformation. Med Heartbeat lär sig servrar om ömsesidiga fel, underhåller ett enda dataschema - tabeller, deras struktur och replikering; partitioneringsschema, klustertopologi, etc.

Kunder

NewSQL = NoSQL+ACID

Istället för standarddrivrutiner används Fat Client-läge. En sådan nod lagrar inte data, men kan fungera som en koordinator för exekvering av begäran, det vill säga att klienten själv fungerar som en koordinator av sina förfrågningar: den frågar efter lagringsrepliker och löser konflikter. Detta är inte bara mer tillförlitligt och snabbare än standarddrivrutinen, som kräver kommunikation med en fjärrkoordinator, utan låter dig också kontrollera överföringen av förfrågningar. Utanför en transaktion som är öppen på klienten skickas förfrågningar till repositories. Om kunden har öppnat en transaktion skickas alla förfrågningar inom transaktionen till transaktionskoordinatorn.
NewSQL = NoSQL+ACID

C*One Transaction Coordinator

Koordinatorn är något vi implementerat för C*One från grunden. Den ansvarar för att hantera transaktioner, lås och i vilken ordning transaktionerna tillämpas.

För varje betjänad transaktion genererar koordinatorn en tidsstämpel: varje efterföljande transaktion är större än den föregående transaktionen. Eftersom Cassandras konfliktlösningssystem är baserat på tidsstämplar (av två motstridiga poster, den med den senaste tidsstämpeln anses vara aktuell), kommer konflikten alltid att lösas till förmån för den efterföljande transaktionen. Så genomförde vi Lamport klocka - ett billigt sätt att lösa konflikter i ett distribuerat system.

Lås

För att säkerställa isolering bestämde vi oss för att använda den enklaste metoden - pessimistiska lås baserade på postens primärnyckel. Med andra ord, i en transaktion måste en post först låsas, först sedan läsas, ändras och sparas. Först efter en framgångsrik commit kan en post låsas upp så att konkurrerande transaktioner kan använda den.

Att implementera sådan låsning är enkelt i en icke-distribuerad miljö. I ett distribuerat system finns det två huvudalternativ: antingen implementera distribuerad låsning på klustret eller distribuera transaktioner så att transaktioner som involverar samma post alltid betjänas av samma koordinator.

Eftersom data i vårt fall redan är distribuerade mellan grupper av lokala transaktioner i SQL, beslutades det att tilldela lokala transaktionsgrupper till koordinatorer: en koordinator utför alla transaktioner med tokens från 0 till 9, den andra - med tokens från 10 till 19, och så vidare. Som ett resultat blir var och en av samordnarinstanserna chef för transaktionsgruppen.

Då kan lås implementeras i form av en banal HashMap i koordinatorns minne.

Koordinator misslyckanden

Eftersom en samordnare uteslutande betjänar en grupp av transaktioner, är det mycket viktigt att snabbt fastställa faktumet att det misslyckades så att det andra försöket att utföra transaktionen kommer att ta slut. För att göra detta snabbt och pålitligt använde vi ett fullt anslutet quorum hearbeat-protokoll:

Varje datacenter är värd för minst två koordinatornoder. Med jämna mellanrum skickar varje koordinator ett hjärtslagsmeddelande till de andra koordinatorerna och informerar dem om dess funktion, samt vilka hjärtslagsmeddelanden den fick från vilka koordinatorer i klustret senast.

NewSQL = NoSQL+ACID

Genom att ta emot liknande information från andra som en del av sina hjärtslagsmeddelanden bestämmer varje koordinator själv vilka klusternoder som fungerar och vilka som inte fungerar, vägledd av kvorumprincipen: om nod X har fått information från majoriteten av noder i klustret om det normala mottagning av meddelanden från nod Y, då fungerar Y. Och vice versa, så fort majoriteten rapporterar saknade meddelanden från nod Y, då har Y tackat nej. Det är konstigt att om kvorumet informerar nod X att den inte längre tar emot meddelanden från den, så kommer nod X själv att anse sig ha misslyckats.

Hjärtslagsmeddelanden skickas med hög frekvens, cirka 20 gånger per sekund, med en period på 50 ms. I Java är det svårt att garantera applikationssvar inom 50 ms på grund av den jämförbara längden på pauser som orsakas av sopsamlaren. Vi kunde uppnå denna svarstid med G1-sopsamlaren, som gör att vi kan ange ett mål för varaktigheten av GC-pauser. Men ibland, ganska sällan, överstiger samlarpauserna 50 ms, vilket kan leda till en falsk feldetektering. För att förhindra att detta inträffar rapporterar koordinatorn inte ett fel på en fjärrnod när det första hjärtslagsmeddelandet från den försvinner, bara om flera har försvunnit i rad. Så lyckades vi upptäcka ett fel i koordinatornoden år 200 Fröken.

Men det räcker inte att snabbt förstå vilken nod som har slutat fungera. Vi måste göra något åt ​​detta.

Bokning

Det klassiska schemat innebär, i händelse av ett mastermisslyckande, att starta ett nyval med en av modern universell algoritmer. Sådana algoritmer har dock välkända problem med tidskonvergens och längden på själva valprocessen. Vi kunde undvika sådana ytterligare förseningar genom att använda ett ersättningssystem för samordnare i ett helt uppkopplat nätverk:

NewSQL = NoSQL+ACID

Låt oss säga att vi vill utföra en transaktion i grupp 50. Låt oss i förväg bestämma ersättningsschemat, det vill säga vilka noder som kommer att utföra transaktioner i grupp 50 i händelse av ett fel hos huvudkoordinatorn. Vårt mål är att upprätthålla systemets funktionalitet i händelse av ett datacenterfel. Låt oss bestämma att den första reserven kommer att vara en nod från ett annat datacenter, och den andra reserven kommer att vara en nod från en tredje. Detta schema väljs en gång och ändras inte förrän topologin för klustret ändras, det vill säga tills nya noder kommer in i det (vilket händer mycket sällan). Proceduren för att välja en ny aktiv master om den gamla misslyckas kommer alltid att vara följande: den första reserven blir den aktiva mastern, och om den har slutat fungera kommer den andra reserven att bli aktiv master.

Detta schema är mer tillförlitligt än den universella algoritmen, eftersom det räcker för att aktivera en ny mästare för att fastställa felet hos den gamla.

Men hur kommer kunderna att förstå vilken master som fungerar nu? Det är omöjligt att skicka information till tusentals kunder på 50 ms. En situation är möjlig när en klient skickar en begäran om att öppna en transaktion, utan att ännu veta att denna master inte längre fungerar, och begäran kommer att ta slut. För att förhindra att detta händer skickar kunder spekulativt en begäran om att öppna en transaktion till gruppmästaren och båda hans reserver samtidigt, men bara den som är den aktiva mästaren för tillfället kommer att svara på denna begäran. Kunden kommer att göra all efterföljande kommunikation inom transaktionen endast med den aktiva mastern.

Backupmaster placerar mottagna förfrågningar om transaktioner som inte är deras i kön av ofödda transaktioner, där de lagras under en tid. Om den aktiva mastern dör, behandlar den nya mastern förfrågningar om att öppna transaktioner från sin kö och svarar på klienten. Om klienten redan har öppnat en transaktion med den gamla mastern ignoreras det andra svaret (och uppenbarligen kommer en sådan transaktion inte att slutföras och kommer att upprepas av klienten).

Hur transaktionen fungerar

Låt oss säga att en klient skickade en begäran till samordnaren om att öppna en transaktion för en sådan och en sådan enhet med en sådan och en primärnyckel. Koordinatorn låser denna enhet och placerar den i låstabellen i minnet. Vid behov läser samordnaren denna enhet från minnet och lagrar den resulterande datan i ett transaktionstillstånd i samordnarens minne.

NewSQL = NoSQL+ACID

När en klient vill ändra data i en transaktion, skickar den en begäran till koordinatorn om att modifiera enheten, och koordinatorn placerar den nya informationen i transaktionsstatustabellen i minnet. Detta avslutar inspelningen - ingen inspelning görs till minnet.

NewSQL = NoSQL+ACID

När en klient begär sina egna ändrade uppgifter som en del av en aktiv transaktion, agerar samordnaren enligt följande:

  • om ID:t redan finns i transaktionen tas data från minnet;
  • om det inte finns något ID i minnet läses den saknade datan från lagringsnoderna, kombinerad med de som redan finns i minnet, och resultatet ges till klienten.

Således kan klienten läsa sina egna ändringar, men andra klienter ser inte dessa ändringar, eftersom de bara lagras i koordinatorns minne, de är ännu inte i Cassandra-noderna.

NewSQL = NoSQL+ACID

När klienten skickar commit, sparas tillståndet som fanns i tjänstens minne av koordinatorn i en loggad batch och skickas som en loggad batch till Cassandra-lagringen. Butikerna gör allt som krävs för att säkerställa att detta paket tillämpas atomärt (helt) och returnerar ett svar till samordnaren, som släpper låsen och bekräftar framgången för transaktionen till kunden.

NewSQL = NoSQL+ACID

Och för att återställa, behöver koordinatorn bara frigöra minnet som upptas av transaktionstillståndet.

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

  • Atomicitet. Detta är en garanti för att ingen transaktion delvis kommer att registreras i systemet, antingen kommer alla dess underoperationer att slutföras, eller så kommer ingen att slutföras. Vi följer denna princip genom inloggad batch i Cassandra.
  • Konsistens. Varje framgångsrik transaktion registrerar per definition endast giltiga resultat. Om det efter att ha öppnat en transaktion och utfört en del av operationerna upptäcks att resultatet är ogiltigt, utförs en återställning.
  • Isolering. När en transaktion genomförs bör samtidiga transaktioner inte påverka dess resultat. Konkurrerande transaktioner isoleras med hjälp av pessimistiska lås på koordinatorn. För läsningar utanför en transaktion tillämpas isoleringsprincipen på Read Committed-nivån.
  • stabilitet. Oavsett problem på lägre nivåer – systemavbrott, hårdvarufel – bör ändringar som gjorts av en framgångsrikt genomförd transaktion förbli bevarade när driften återupptas.

Läsning efter index

Låt oss ta en enkel tabell:

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

Den har ett ID (primärnyckel), ägare och ändringsdatum. Du måste göra en mycket enkel begäran - välj data om ägaren med ändringsdatumet "för den sista dagen".

SELECT *
WHERE owner=?
AND modified>?

För att en sådan fråga ska kunna bearbetas snabbt måste du i en klassisk SQL DBMS bygga ett index efter kolumner (ägare, modifierad). Vi kan göra detta ganska enkelt, eftersom vi nu har SYRA-garantier!

Index i C*One

Det finns en källtabell med fotografier där post-ID är den primära nyckeln.

NewSQL = NoSQL+ACID

För ett index skapar C*One en ny tabell som är en kopia av originalet. Nyckeln är densamma som indexuttrycket, och den inkluderar även postens primärnyckel från källtabellen:

NewSQL = NoSQL+ACID

Nu kan frågan för "ägare för den sista dagen" skrivas om som ett urval från en annan tabell:

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

Överensstämmelsen mellan uppgifterna i källtabellsfoton och indextabellen i1 upprätthålls automatiskt av koordinatorn. Enbart baserat på dataschemat, när en ändring tas emot, genererar och lagrar koordinatorn en ändring inte bara i huvudtabellen utan också i kopior. Inga ytterligare åtgärder utförs på indextabellen, loggar läses inte och inga lås används. Det vill säga att lägga till index förbrukar nästan inga resurser och har praktiskt taget ingen effekt på hastigheten för att tillämpa ändringar.

Med ACID kunde vi implementera SQL-liknande index. De är konsekventa, skalbara, snabba, komponerbara och inbyggda i CQL-frågespråket. Inga ändringar av applikationskoden krävs för att stödja index. Allt är lika enkelt som i SQL. Och viktigast av allt, index påverkar inte exekveringshastigheten för ändringar av den ursprungliga transaktionstabellen.

Vad hände

Vi utvecklade C*One för tre år sedan och lanserade den i kommersiell drift.

Vad fick vi till slut? Låt oss utvärdera detta med hjälp av exemplet med bildbehandlings- och lagringsundersystemet, en av de viktigaste typerna av data i ett socialt nätverk. Vi pratar inte om själva bildernas kroppar, utan om all slags metainformation. Nu har Odnoklassniki cirka 20 miljarder sådana poster, systemet behandlar 80 tusen läsbegäranden per sekund, upp till 8 tusen ACID-transaktioner per sekund i samband med datamodifiering.

När vi använde SQL med replikeringsfaktor = 1 (men i RAID 10), lagrades fotometainformationen på ett mycket tillgängligt kluster av 32 maskiner som körde Microsoft SQL Server (plus 11 säkerhetskopior). 10 servrar tilldelades också för lagring av säkerhetskopior. Totalt 50 dyra bilar. Samtidigt fungerade systemet med märklast, utan reserv.

Efter migreringen till det nya systemet fick vi replikeringsfaktor = 3 - en kopia i varje datacenter. Systemet består av 63 Cassandra-lagringsnoder och 6 koordinatormaskiner, för totalt 69 servrar. Men dessa maskiner är mycket billigare, deras totala kostnad är cirka 30% av kostnaden för ett SQL-system. Samtidigt hålls belastningen på 30 %.

Med introduktionen av C*One minskade även latensen: i SQL tog en skrivoperation cirka 4,5 ms. I C*One - cirka 1,6 ms. Transaktionens varaktighet är i genomsnitt mindre än 40 ms, bekräftelsen är klar på 2 ms, läs- och skrivlängden är i genomsnitt 2 ms. 99:e percentilen - endast 3-3,1 ms, antalet timeouts har minskat med 100 gånger - allt på grund av den utbredda användningen av spekulation.

Vid det här laget har de flesta av SQL Server-noderna tagits ur drift, nya produkter utvecklas endast med C*One. Vi anpassade C*One för att fungera i vårt moln ett moln, vilket gjorde det möjligt att påskynda utplaceringen av nya kluster, förenkla konfigurationen och automatisera driften. Utan källkoden skulle detta vara mycket svårare och krångligare.

Nu jobbar vi på att flytta över våra andra lagringsmöjligheter till molnet – men det är en helt annan historia.

Källa: will.com

Lägg en kommentar