NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Indtil for nylig i Odnoklassniki blev omkring 50 TB data behandlet i realtid gemt i SQL Server. For en sådan volumen er det næsten umuligt at give hurtig og pålidelig og endda fejltolerant datacenteradgang ved hjælp af SQL DBMS. Normalt i sådanne tilfælde bruges en af ​​NoSQL-butikkerne, men ikke alt kan overføres til NoSQL: nogle enheder kræver ACID-transaktionsgarantier.

Dette førte os til brugen af ​​NewSQL-lagring, det vil sige et DBMS, der giver fejltolerance, skalerbarhed og ydeevne af NoSQL-systemer, men samtidig bevarer de ACID-garantier, der er kendt for klassiske systemer. Der er få fungerende industrielle systemer af denne nye klasse, så vi implementerede et sådant system selv og satte det i kommerciel drift.

Hvordan det virker og hvad der skete - læs under klippet.

I dag er Odnoklassnikis månedlige publikum mere end 70 millioner unikke besøgende. Vi komme ind i top fem største sociale netværk i verden og blandt de tyve bedste websteder, hvor brugerne bruger mest tid. "OK"-infrastrukturen håndterer meget høje belastninger: over en million HTTP-anmodninger/sek. pr. front. Dele af serverparken i mængden af ​​mere end 8000 stykker er placeret tæt på hinanden - i fire Moskva-datacentre, hvilket gør det muligt at give en netværksforsinkelse på mindre end 1 ms mellem dem.

Vi har brugt Cassandra siden 2010, startende med version 0.6. I dag er flere dusin klynger i drift. Den hurtigste klynge behandler over 4 millioner operationer i sekundet, mens den største lagrer 260 TB.

Dette er dog alle almindelige NoSQL-klynger, der bruges til at gemme svagt koordineret data. Vi ønskede også at erstatte det primære konsistente lager, Microsoft SQL Server, som har været brugt siden grundlæggelsen af ​​Odnoklassniki. Lageret bestod af mere end 300 SQL Server Standard Edition-maskiner, som indeholdt 50 TB data - forretningsenheder. Disse data er ændret som en del af ACID-transaktioner og kræver høj konsistens.

For at distribuere data på tværs af SQL Server-noder brugte vi både lodret og vandret opdeling (skæring). Historisk brugte vi et simpelt datadelingsskema: hver enhed var forbundet med et token - en funktion af enheds-id'et. Enheder med samme token blev placeret på den samme SQL-server. Hoved-detalje-relationen blev implementeret på en sådan måde, at tokens for master- og underordnede poster altid matcher og er placeret på den samme server. I et socialt netværk genereres næsten alle registreringer på vegne af brugeren, hvilket betyder, at alle brugerdata inden for et funktionelt undersystem er gemt på én server. Det vil sige, at en forretningstransaktion næsten altid involverede tabeller på én SQL-server, hvilket gjorde det muligt at sikre datakonsistens ved hjælp af lokale ACID-transaktioner uden behov for brug langsom og upålidelig distribuerede ACID-transaktioner.

Takket være sharding og for at fremskynde SQL:

  • Vi bruger ikke begrænsninger for fremmednøgle, da enheds-id'et ved sharding kan være placeret på en anden server.
  • Vi bruger ikke lagrede procedurer og triggere på grund af den ekstra belastning på DBMS CPU'en.
  • Vi bruger ikke JOINs på grund af alt ovenstående og en masse tilfældige læsninger fra disken.
  • Uden for en transaktion bruger vi isolationsniveauet Læs ikke-forpligtet til at reducere dødvande.
  • Vi udfører kun korte transaktioner (mindre end 100 ms i gennemsnit).
  • Vi bruger ikke multi-row UPDATE og DELETE på grund af et stort antal deadlocks - vi opdaterer kun én post ad gangen.
  • Forespørgsler udføres altid kun af indekser - en forespørgsel med en fuld tabelscanningsplan betyder for os en overbelastning af databasen og dens fejl.

Disse trin gjorde det muligt for os at presse næsten den maksimale ydeevne ud af SQL-servere. Problemerne blev dog flere og flere. Lad os tage et kig på dem.

