
Frem til nylig har omkring 50 TB med data blitt behandlet i sanntid i Odnoklassniki, lagret i SQL Server. For sÄ mye data er det nesten umulig Ä sikre rask og pÄlitelig, samt feiltolerant tilgang til datasenteret ved hjelp av SQL-databaser. Vanligvis brukes en av NoSQL-lagringene i slike tilfeller, men ikke alt kan flyttes til NoSQL: enkelte entiteter krever garantier for ACID-transaksjoner.
Dette fÞrte oss til Ä bruke et NewSQL-lagringssystem, altsÄ en database som tilbyr feiltoleranse, skalerbarhet og ytelsen til NoSQL-systemer, samtidig som den opprettholder de kjente ACID-garantiene fra klassiske systemer. Det finnes fÄ fungerende industrielle systemer av denne nye klassen, sÄ vi implementerte et lignende system selv og tok det i drift.
Hvordan dette fungerer og hva vi fikk til â les mer under.
I dag er den mÄnedlige publikum pÄ «Odnoklassniki» over 70 millioner unike besÞkende. Vi sosiale mediene i verden, og blant de tyve nettstedene der brukerne tilbringer mest tid. Infrastrukturene til «OK» hÄndterer svÊrt hÞye belastninger: over en million HTTP-forespÞrsel/s pÄ frontene. Deler av serverparken, som bestÄr av mer enn 8000 enheter, er plassert nÊrt hverandre - i fire datasilos i Moskva, noe som muliggjÞr en nettverksforsinkelse pÄ mindre enn 1 ms mellom dem.
Vi har brukt Cassandra siden 2010, fra versjon 0.6. I dag er flere titalls klynger i drift. Den raskeste klyngen behandler over 4 millioner operasjoner per sekund, og den stĂžrste lagrer 260 TB.
Imidlertid er alt dette vanlige NoSQL-klynger som brukes til lagring av data. Vi Ăžnsket imidlertid Ă„ erstatte det primĂŠre konsistente lageret, Microsoft SQL Server, som har blitt brukt siden «Odnoklassniki» ble grunnlagt. Lagringssystemet besto av mer enn 300 SQL Server Standard Edition-maskiner, som inneholdt 50 TB med data â forretningsentiteter. Disse dataene modifiseres innenfor ACID-transaksjoner og krever .
For distribusjon av data mellom nodene i SQL Server brukte vi bĂ„de vertikal og horisontal (sharding). Historisk har vi brukt et enkelt datashardingskjema: hver entitet ble tildelt en token â en funksjon av entitetens ID. Entiteter med samme token ble plassert pĂ„ en SQL-server. Master-detail-forhold ble implementert slik at tokenene for hoved- og underpost alltid stemte overens og befant seg pĂ„ samme server. I sosiale nettverk genereres nesten alle poster pĂ„ vegne av brukeren â dermed lagres alle brukerdata innenfor et funksjonelt subsystem pĂ„ Ă©n server. Det vil si at i forretningstransaksjoner deltok nesten alltid tabeller fra Ă©n SQL-server, noe som gjorde det mulig Ă„ sikre datakonsistens ved hjelp av lokale ACID-transaksjoner, uten behov for Ă„ bruke distribuerte ACID-transaksjoner.
Takket vÊre sharding og for Ä Þke hastigheten pÄ SQL:
- Vi bruker ikke fremmednÞkkelbegrensninger, siden entitetens ID kan befinne seg pÄ en annen server ved sharding.
- Vi bruker ikke lagrede prosedyrer og triggere pÄ grunn av ekstra belastning pÄ database-CPU-en.
- Vi bruker ikke JOINs pÄ grunn av alt det ovennevnte og mange tilfeldige disklesinger.
- Utenfor transaksjoner for Ä redusere dÞdlÄs bruker vi isolasjonsnivÄet Read Uncommitted.
- Vi utfĂžrer kun korte transaksjoner (i gjennomsnitt kortere enn 100 ms).
- Vi bruker ikke flerader UPDATE og DELETE pĂ„ grunn av mange dĂždlĂ„ser â vi oppdaterer kun Ă©n post om gangen.
- ForespĂžrslene utfĂžres alltid kun etter indekser â en forespĂžrsel med et fullt tabellvisningsplan betyr overbelastning av databasen og dens svikt.
Disse trinnene har gjort det mulig Ä presse nesten maksimal ytelse ut av SQL-serverne. Imidlertid ble problemene stadig flere. La oss se pÄ dem.
Problemer med SQL
- Siden vi brukte egenutviklet sharding, ble tilfĂžrsel av nye shards utfĂžrt manuelt av administratorene. I hele denne perioden betjente ikke skalerbare datakopier forespĂžrslene.
- Etter hvert som antallet poster i tabellen Þker, reduseres hastigheten for innsetting og modifikasjon; nÄr man legger til indekser til eksisterende tabeller, synker hastigheten drastisk, og opprettelse og gjenoppretting av indekser skjer med nedetid.
- TilstedevÊrelsen av et lite antall Windows-servere for SQL Server i produksjon kompliserer hÄndteringen av infrastrukturen.
Men det stĂžrste problemet er â
Feiltoleranse
En klassisk SQL-server har dÄrlig feiloverfÞringsevne. La oss si at du bare har én database-server, og den feiler hvert tredje Är. I lÞpet av denne tiden fungerer ikke nettstedet i 20 minutter, noe som er akseptabelt. Men hvis du har 64 servere, vil nettstedet ikke fungere hver tredje uke. Og hvis du har 200 servere, vil nettstedet ikke fungere hver uke. Dette er et problem.
Hva kan gjÞres for Ä forbedre feiloverfÞringsevnen til en SQL-server? Wikipedia foreslÄr at vi bygger : der det finnes en duplikat i tilfelle en av komponentene feiler.
Dette krever en park av kostbart utstyr: omfattende duplisering, fiberoptikk, felles lagring, og det Ă„ inkludere reserver fungerer usikkert: rundt 10 % av oppstartene ender med feil i den reserve noden i rĂŠkken bakerst.
Men den stÞrste ulempen med en slik hÞytilgjengelig klynge er null tilgjengelighet ved svikt i datasenteret den er plassert i. «Odnoklassniki» har fire datasentre, og vi mÄ sikre drift under total havari i ett av dem.
For dette kunne man bruke replikering, innebygd i SQL Server. Denne lĂžsningen er betydelig dyrere pĂ„ grunn av programvarekostnaden og sliter med godt kjente problemer med replikering â uforutsigbare forsinkelser i transaksjoner ved synkron replikering og forsinkelser i anvendelsen av replikeringene (og dermed tapte modifikasjoner) ved asynkron. Det underforstĂ„tte gjĂžr dette alternativet helt ubrukelig for oss.
Alle disse problemene krevde en radikal lĂžsning, og vi begynte en detaljert analyse av dem. Her mĂ„ vi bli kjent med hva SQL Server fĂžrst og fremst gjĂžr â transaksjoner.
En enkel transaksjon
La oss se pÄ den enkleste transaksjonen, fra et praktisk SQL-programmerers perspektiv: legge til et bilde i et album. Album og bilder lagres i ulike tabeller. Et album har en teller for offentlige bilder. Da deles en slik transaksjon opp i fÞlgende trinn:
- Vi lÄser albumet med en nÞkkel.
- Vi oppretter en post i bildets tabell.
- Hvis bildet har offentlig status, Ăžker vi tellingen av offentlige bilder i albumet, oppdaterer posten og forplikter transaksjonen.
Eller i form av 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 vanlige scenariet for forretningstransaksjoner er Ă„ lese data fra databasen inn i applikasjonsserverens minne, gjĂžre noen endringer og lagre de nye verdiene tilbake i databasen. Vanligvis i en slik transaksjon oppdaterer vi flere entiteter, flere tabeller.
NÄr transaksjonen utfÞres, kan det skje konkurrerende modifikasjoner av de samme dataene fra et annet system. For eksempel kan Antispam vurdere en bruker som mistenkelig, og derfor skal alle bilder til den brukeren ikke lenger vÊre offentlige, de mÄ sendes til moderering, noe som betyr at photo.status mÄ endres til en annen verdi og de tilhÞrende tellerne mÄ justeres. Det er Äpenbart at hvis denne operasjonen skjer uten garantier for atomisk anvendelse og isolasjon av konkurrerende modifikasjoner, som i , vil ikke resultatet bli det som er nÞdvendig - enten vil foto-telleren vise en feil verdi, eller ikke alle bilder vil bli sendt til moderering.
Det er skrevet mye liknende kode, som manipulerer forskjellige forretningsenheter innenfor en enkelt transaksjon, gjennom tiden til Eksperimentene. Basert pÄ erfaringene fra migrering til NoSQL med vet vi at de stÞrste utfordringene (og tidskostnadene) kommer fra behovet for Ä utvikle kode som opprettholder datakonsistens. Derfor var det viktigste kravet til det nye lagringssystemet Ä sikre ekte ACID-transaksjoner for applikasjonslogikken.
Andre, minst like viktige, krav var:
- Ved sentersvikt mÄ bÄde lesing og skriving til det nye lagringssystemet vÊre tilgjengelig.
- Opprettholde den nÄvÊrende hastigheten for utvikling. Det vil si at arbeidet med det nye lagringssystemet ikke skal kreve mye mer kode, og det skal ikke vÊre behov for Ä skrive noe nytt i lagringssystemet, utvikle algoritmer for konfliktlÞsning, opprettholde sekundÊre indekser osv.
- Hastigheten pÄ det nye lagringssystemet mÄ vÊre tilstrekkelig hÞy, bÄde ved datauthenting og ved behandling av transaksjoner, noe som effektivt betyr at akademisk strenge, universelle, men langsomme lÞsninger, som for eksempel .
- Automatisk skalering i sanntid.
- Bruken av vanlige, billige servere, uten behov for Ă„ kjĂžpe eksotisk maskinvare.
- Muligheten for utvikling av lagringssystemet av selskapets utviklere. Med andre ord, prioriteten ble gitt til egne eller Äpne lÞsninger, helst pÄ Java.
LĂžsninger, lĂžsninger
Ved Ă„ analysere mulige lĂžsninger kom vi til to mulige valg av arkitektur:
Den fÞrste er Ä ta en hvilken som helst SQL-server og implementere nÞdvendig feilresistens, skaleringsmekanisme, feilresistent klynge, konfliktlÞsing og distribuerte, pÄlitelige og hurtige ACID-transaksjoner. Vi vurderte dette alternativet som ganske uvanlig og arbeidskrevende.
Det andre alternativet er Ä ta et ferdig NoSQL-lager med implementert skalerbarhet, feilresistent klynge, konfliktlÞsing og implementere transaksjoner og SQL selv. Ved fÞrste Þyekast virker selv oppgaven med Ä implementere SQL, for ikke Ä snakke om ACID-transaksjoner, som en oppgave for flere Är. Men sÄ skjÞnte vi at settet av SQL-muligheter som vi bruker i praksis, er langt fra ANSI SQL sÄ langt som er langt fra ANSI SQL. Ved Ä se nÊrmere pÄ CQL, innsÄ vi at det er nÊrt det vi trenger.
Cassandra og CQL
SĂ„ hva er det interessante med Cassandra, hvilke muligheter har den?
For det fÞrste kan du opprette tabeller som stÞtter ulike datatyper, og du kan gjÞre SELECT eller UPDATE basert pÄ primÊrnÞkkelen.
CREATE TABLE photos (id bigint KEY, owner bigint,âŠ);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET ⊠WHERE id=?;For Ă„ sikre datakonsistens mellom replikaene, bruker Cassandra I det enkleste tilfellet betyr dette at nĂ„r man setter opp tre repliker av samme rad pĂ„ forskjellige noder i klyngen, anses skriveoperasjonen som vellykket hvis flertallet av nodene (dvs. to av tre) har bekreftet suksessen med denne skriveoperasjonen. Raddataene anses som konsistente hvis flertallet av nodene ble forespurt under lesingen og bekreftet dem. Dermed garanterer tilstedevĂŠrelsen av tre repliker fullstendig og umiddelbar datakonsistens ved feil av en node. Denne tilnĂŠrmingen har gjort det mulig for oss Ă„ implementere et enda mer pĂ„litelig system: alltid sende forespĂžrslene til alle tre repliker, og vente pĂ„ svar fra de to raskeste. Det forsinkede svaret fra den tredje replikaen blir i slike tilfeller forkastet. Den forsinkede noden kan ha alvorlige problemer â forsinkelser, sĂžppelinnsamling i JVM, direkte minnefrigivelse i linux-kjernen, maskinvarefeil, nettverksavbrudd. Men dette pĂ„virker ikke klientoperasjonen eller dataene pĂ„ noen mĂ„te.
TilnÊrmingen der vi henvender oss til tre noder og fÄr svar fra to, kalles : forespÞrselen til de ekstra replikaene sendes fÞr de faktisk 'faller av'.
En annen fordel med Cassandra er Batchlog â en mekanisme som garanterer enten fullstendig anvendelse eller fullstendig annullering av pakken med endringer du gjĂžr. Dette lar oss lĂžse A i ACID â atomisitet rett ut av boksen.
Det som er nÊrmest transaksjoner i Cassandra er det som kalles, men de er langt fra 'ekte' ACID-transaksjoner: i realiteten er dette muligheten til Ä gjÞre pÄ dataene til kun én oppfÞring, ved hjelp av konsensus via den tunge protokollen Paxos. Derfor er hastigheten pÄ slike transaksjoner ikke hÞy.
Hva vi manglet i Cassandra
SÄ vi mÄtte implementere ekte ACID-transaksjoner i Cassandra. Med dem kunne vi lett realisere to andre praktiske funksjoner fra klassiske DBMS: konsistente raske indekser, som ville gjÞre det mulig for oss Ä hente data ikke bare etter primÊrnÞkkelen, og en vanlig generator av monotont autoinkrementerende ID-er.
C*One
SÄ ble en ny DBMS fÞdt, C*Onesom bestÄr av tre typer servernoder:
- Lager â (nesten) standard Cassandra-servere ansvarlige for lagring av data pĂ„ lokale disker. Etter hvert som belastningen og datamengden Ăžker, kan antallet enkelt skaleres opp til titalls og hundrevis.
- Transaksjonskoordinatorer â sĂžrger for gjennomfĂžring av transaksjoner.
- Kunder â applikasjonsservere som utfĂžrer forretningsoperasjoner og initierer transaksjoner. Det kan vĂŠre tusenvis av slike kunder.