Problemer med SQL

  • Da vi brugte selvskrevet sharding, blev tilføjelse af nye shards udført manuelt af administratorer. Al denne tid har skalerbare datareplikaer ikke leveret anmodninger.
  • Efterhånden som antallet af poster i tabellen vokser, falder hastigheden af ​​indsættelse og modifikation, når du tilføjer indekser til en eksisterende tabel, falder hastigheden med et multiplum, oprettelsen og genskabelsen af ​​indekser tager en nedetid.
  • At have en lille mængde Windows til SQL Server i produktion gør infrastrukturstyring vanskelig

Men hovedproblemet er

fejltolerance

Klassisk SQL Server har dårlig fejltolerance. Lad os sige, at du kun har én databaseserver, og den fejler hvert tredje år. På nuværende tidspunkt er siden nede i 20 minutter, dette er acceptabelt. Hvis du har 64 servere, så er siden nede en gang hver tredje uge. Og hvis du har 200 servere, så virker siden ikke hver uge. Dette er et problem.

Hvad kan der gøres for at forbedre SQL-serverens fejltolerance? Wikipedia inviterer os til at bygge høj tilgængelig klynge: hvor der i tilfælde af fejl på nogen af ​​komponenterne er en backup.

Dette kræver en flåde af dyrt udstyr: talrige duplikeringer, fiberoptik, delt lagring, og medtagelsen af ​​reserven fungerer ikke pålideligt: ​​omkring 10 % af indeslutningerne ender i fejl i backup-knudepunktet med et tog bag hovedknudepunktet.

Men den største ulempe ved en sådan meget tilgængelig klynge er nul tilgængelighed i tilfælde af fejl i det datacenter, hvor det er placeret. Odnoklassniki har fire datacentre, og vi skal sikre arbejde i et af dem i tilfælde af en fuldstændig fejl.

Til dette kunne man bruge Multi Master replikering indbygget i SQL Server. Denne løsning er meget dyrere på grund af omkostningerne ved software og lider af velkendte replikeringsproblemer - uforudsigelige transaktionsforsinkelser med synkron replikering og forsinkelser i at anvende replikering (og som følge heraf tabte modifikationer) med asynkron. underforstået manuel konfliktløsning gør denne mulighed fuldstændig uanvendelig for os.

Alle disse problemer krævede en kardinal løsning, og vi gik videre til deres detaljerede analyse. Her skal vi sætte os ind i, hvad SQL Server i bund og grund gør – transaktioner.

Enkel transaktion

Overvej den enkleste transaktion fra en anvendt SQL-programmørs synspunkt: at tilføje et foto til et album. Album og fotos er gemt i forskellige plader. Albummet har en offentlig fototæller. Så er en sådan transaktion opdelt i følgende trin:

  1. Vi blokerer albummet med nøgle.
  2. Opret en post i fototabellen.
  3. Hvis billedet har en offentlig status, afvikler vi tælleren for offentlige billeder i albummet, opdaterer posten og foretager transaktionen.

Eller i pseudokode:

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, at det mest almindelige scenarie for en forretningstransaktion er at læse data fra databasen ind i applikationsserverens hukommelse, ændre noget og gemme de nye værdier tilbage til databasen. Normalt i en sådan transaktion opdaterer vi flere enheder, flere tabeller.

Når en transaktion udføres, kan der forekomme en samtidig ændring af de samme data fra et andet system. For eksempel kan Antispam beslutte, at brugeren på en eller anden måde er mistænksom, og derfor bør alle billeder af brugeren ikke længere være offentlige, de skal sendes til moderation, hvilket betyder, at foto.status ændres til en anden værdi og de tilsvarende tællere slukkes. Det er klart, hvis denne operation vil finde sted uden garantier for atomicitet af anvendelse og isolering af konkurrerende modifikationer, som i ACID, så bliver resultatet ikke, hvad du har brug for - enten vil fototælleren vise den forkerte værdi, eller også vil ikke alle billeder blive sendt til moderation.