Servere av alle typer er en del av et felles klynge, bruker den interne meldingsprotokollen Cassandra for Ă„ kommunisere med hverandre og for Ă„ dele klyngeinformasjon. Gjennom Heartbeat fĂ„r serverne informasjon om gjensidige fiaskoer, opprettholder en felles dataskjema â tabeller, deres struktur og replikasjon; partisjoneringsskjema, topologi av klyngen, osv.
Kunder

I stedet for standarddrivere brukes Fat Client-modus. En slik node lagrer ikke data, men kan fungere som koordinator for forespÞrselene, det vil si at klienten selv utfÞrer koordinatorfunksjonen for forespÞrslene sine: spÞr replikene i lagringssystemet og lÞser konflikter. Dette er ikke bare mer pÄlitelig og raskere enn den standarddriveren som krever kommunikasjon med en ekstern koordinator, men det muliggjÞr ogsÄ administrasjon av forespÞrselsoverfÞring. Utenfor en Äpen transaksjon hos klienten sendes forespÞrslene til lagringene. Hvis klienten derimot har Äpnet en transaksjon, sendes alle forespÞrslene innenfor transaksjonen til transaksjonskoordinatoren.

Transaksjonskoordinator C*One
Koordinator â det vi har implementert for C*One fra bunnen av. Han er ansvarlig for administrasjon av transaksjoner, lĂ„ser og rekkefĂžlge av anvendelse av transaksjoner.
For hver hĂ„ndtert transaksjon genererer koordinatoren et tidsstempel: hvert pĂ„fĂžlgende er stĂžrre enn det for den foregĂ„ende transaksjonen. Siden konfliktlĂžsningen i Cassandra-systemet er basert pĂ„ tidsstempler (blant to konfliktfylte poster anses den som aktuell med det senere tidsstempelet), vil konflikten alltid bli lĂžst til fordel for den pĂ„fĂžlgende transaksjonen. Dermed har vi implementert â en billig mĂ„te Ă„ lĂžse konflikter i et distribuert system.
LÄser
For Ă„ sikre isolasjon valgte vi den enkleste metoden â pessimistiske lĂ„ser basert pĂ„ primĂŠrnĂžkkelen til oppfĂžringen. Med andre ord, i transaksjonen mĂ„ posten fĂžrst lĂ„ses, deretter leses, modifiseres og lagres. FĂžrst etter en vellykket commit kan posten lĂ„ses opp, slik at konkurrrerende transaksjoner kan bruke den.
Implementering av en slik lÄsing er enkel i et ufordelt miljÞ. I et fordelt system er det to hovedveier: enten implementere en distribuert lÄsing i klyngen, eller fordele transaksjoner slik at transaksjoner som involverer en enkelt post alltid hÄndteres av den samme koordinatoren.
Siden dataene i vÄrt tilfelle allerede er fordelt pÄ grupper av lokale transaksjoner i SQL, ble det besluttet Ä tildele koordinatorene grupper av lokale transaksjoner: én koordinator hÄndterer alle transaksjoner med en token fra 0 til 9, den andre - fra 10 til 19, og sÄ videre. Resultatet er at hver av koordinatorinstansene blir mester for sin gruppe av transaksjoner.
Da kan lÄsene implementeres i form av en enkel HashMap i minnet til koordinatoren.
Koordinatorfeil
Siden én koordinator utelukkende betjener en gruppe transaksjoner, er det avgjÞrende Ä raskt fastslÄ om den har sviktet, slik at gjenopptakelsen av transaksjonen kan skje innen tidsfristen. For Ä oppnÄ dette raskt og pÄlitelig, har vi anvendt en fullstendig koblet kvorum heartbeat-protokoll:
Hver datasenter har minst to koordinatornoder. Regelmessig sender hver koordinator heartbeat-meldinger til de andre koordinatorene for Ă„ informere dem om at den fungerer, samt hvilke heartbeat-meldinger fra andre koordinatorer i klyngen den sist mottok.