En masse sådan kode, der manipulerer forskellige forretningsenheder inden for en enkelt transaktion, er blevet skrevet gennem hele Odnoklassnikis eksistens. Baseret på erfaringerne med at migrere til NoSQL med Begivenhedskonsistens vi ved, at den største udfordring (og tidskrævende) er behovet for at udvikle kode for at opretholde datakonsistens. Derfor anså vi hovedkravet for det nye lager for at være bestemmelsen til applikationslogikken for rigtige ACID-transaktioner.

Andre lige vigtige krav var:

  • I tilfælde af datacenterfejl skal både læsning og skrivning til det nye lager være tilgængeligt.
  • Fastholdelse af den nuværende udviklingshastighed. Det vil sige, at når man arbejder med et nyt lager, skal mængden af ​​kode være nogenlunde den samme, der skal ikke være behov for at tilføje noget til depotet, udvikle algoritmer til løsning af konflikter, vedligeholde sekundære indekser osv.
  • Hastigheden på den nye lagring skulle være hurtig nok, både ved læsning af data og ved behandling af transaktioner, hvilket reelt betød, at akademisk stringente, generelle, men langsomme løsninger, som f.eks. to-faset commits.
  • Automatisk skalering i farten.
  • Bruger almindelige billige servere, uden at det er nødvendigt at købe eksotiske stykker jern.
  • Mulighed for lagerudvikling af virksomhedens udviklere. Med andre ord blev der prioriteret proprietære eller open source-løsninger, helst i Java.

Beslutninger, beslutninger

Ved at analysere mulige løsninger kom vi frem til to mulige arkitekturvalg:

Den første er at tage en hvilken som helst SQL-server og implementere den nødvendige fejltolerance, skaleringsmekanisme, failover-klynger, konfliktløsning og distribuerede, pålidelige og hurtige ACID-transaktioner. Vi vurderede denne mulighed som meget ikke-triviel og tidskrævende.

Den anden mulighed er at tage et færdiglavet NoSQL-lager med implementeret skalering, failover-clustering, konfliktløsning og selv implementere transaktioner og SQL. Ved første øjekast ser selv opgaven med at implementere SQL, for ikke at nævne ACID-transaktioner, ud som en opgave i årevis. Men så indså vi, at det sæt af SQL-funktioner, som vi bruger i praksis, er så langt fra ANSI SQL som Cassandra CQL langt fra ANSI SQL. Da vi kiggede nærmere på CQL, indså vi, at det er tæt nok på det, vi har brug for.

Cassandra og CQL

Så hvad er interessant ved Cassandra, hvilke funktioner har det?

For det første kan du her oprette tabeller med understøttelse af forskellige datatyper, du kan gøre SELECT eller UPDATE med primærnøgle.

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

For at sikre replikadatakonsistens bruger Cassandra kvorum tilgang. I det enkleste tilfælde betyder dette, at når du placerer tre replikaer af samme række på forskellige noder i klyngen, anses skrivningen for at være vellykket, hvis størstedelen af ​​noderne (dvs. to ud af tre) bekræftede succesen af ​​denne skriveoperation. Dataene for en serie anses for at være konsistente, hvis de fleste knudepunkter blev undersøgt og bekræftet under læsning. Hvis der er tre replikaer, garanteres fuld og øjeblikkelig datakonsistens, hvis en node fejler. Denne tilgang gjorde det muligt for os at implementere en endnu mere pålidelig ordning: send altid anmodninger til alle tre replikaer og venter på svar fra de to hurtigste. Det sene svar fra den tredje replika kasseres derefter. Samtidig kan en node, der er forsinket med et svar, have alvorlige problemer - bremser, affaldsopsamling i JVM, direkte hukommelsesgenvinding i linux-kernen, hardwarefejl, afbrydelse af netværket. Klientdrift og data påvirkes dog ikke på nogen måde.

Tilgangen, når vi får adgang til tre noder og modtager et svar fra to, kaldes spekulation: en anmodning om ekstra replikaer sendes, før den "falder af".

En anden fordel ved Cassandra er Batchlog, en mekanisme, der sikrer, at de ændringer, du foretager, enten anvendes fuldt ud eller ikke anvendes fuldstændigt på pakken. Dette giver os mulighed for at løse A i ACID - atomicitet ud af boksen.