Ved Ä motta lignende informasjon fra de andre koordinatorene i sine heartbeat-meldinger, bestemmer hver koordinator hvilke noder i klyngen som fungerer, og hvilke som ikke gjÞr det, basert pÄ kvorumprinsippet: hvis node X har fÄtt bekreftelse fra flertallet av nodene i klyngen om at meldinger fra node Y er mottatt normalt, fungerer Y. Og omvendt, sÄ snart flertallet rapporterer om tap av meldinger fra node Y, har Y sviktet. Det er interessant at hvis kvorum informerer node X om at den ikke har fÄtt meldinger fra den, vil node X selv anse seg som sviktet.
Heartbeat-meldinger sendes med hÞy frekvens, omtrent 20 ganger i sekundet, med en periode pÄ 50 ms. I Java er det vanskelig Ä garantere applikasjonens respons innen 50 ms pÄ grunn av sammenlignbare pauser forÄrsaket av sÞppelsamleren. Vi klarte Ä oppnÄ en slik respons tid ved Ä bruke G1-sÞppelsamler, som gjÞr det mulig Ä spesifisere mÄl for varigheten av GC-pauser. Imidlertid, av og til, sjeldent, overskrider pausesÞppelsamlerens tid 50 ms, noe som kan fÞre til falsk feiloppdagelse. For Ä unngÄ dette, varsler ikke koordinatoren om feil pÄ en ekstern node ved tap av den fÞrste heartbeat-meldingen fra den, men kun hvis flere meldinger gÄr tapt pÄ rad. PÄ den mÄten klarte vi Ä oppnÄ oppdagelse av feil pÄ koordinatoren innen 200 ms.
Men det er ikke nok Ä raskt forstÄ hvilken node som har sluttet Ä fungere. Det mÄ gjÞres noe med det.
Redundans
Den klassiske ordningen innebÊrer i tilfelle av feil hos master Ä starte valg av en ny med hjelp av en av algoritmene. Imidlertid har slike algoritmer godt kjente problemer med konvergens over tid og varigheten av selve valgprosessen. Slike ekstra forsinkelser klarte vi Ä unngÄ ved Ä bruke en erstatningsordning for koordinatorer i et fullt sammenkoblet nettverk:

La oss si at vi vil utfĂžre en transaksjon i gruppe 50. Vi definerer pĂ„ forhĂ„nd erstatningsordningen, det vil si hvilke noder som skal utfĂžre transaksjoner for gruppe 50 i tilfelle hovedkoordinatoren feiler. MĂ„let vĂ„rt er Ă„ opprettholde systemets funksjonalitet ved et datacenters svikt. Vi definerer at det fĂžrste reserve vil vĂŠre en node fra et annet datacenter, og det andre reserve vil vĂŠre en node fra det tredje. Denne ordningen velges en gang og endres ikke sĂ„ lenge klustertopologien ikke endres, det vil si sĂ„ lenge det ikke legges til nye noder i den (noe som skjer svĂŠrt sjeldent). Rangeringen for valg av en ny aktiv master ved feil hos den gamle vil alltid vĂŠre slik: den aktive masteren blir det fĂžrste reserve, og hvis det ogsĂ„ har sluttet Ă„ fungere â det andre reserve.
Denne ordningen er mer pÄlitelig enn den universelle algoritmen, ettersom det er tilstrekkelig Ä fastslÄ faktum om feilen til den gamle for Ä aktivere den nye masteren.
Men klientene forstÄr ikke hvilken av mesterne som jobber akkurat nÄ? PÄ 50 ms er det umulig Ä sende informasjon til tusenvis av kunder. Det kan oppstÄ en situasjon der kunden sender en forespÞrsel om Ä Äpne en transaksjon, uten Ä vite at den mesteren allerede ikke fungerer, og forespÞrselen vil henge pÄ time-out. For Ä unngÄ dette, sender kundene spekulativt forespÞrselen om Ä Äpne transaksjonen til gruppens mester og begge hans reservemestrene, men det vil bare vÊre den aktive mesteren som svarer pÄ denne forespÞrselen i Þyeblikket. All videre kommunikasjon i forbindelse med transaksjonen vil kunden ha bare med den aktive mesteren.
Reserve-mestrene plasserer forespÞrslene de mottar om andre mestres transaksjoner i kÞen for ikke-fÞdte transaksjoner, hvor de oppbevares en stund. Hvis den aktive mesteren dÞr, sÄ behandler den nye mesteren forespÞrslene om Ä Äpne transaksjoner fra sin egen kÞ og svarer kunden. Hvis kunden allerede har klart Ä Äpne en transaksjon med den gamle mesteren, sÄ ignoreres det andre svaret (og denne transaksjonen vil Äpenbart ikke fullfÞres og mÄ gjentas av kunden).
Hvordan fungerer en transaksjon
La oss si at kunden har sendt en forespÞrsel til koordinatoren om Ä Äpne en transaksjon for en bestemt enhet med en viss primÊrnÞkkel. Koordinatoren lÄser denne enheten og plasserer den i blokkeringstabellen i minnet. Om nÞdvendig leser koordinatoren denne enheten fra lagringen og lagrer de mottatte dataene i transaksjonens tilstand i koordinatorens minne.