Det tætteste på transaktioner i Cassandra er den såkaldte "lette transaktioner". Men de er langt fra "rigtige" ACID-transaktioner: faktisk er dette en mulighed for at lave CAS på data fra kun én post, ved hjælp af Paxos tungvægtsprotokol konsensus. Derfor er hastigheden af ​​sådanne transaktioner lav.

Hvad vi savnede i Cassandra

Så vi var nødt til at implementere rigtige ACID-transaktioner i Cassandra. Ved hjælp af hvilken vi nemt kunne implementere to andre bekvemme funktioner i det klassiske DBMS: konsekvente hurtige indekser, som ville give os mulighed for at vælge data ikke kun med den primære nøgle, og den sædvanlige generator af monotone auto-inkrement-id'er.

C*En

Så det nye DBMS blev født C*En, bestående af tre typer servernoder:

  • Storage er (næsten) standard Cassandra-servere, der er ansvarlige for lagring af data på lokale drev. Efterhånden som belastningen og mængden af ​​data vokser, kan deres antal nemt skaleres op til tiere og hundreder.
  • Transaktionskoordinatorer - sikre udførelsen af ​​transaktioner.
  • Klienter er applikationsservere, der implementerer forretningsdrift og initierer transaktioner. Der kan være tusindvis af sådanne kunder.

NewSQL = NoSQL+ACID

Servere af alle typer er i en fælles klynge, bruger Cassandras interne meddelelsesprotokol til at kommunikere med hinanden og sladder til udveksling af klyngeoplysninger. Ved hjælp af Heartbeat lærer servere om gensidige fejl, vedligeholder et enkelt dataskema - tabeller, deres struktur og replikering; opdelingsskema, klyngetopologi mv.

Kunder

NewSQL = NoSQL+ACID

I stedet for standarddrivere bruges Fat Client-tilstand. En sådan node gemmer ikke data, men kan fungere som en anmodningsudførelseskoordinator, det vil sige, at klienten selv fungerer som en koordinator af sine anmodninger: den poller lagerreplikaer og løser konflikter. Dette er ikke kun mere pålideligt og hurtigere end standarddriveren, som kræver kommunikation med en fjernkoordinator, men giver dig også mulighed for at kontrollere transmissionen af ​​anmodninger. Uden for en transaktion, der er åben på klienten, sendes anmodninger til lagre. Hvis klienten åbnede en transaktion, sendes alle anmodninger inden for transaktionen til transaktionskoordinatoren.
NewSQL = NoSQL+ACID

C*One Transaction Coordinator

Koordinatoren er det, vi implementerede for C*One fra bunden. Det er ansvarligt for at administrere transaktioner, låse og den rækkefølge, som transaktioner anvendes i.

For hver serviceret transaktion genererer koordinatoren et tidsstempel: hver efterfølgende er større end den tidligere transaktion. Da konfliktløsningssystemet i Cassandra er baseret på tidsstempler (af to modstridende poster anses det seneste tidsstempel for relevant), vil konflikten altid blive løst til fordel for den efterfølgende transaktion. Således har vi implementeret lamport ur er en billig måde at løse konflikter på i et distribueret system.

Låse

For at sikre isolation besluttede vi at bruge den nemmeste måde - pessimistiske låse på postens primære nøgle. Med andre ord, i en transaktion skal en post først låses, først derefter læses, ændres og gemmes. Først efter en vellykket commit kan en post låses op, så konkurrerende transaktioner kan bruge den.

Implementering af en sådan lås er enkel i et ikke-distribueret miljø. I et distribueret system er der to hovedmåder: enten implementere distribueret låsning på en klynge eller distribuere transaktioner, så transaktioner, der involverer den samme post, altid betjenes af den samme koordinator.

Da dataene i vores tilfælde allerede er fordelt mellem grupper af lokale transaktioner i SQL, blev det besluttet at tildele grupper af lokale transaktioner til koordinatorerne: en koordinator udfører alle transaktioner med et token fra 0 til 9, den anden - med et token fra 10 til 19 og så videre. Som et resultat bliver hver af forekomsterne af koordinatoren master for transaktionsgruppen.

Så kan låse implementeres som et banalt HashMap i koordinatorens hukommelse.

Afslag fra koordinatorer

Da en koordinator udelukkende betjener en gruppe af transaktioner, er det meget vigtigt hurtigt at fastslå, om dens fiasko, så det gentagne forsøg på at udføre transaktionen er inden for timeout. For at gøre dette hurtigt og pålideligt brugte vi en fuldt masket kvorum-hearbeat-protokol:

Hvert datacenter er vært for mindst to koordinatorknudepunkter. Med jævne mellemrum sender hver koordinator en hjerteslagsbesked til de andre koordinatorer og informerer dem om dens funktion, samt om sidste gang den modtog hjerteslagsmeddelelser fra hvilke koordinatorer i klyngen.

NewSQL = NoSQL+ACID

Ved at modtage lignende information fra resten som en del af deres hjerteslagsmeddelelser beslutter hver koordinator selv, hvilke noder i klyngen der fungerer, og hvilke der ikke er, styret af kvorumsprincippet: hvis node X modtog information fra flertallet af noder i klyngen ca. den normale modtagelse af beskeder fra node Y, så fungerer Y. Omvendt, så snart flertallet rapporterer manglende beskeder fra node Y, så har Y fejlet. Mærkeligt nok, hvis kvorum fortæller node X, at det ikke modtager flere meddelelser fra det, så vil node X selv anse sig selv for at have fejlet.

Heartbeat-beskeder sendes med en høj frekvens, omkring 20 gange i sekundet, med en periode på 50 ms. I Java er det svært at garantere et programsvar inden for 50 ms på grund af sammenlignelige pausetider forårsaget af skraldeopsamleren. Vi var i stand til at opnå denne responstid ved at bruge G1-affaldsopsamleren, som giver dig mulighed for at angive et mål for varigheden af ​​GC-pauser. Men nogle gange, ret sjældent, går opsamlerpauserne ud over 50 ms, hvilket kan føre til en falsk fejlregistrering. For at undgå dette melder koordinatoren ikke fejlen af ​​fjernknuden, når den første hjerteslagsmeddelelse fra den er tabt, kun hvis der mangler flere i træk, så det lykkedes os at opdage fejlen i koordinatorknuden på 200 ms.

Men det er ikke nok hurtigt at forstå, hvilken node der er holdt op med at fungere. Det skal der gøres noget ved.

Reservation

Den klassiske ordning antager, at i tilfælde af at mesteren ikke lancerer valget af en ny ved hjælp af en af på mode universel algoritmer. Sådanne algoritmer har dog velkendte problemer med konvergens i tid og varigheden af ​​selve valgprocessen. Det lykkedes os at undgå sådanne yderligere forsinkelser ved at bruge koordinatorerstatningsordningen i et fuldt tilsluttet netværk:

NewSQL = NoSQL+ACID

Lad os sige, at vi ønsker at udføre en transaktion i gruppe 50. Lad os definere et erstatningsskema på forhånd, det vil sige, hvilke noder der vil udføre gruppe 50-transaktioner i tilfælde af fejl hos hovedkoordinatoren. Vores mål er at holde systemet oppe og køre i tilfælde af et datacenterfejl. Lad os definere, at den første reserve vil være en node fra et andet datacenter, og den anden reserve vil være en node fra den tredje. Dette skema vælges én gang og ændres ikke, før klyngens topologi ændres, det vil sige, indtil nye noder kommer ind i den (hvilket sker meget sjældent). Rækkefølgen for at vælge en ny aktiv master i tilfælde af fejl i den gamle vil altid være som følger: den første reserve bliver den aktive master, og hvis den er ophørt med at fungere, bliver den anden reserve.

En sådan ordning er mere pålidelig end en universel algoritme, da for at aktivere en ny mester er det nok at bestemme kendsgerningen om den gamle fejl.

Men hvordan vil kunderne forstå, hvem af mestrene der arbejder i øjeblikket? Det er umuligt at sende information til tusindvis af kunder på 50 ms. Det er muligt, at en klient sender en anmodning om at åbne en transaktion uden endnu at vide, at denne master ikke længere fungerer, og anmodningen vil hænge på en timeout. For at forhindre dette i at ske, sender klienter spekulativt en anmodning om at åbne en transaktion til masteren af ​​gruppen og begge dens reserver på én gang, men kun den, der er den aktive master i øjeblikket, vil svare på denne anmodning. Al efterfølgende kommunikation inden for transaktionen udføres kun af klienten med den aktive master.