NĂ„r kunden Ăžnsker Ă„ endre dataene i transaksjonen, sender han en forespĂžrsel til koordinatoren om Ă„ modifisere enheten, som da plasserer de nye dataene i tabellen for transaksjonsstatus i minnet. PĂ„ dette punktet er oppfĂžrselen fullfĂžrt â ingen skriving til lagringen finner sted.

NÄr kunden anmoder om egne endrede data innenfor en aktiv transaksjon, handler koordinatoren slik:
- hvis ID allerede finnes i transaksjonen, tas dataene fra minnet;
- hvis ID ikke finnes i minnet, leses de manglende dataene fra nodene i lagringen, kombineres med de allerede tilgjengelige i minnet, og resultatet returneres til kunden.
Dermed kan kunden lese sine egne endringer, mens andre kunder ikke ser disse endringene, fordi de bare lagres i koordinatorens minne; i nodene i Cassandra finnes de ikke ennÄ.

NÄr en klient sender en commit, blir tilstanden som har vÊrt i minnet hos tjenesten, lagret av koordinatoren i logged batch, og i form av logged batch sendes den til Cassandra-lagerne. Lagringene gjÞr alt nÞdvendig for at denne pakken skal bli atomisk (fullstendig) anvendt, og returnerer et svar til koordinatoren, som deretter frigjÞr lÄser og bekrefter transaksjonens suksess til klienten.