Standby-masterne placerer de modtagne anmodninger om transaktioner, der ikke er deres egne, i køen af ​​ufødte transaktioner, hvor de opbevares i nogen tid. Hvis den aktive master dør, behandler den nye master anmodninger om at åbne transaktioner fra sin kø og svarer til klienten. Hvis klienten allerede har formået at åbne en transaktion med den gamle master, ignoreres det andet svar (og naturligvis vil en sådan transaktion ikke fuldføres og vil blive gentaget af klienten).

Sådan fungerer en transaktion

Antag, at klienten sendte en anmodning til koordinatoren om at åbne en transaktion for sådan og sådan entitet med sådan og sådan en primær nøgle. Koordinatoren låser denne enhed og placerer den i låsetabellen i hukommelsen. Om nødvendigt læser koordinatoren denne enhed fra lageret og gemmer de modtagne data i en transaktionstilstand i koordinatorens hukommelse.

NewSQL = NoSQL+ACID

Når en klient ønsker at ændre dataene i en transaktion, sender den en anmodning til koordinatoren om at ændre entiteten, og koordinatoren placerer de nye data i transaktionstilstandstabellen i hukommelsen. Dette afslutter optagelsen - lageret skrives ikke til.

NewSQL = NoSQL+ACID

Når en klient anmoder om sine egne ændrede data som en del af en aktiv transaktion, handler koordinatoren således:

  • hvis ID'et allerede er i transaktionen, tages dataene fra hukommelsen;
  • hvis der ikke er noget ID i hukommelsen, så læses de manglende data fra lagringsknuderne, kombineret med dem, der allerede er i hukommelsen, og resultatet returneres til klienten.

Således kan klienten læse sine egne ændringer, og andre klienter kan ikke se disse ændringer, fordi de kun er gemt i koordinatorens hukommelse, de er endnu ikke i Cassandra-knuderne.

NewSQL = NoSQL+ACID

Når klienten sender en commit, bliver den tilstand, som tjenesten havde i hukommelsen, gemt af koordinatoren i en logget batch og sendt til Cassandra-butikkerne som en logget batch. Lagrene gør alt, hvad der er nødvendigt for, at denne pakke bliver atomært (fuldt) anvendt, og returnerer et svar til koordinatoren, som frigiver låsene og bekræfter transaktionens succes til klienten.

NewSQL = NoSQL+ACID

Og for tilbagerulning behøver koordinatoren kun at frigøre den hukommelse, der er optaget af transaktionens tilstand.

Som et resultat af de ovenfor beskrevne forbedringer har vi implementeret ACID-principperne:

  • Atomicitet. Dette er en garanti for, at ingen transaktion vil blive delvist rettet i systemet, enten vil alle dets underoperationer blive udført, eller ingen af ​​dem vil blive udført. I vores tilfælde overholdes dette princip på grund af den loggede batch i Cassandra.
  • Konsistens. Hver vellykket transaktion forpligter per definition kun gyldige resultater. Hvis det efter åbning af en transaktion og udførelse af nogle af operationerne konstateres, at resultatet er ugyldigt, udføres en tilbagerulning.
  • isolation. Når en transaktion udføres, bør parallelle transaktioner ikke påvirke resultatet. Samtidige transaktioner er isoleret med pessimistiske låse på koordinatoren. For aflæsninger uden for en transaktion overholdes princippet om isolation på Read Committed-niveauet.
  • stabilitet. Uanset problemer på de lavere niveauer - et systemafbrydelse, en hardwarefejl - bør ændringerne, der er foretaget af en vellykket gennemført transaktion, forblive gemt efter genoptagelsen af ​​funktionen.

Læsning efter indeks

Lad os tage en simpel tabel:

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

Den har et ID (primær nøgle), en ejer og en ændringsdato. Du skal lave en meget enkel anmodning - vælg data om ejeren med datoen for ændringen "for den sidste dag".