Og for Ă„ rulle tilbake trenger koordinatoren bare Ă„ frigjĂžre minnet som ble brukt til transaksjonens tilstand.
Som et resultat av de ovennevnte forbedringene har vi implementert ACID-prinsippene:
- Atomisitet. Dette er en garanti for at ingen transaksjoner vil bli delvis bekreftet i systemet; enten blir alle deloperasjoner utfÞrt, eller ingen av dem. I vÄrt tilfelle overholdes dette prinsippet ved hjelp av logged batch i Cassandra.
- Konsistens. Hver vellykket transaksjon registrerer per definisjon bare gyldige resultater. Hvis det etter Äpning av en transaksjon og utfÞrelse av deler av operasjonene oppdages at resultatet er ugyldig, utfÞres en rulling tilbake.
- Isolasjon. NÄr en transaksjon utfÞres, mÄ parallelle transaksjoner ikke pÄvirke dens resultat. Konkurrerende transaksjoner er isolert ved hjelp av pessimistiske lÄser pÄ koordinatoren. For leseoperasjoner utenfor transaksjonen overholdes isolasjonsprinsippet pÄ nivÄet Read Committed.
- Motstandskraft. Uavhengig av problemer pĂ„ lavere nivĂ„er â strĂžmbrudd eller maskinvarefeil â mĂ„ endringene gjort av en vellykket avsluttet transaksjon forbli lagret etter at funksjonen er gjenopptatt.
Lesing etter indekser
La oss ta en enkel tabell:
CREATE TABLE photos (
id bigint primary key,
eier bigint,
modifisert timestamp,
âŠ)Den har en ID (primĂŠrnĂžkkel), eier og endringsdato. Vi mĂ„ gjĂžre en veldig enkel forespĂžrsel â velge data etter eier med endringsdato "de siste 24 timer".
SELECT *
WHERE owner=?
AND modified>?For at en slik forespÞrsel skal kjÞre raskt, mÄ vi i en klassisk SQL-database bygge et indeks pÄ kolonnene (owner, modified). Dette kan vi gjÞre ganske enkelt, siden vi nÄ har garantier for ACID!
Indekser i C*One
Det finnes en original tabell med bilder, hvor ID-en til posten er primĂŠrnĂžkkelen.

For indeksen lager C*One en ny tabell som er en kopi av originalen. NÞkkelen samsvarer med indeksuttrykket, samtidig som den ogsÄ inkluderer den primÊre nÞkkelen fra originaltabellen:

NĂ„ kan spĂžrringen "eier for de siste 24 timene" skrives om som select fra en annen tabell:
SELECT * FROM i1_test
WHERE owner=?
AND modified>?Konsistensen til den opprinnelige tabellen photos og indekstabellen i1 opprettholdes automatisk av koordinatoren. Ut fra bare dataskjemaet, nÄr det gjÞres endringer, genererer og husker koordinatoren endringer ikke bare i hovedtabellen, men ogsÄ endringer i kopiene. Ingen ekstra handlinger med indeks-tabellen utfÞres, logger leses ikke, lÄsing brukes ikke. Det vil si at tillegg av indekser nesten ikke bruker ressurser og har praktisk talt ingen innvirkning pÄ hastigheten av modifikasjoner.
Med ACID klarte vi Ä implementere indekser "som i SQL". De har konsistens, kan skaleres, jobber raskt, kan vÊre sammensatte og integrert i CQL-spÞrresprÄket. For Ä stÞtte indekser er det ikke nÞdvendig med endringer i anvendt kode. Alt er enkelt, som i SQL. Og det viktigste, indeksene pÄvirker ikke hastigheten pÄ utfÞrelsen av modifikasjoner i transaksjons tabellen.
Hva fikk vi?
Vi utviklet C*One for tre Är siden og satte det i produksjon.
Hva fikk vi ut av dette? La oss vurdere det ved hjelp av delsystemet for behandling og lagring av bilder, en av de viktigste datatypene i sosiale nettverk. Det handler ikke om selve bildene, men om all slags metadata. NÄ er det omtrent 20 milliarder slike poster i "Odnoklassniki", systemet hÄndterer 80 000 lesespÞrringer i sekundet, opptil 8 000 ACID-transaksjoner i sekundet knyttet til datamodifikasjoner.
Da vi brukte SQL med replication factor = 1 (men i RAID 10), ble metadataene om bildene lagret pÄ en hÞyt tilgjengelig klynge bestÄende av 32 maskiner med Microsoft SQL Server (pluss 11 backup-maskiner). Det ble ogsÄ avsatt 10 servere for Ä lagre sikkerhetskopier. Totalt 50 kostbare maskiner. Systemet fungerte med nominell belastning, uten margin.
Etter migrering til det nye systemet fikk vi replication factor = 3 â en kopi i hvert datasenter. Systemet bestĂ„r av 63 Cassandra-lagringsnoder og 6 koordinatormaskiner, totalt 69 servere. Disse maskinene er imidlertid betydelig billigere, den totale kostnaden er omtrent 30 % av kostnaden ved SQL-systemet. Samtidig holdes belastningen pĂ„ nivĂ„et 30 %.
Med innfĂžringen av C*One har bĂ„de forsinkelser redusert: i SQL tok skriveoperasjonen omtrent 4,5 ms. I C*One â omtrent 1,6 ms. Gjennomsnittlig transaksjonstid er mindre enn 40 ms, commiten utfĂžres pĂ„ 2 ms, lese- og skrivevarigheten er i gjennomsnitt 2 ms. 99. percentil er bare 3-3,1 ms, antall tidsavbrudd har redusert med 100 ganger â alt takket vĂŠre det brede bruken av spekulasjoner.
Per nÄ er det meste av SQL Server-nodene tatt ut av drift, nye produkter utvikles kun ved bruk av C*One. Vi har tilpasset C*One for drift i vÄrt sky. , noe som har gjort det mulig Ä akselerere opprettelsen av nye klynger, forenkle konfigurasjonen og automatisere driften. Uten kildekoden ville dette vÊrt betydelig mer komplisert og klÞnete.
Akkurat nĂ„ jobber vi med Ă„ flytte andre datalagre til skyen â men det er en helt annen historie.
Kilde: habr.com