SELECT *
WHERE owner=?
AND modified>?

For at kunne behandle en sådan forespørgsel hurtigt, i en klassisk SQL DBMS, skal du bygge et indeks på kolonnerne (ejer, modificeret). Det kan vi ganske enkelt, da vi nu har SYRE-garantier!

Indekser i C*One

Der er en indledende tabel med billeder, hvor post-ID er en primær nøgle.

NewSQL = NoSQL+ACID

For et indeks opretter C*One en ny tabel, der er en kopi af den originale tabel. Nøglen er den samme som indeksudtrykket, men den inkluderer også postens primære nøgle fra kildetabellen:

NewSQL = NoSQL+ACID

Nu kan forespørgslen efter "ejer inden for de sidste XNUMX timer" omskrives som et udvalg fra en anden tabel:

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

Datakonsistens mellem kildetabelbillederne og indeks i1 opretholdes automatisk af koordinatoren. Baseret på dataskemaet alene, når en ændring modtages, genererer og husker koordinatoren ændringen ikke kun af hovedtabellen, men også ændringerne af kopierne. Der udføres ingen yderligere handlinger med indekstabellen, logs læses ikke, låse bruges ikke. Det vil sige, at tilføjelse af indekser næsten ikke bruger ressourcer og praktisk talt ikke påvirker hastigheden af ​​at anvende ændringer.

Ved hjælp af ACID lykkedes det os at implementere indekser "som i SQL". De er konsekvente, skalerbare, hurtige, komponerbare og indbygget i CQL-forespørgselssproget. Indekssupport kræver ingen ændringer af applikationskoden. Alt er enkelt, som i SQL. Og vigtigst af alt påvirker indekser ikke hastigheden for udførelse af ændringer til den oprindelige transaktionstabel.

Hvad skete der

Vi udviklede C*One for tre år siden og satte den i kommerciel drift.

Hvad endte vi med? Lad os evaluere dette på eksemplet med et undersystem til behandling og lagring af fotos, en af ​​de vigtigste typer data i et socialt netværk. Det handler ikke om selve fotografiernes kroppe, men om alle former for metainformation. Nu i Odnoklassniki er der omkring 20 milliarder sådanne poster, systemet behandler 80 tusind læseanmodninger i sekundet, op til 8 tusind ACID-transaktioner i sekundet relateret til datamodifikation.

Da vi brugte SQL med replikeringsfaktor = 1 (men i RAID 10), blev fotometainformationen gemt på en meget tilgængelig klynge af 32 Microsoft SQL Server-maskiner (plus 11 reservedele). Desuden blev 10 servere tildelt til lagring af sikkerhedskopier. I alt 50 dyre biler. Samtidig fungerede systemet ved nominel belastning uden margen.

Efter migrering til et nyt system fik vi replikeringsfaktor = 3 - en kopi i hvert datacenter. Systemet består af 63 Cassandra storage noder og 6 koordinatormaskiner, til i alt 69 servere. Men disse maskiner er meget billigere og udgør i alt omkring 30 % af omkostningerne ved et SQL-system. I dette tilfælde holdes belastningen på niveauet 30%.

Med introduktionen af ​​C*One faldt ventetiden også: i SQL tog en skriveoperation omkring 4,5 ms. I C * One - omkring 1,6 ms. Varigheden af ​​en transaktion er i gennemsnit mindre end 40 ms, forpligtelsen er afsluttet på 2 ms, varigheden af ​​læsning og skrivning er 2 ms i gennemsnit. 99. percentilen er kun 3-3,1 ms, antallet af timeouts er faldet 100 gange – alt sammen på grund af den udbredte brug af spekulation.

Til dato er de fleste af SQL Server-knuderne blevet dekommissioneret, nye produkter udvikles kun ved hjælp af C * One. Vi tilpassede C*One til at fungere i vores cloud én-sky, som gjorde det muligt at fremskynde implementeringen af ​​nye klynger, forenkle konfigurationen og automatisere driften. Uden kildekoden ville dette være meget vanskeligere og mere ulækkert.

Nu arbejder vi på at overføre vores andre storages til skyen – men det er en helt anden historie.

Kilde: www.habr.com

Tilføj en kommentar