NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
Tot voor kort bewaarde Odnoklassniki ongeveer 50 TB aan gegevens die in realtime werden verwerkt in SQL Server. Voor een dergelijk volume is het vrijwel onmogelijk om snelle, betrouwbare en zelfs fouttolerante toegang tot het datacenter te bieden met behulp van een SQL DBMS. Meestal wordt in dergelijke gevallen een van de NoSQL-opslagplaatsen gebruikt, maar niet alles kan naar NoSQL worden overgedragen: sommige entiteiten vereisen ACID-transactiegaranties.

Dit leidde ons tot het gebruik van NewSQL-opslag, dat wil zeggen een DBMS dat fouttolerantie, schaalbaarheid en prestaties van NoSQL-systemen biedt, maar tegelijkertijd de ACID-garanties behoudt die bekend zijn bij klassieke systemen. Er zijn maar weinig werkende industriële systemen van deze nieuwe klasse, dus hebben we een dergelijk systeem zelf geïmplementeerd en commercieel in gebruik genomen.

Hoe het werkt en wat er is gebeurd - lees onder de snede.

Tegenwoordig bestaat het maandelijkse publiek van Odnoklassniki uit meer dan 70 miljoen unieke bezoekers. Wij Wij staan ​​in de top vijf grootste sociale netwerken ter wereld, en een van de twintig sites waarop gebruikers de meeste tijd doorbrengen. De OK-infrastructuur kan zeer hoge belastingen aan: meer dan een miljoen HTTP-verzoeken/sec per front. Delen van een servervloot van meer dan 8000 stuks bevinden zich dicht bij elkaar - in vier datacenters in Moskou, waardoor een netwerklatentie van minder dan 1 ms tussen hen mogelijk is.

We gebruiken Cassandra sinds 2010, te beginnen met versie 0.6. Tegenwoordig zijn er enkele tientallen clusters in bedrijf. Het snelste cluster verwerkt meer dan 4 miljoen bewerkingen per seconde, en het grootste cluster slaat 260 TB op.

Dit zijn echter allemaal gewone NoSQL-clusters die voor opslag worden gebruikt zwak gecoördineerd gegevens. We wilden de belangrijkste consistente opslag, Microsoft SQL Server, vervangen die al sinds de oprichting van Odnoklassniki wordt gebruikt. De opslag bestond uit meer dan 300 SQL Server Standard Edition-machines, die 50 TB aan gegevens bevatten - bedrijfsentiteiten. Deze gegevens worden gewijzigd als onderdeel van ACID-transacties en vereisen hoge consistentie.

Om gegevens over SQL Server-knooppunten te distribueren, hebben we zowel verticaal als horizontaal gebruikt verdeling (scherven). Historisch gezien gebruikten we een eenvoudig data-sharding-schema: elke entiteit was gekoppeld aan een token - een functie van de entiteit-ID. Entiteiten met hetzelfde token werden op dezelfde SQL-server geplaatst. De hoofd-detailrelatie werd zo geïmplementeerd dat de tokens van de hoofd- en onderliggende records altijd overeenkwamen en zich op dezelfde server bevonden. In een sociaal netwerk worden vrijwel alle records namens de gebruiker gegenereerd - wat betekent dat alle gebruikersgegevens binnen één functioneel subsysteem op één server worden opgeslagen. Dat wil zeggen dat bij een zakelijke transactie bijna altijd tabellen van één SQL-server betrokken waren, waardoor het mogelijk werd om gegevensconsistentie te garanderen met behulp van lokale ACID-transacties, zonder de noodzaak om traag en onbetrouwbaar gedistribueerde ACID-transacties.

Dankzij sharding en om SQL te versnellen:

  • We gebruiken geen beperkingen voor buitenlandse sleutels, omdat bij het delen van de entiteits-ID zich mogelijk op een andere server bevindt.
  • We gebruiken geen opgeslagen procedures en triggers vanwege de extra belasting van de DBMS CPU.
  • We gebruiken geen JOIN's vanwege al het bovenstaande en veel willekeurige leesbewerkingen vanaf schijf.
  • Buiten een transactie gebruiken we het Read Uncomshed-isolatieniveau om impasses te verminderen.
  • Wij voeren alleen korte transacties uit (gemiddeld korter dan 100 ms).
  • We gebruiken geen UPDATE en DELETE met meerdere rijen vanwege het grote aantal deadlocks; we werken slechts één record tegelijk bij.
  • We voeren altijd alleen query's uit op indexen - een query met een volledig tabelscanplan betekent voor ons dat de database overbelast raakt en ervoor zorgt dat deze mislukt.

Dankzij deze stappen konden we bijna maximale prestaties uit SQL-servers halen. De problemen werden echter steeds talrijker. Laten we ze bekijken.

Problemen met SQL

  • Omdat we zelfgeschreven sharding gebruikten, werd het toevoegen van nieuwe shards handmatig gedaan door beheerders. Al die tijd waren schaalbare gegevensreplica's geen serviceverzoeken.
  • Naarmate het aantal records in de tabel groeit, neemt de snelheid van invoeging en wijziging af; bij het toevoegen van indexen aan een bestaande tabel daalt de snelheid met een factor; het maken en opnieuw aanmaken van indexen vindt plaats met downtime.
  • Het in productie hebben van een kleine hoeveelheid Windows voor SQL Server maakt het beheer van de infrastructuur lastig

Maar het grootste probleem is

fout tolerantie

De klassieke SQL-server heeft een slechte fouttolerantie. Stel dat u slechts één databaseserver heeft, en deze valt eens in de drie jaar uit. Gedurende deze tijd is de site 20 minuten offline, wat acceptabel is. Als je 64 servers hebt, dan ligt de site eens in de drie weken uit de lucht. En als je 200 servers hebt, dan werkt de site niet wekelijks. Dit is het probleem.

Wat kan er gedaan worden om de fouttolerantie van een SQL-server te verbeteren? Wikipedia nodigt ons uit om te bouwen zeer beschikbaar cluster: waarbij er een back-up is voor het geval een van de componenten uitvalt.

Dit vereist een vloot van dure apparatuur: talrijke duplicaties, optische vezels, gedeelde opslag en het opnemen van een reserve werkt niet betrouwbaar: ongeveer 10% van de schakelingen eindigt met het uitvallen van het back-upknooppunt als een trein achter het hoofdknooppunt.

Maar het grootste nadeel van een dergelijk hoog beschikbaar cluster is dat er geen beschikbaarheid is als het datacenter waarin het zich bevindt uitvalt. Odnoklassniki heeft vier datacenters en we moeten ervoor zorgen dat er in één daarvan een volledige storing optreedt.

Hiervoor zouden we kunnen gebruiken Multimaster replicatie ingebouwd in SQL Server. Deze oplossing is veel duurder vanwege de kosten van software en heeft te kampen met bekende problemen met replicatie: onvoorspelbare transactievertragingen bij synchrone replicatie en vertragingen bij het toepassen van replicaties (en als gevolg daarvan verloren wijzigingen) bij asynchrone replicatie. Het impliciete handmatige conflictoplossing maakt deze optie voor ons volstrekt niet van toepassing.

Al deze problemen vereisten een radicale oplossing en we begonnen ze in detail te analyseren. Hier moeten we kennis maken met wat SQL Server voornamelijk doet: transacties.

Eenvoudige transactie

Laten we eens kijken naar de eenvoudigste transactie, vanuit het standpunt van een toegepaste SQL-programmeur: een foto toevoegen aan een album. Albums en foto's worden op verschillende platen opgeslagen. Het album heeft een openbare fototeller. Vervolgens wordt zo’n transactie opgedeeld in de volgende stappen:

  1. We vergrendelen het album met de sleutel.
  2. Maak een vermelding in de fototabel.
  3. Als de foto een openbare status heeft, voeg dan een openbare fototeller toe aan het album, werk de record bij en voer de transactie uit.

Of in pseudocode:

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

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

TX.commit();

We zien dat het meest voorkomende scenario voor een zakelijke transactie is om gegevens uit de database in het geheugen van de applicatieserver te lezen, iets te veranderen en de nieuwe waarden weer op te slaan in de database. Meestal werken we bij een dergelijke transactie verschillende entiteiten en meerdere tabellen bij.

Bij het uitvoeren van een transactie kan gelijktijdige wijziging van dezelfde gegevens uit een ander systeem plaatsvinden. Antispam kan bijvoorbeeld besluiten dat de gebruiker op de een of andere manier verdacht is en daarom mogen alle foto's van de gebruiker niet langer openbaar zijn. Ze moeten ter moderatie worden verzonden, wat betekent dat photo.status naar een andere waarde moet worden gewijzigd en de bijbehorende tellers moeten worden uitgeschakeld. Het is duidelijk dat als deze operatie plaatsvindt zonder garanties op atomaire toepassing en isolatie van concurrerende wijzigingen, zoals in ACID, dan zal het resultaat niet zijn wat nodig is: de fototeller geeft de verkeerde waarde weer, of niet alle foto's worden ter moderatie verzonden.

Gedurende het hele bestaan ​​van Odnoklassniki is er veel vergelijkbare code geschreven, die verschillende bedrijfsentiteiten binnen één transactie manipuleert. Gebaseerd op de ervaring met migraties naar NoSQL van Eventuele consistentie We weten dat de grootste uitdaging (en tijdinvestering) voortkomt uit het ontwikkelen van code om de gegevensconsistentie te behouden. Daarom hebben we overwogen dat de belangrijkste vereiste voor de nieuwe opslag de voorziening zou zijn voor echte ACID-transacties voor applicatielogica.

Andere, niet minder belangrijke, vereisten waren:

  • Als het datacenter uitvalt, moet zowel lezen als schrijven naar de nieuwe opslag beschikbaar zijn.
  • Het handhaven van de huidige ontwikkelingssnelheid. Dat wil zeggen dat bij het werken met een nieuwe repository de hoeveelheid code ongeveer hetzelfde moet zijn; het zou niet nodig moeten zijn om iets aan de repository toe te voegen, algoritmen te ontwikkelen voor het oplossen van conflicten, het onderhouden van secundaire indexen, enz.
  • De snelheid van de nieuwe opslag moest behoorlijk hoog zijn, zowel bij het lezen van gegevens als bij het verwerken van transacties, wat feitelijk betekende dat academisch rigoureuze, universele, maar trage oplossingen, zoals bijvoorbeeld, niet toepasbaar waren tweefasige commits.
  • Automatische on-the-fly schaling.
  • Gebruikmakend van reguliere goedkope servers, zonder de noodzaak om exotische hardware aan te schaffen.
  • Mogelijkheid tot opslagontwikkeling door bedrijfsontwikkelaars. Met andere woorden: er werd prioriteit gegeven aan propriëtaire of open source-oplossingen, bij voorkeur in Java.

Beslissingen beslissingen

Bij het analyseren van mogelijke oplossingen kwamen we tot twee mogelijke architectuurkeuzes:

De eerste is het nemen van een willekeurige SQL-server en het implementeren van de vereiste fouttolerantie, schaalmechanisme, failovercluster, conflictoplossing en gedistribueerde, betrouwbare en snelle ACID-transacties. We beoordeelden deze optie als zeer niet-triviaal en arbeidsintensief.

De tweede optie is om een ​​kant-en-klare NoSQL-opslag te nemen met geïmplementeerde schaling, een failovercluster, conflictoplossing en zelf transacties en SQL te implementeren. Op het eerste gezicht lijkt zelfs de taak van het implementeren van SQL, om nog maar te zwijgen van ACID-transacties, een taak die jaren zal duren. Maar toen beseften we dat de SQL-functies die we in de praktijk gebruiken net zo ver verwijderd zijn van ANSI SQL Cassandra CQL verre van ANSI SQL. Toen we CQL nog nader bekeken, beseften we dat het vrij dicht in de buurt kwam van wat we nodig hadden.

Cassandra en CQL

Wat is er interessant aan Cassandra, welke mogelijkheden heeft het?

Ten eerste kunt u hier tabellen maken die verschillende gegevenstypen ondersteunen; u kunt SELECT of UPDATE uitvoeren op de primaire sleutel.

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

Om de consistentie van replicagegevens te garanderen, gebruikt Cassandra quorumbenadering. In het eenvoudigste geval betekent dit dat wanneer drie replica's van dezelfde rij op verschillende knooppunten van het cluster worden geplaatst, het schrijven als succesvol wordt beschouwd als de meerderheid van de knooppunten (dat wil zeggen twee van de drie) het succes van deze schrijfbewerking bevestigt. . De rijgegevens worden als consistent beschouwd als bij het lezen de meerderheid van de knooppunten is ondervraagd en bevestigd. Met drie replica's is dus volledige en onmiddellijke gegevensconsistentie gegarandeerd als één knooppunt uitvalt. Dankzij deze aanpak konden we een nog betrouwbaarder schema implementeren: stuur altijd verzoeken naar alle drie de replica's, wachtend op een reactie van de twee snelste. De late reactie van de derde replica wordt in dit geval weggegooid. Een knooppunt dat te laat reageert, kan ernstige problemen hebben: remmen, garbagecollection in de JVM, direct geheugenherstel in de Linux-kernel, hardwarestoring, ontkoppeling met het netwerk. Dit heeft echter op geen enkele wijze invloed op de bedrijfsvoering of gegevens van de opdrachtgever.

De aanpak waarbij we contact maken met drie knooppunten en een reactie van twee ontvangen, wordt genoemd speculatie: een verzoek om extra replica's wordt verzonden nog voordat deze “eraf valt”.

Een ander voordeel van Cassandra is Batchlog, een mechanisme dat ervoor zorgt dat een reeks wijzigingen die u aanbrengt, volledig of helemaal niet wordt toegepast. Hierdoor kunnen we A in ACID - atomiciteit out-of-the-box oplossen.

Het dichtst bij transacties in Cassandra zijn de zogenaamde “lichtgewicht transacties". Maar het zijn verre van ‘echte’ ACID-transacties: in feite is dit een kans om dit te doen CAS op gegevens uit slechts één record, met behulp van consensus met behulp van het zwaargewicht Paxos-protocol. Daarom is de snelheid van dergelijke transacties laag.

Wat we misten in Cassandra

We moesten dus echte ACID-transacties in Cassandra implementeren. Hiermee konden we gemakkelijk twee andere handige kenmerken van klassieke DBMS implementeren: consistente snelle indexen, waardoor we dataselecties niet alleen op basis van de primaire sleutel konden uitvoeren, en een reguliere generator van monotone, automatisch oplopende ID's.

Kegel

Zo werd een nieuw DBMS geboren Kegel, bestaande uit drie soorten serverknooppunten:

  • Opslag – (bijna) standaard Cassandra-servers die verantwoordelijk zijn voor het opslaan van gegevens op lokale schijven. Naarmate de belasting en het volume van de gegevens toeneemt, kan de hoeveelheid ervan eenvoudig worden opgeschaald naar tientallen en honderden.
  • Transactiecoördinatoren - zorgen voor de uitvoering van transacties.
  • Clients zijn applicatieservers die bedrijfsactiviteiten uitvoeren en transacties initiëren. Er kunnen duizenden van dergelijke klanten zijn.

NewSQL = NoSQL+ACID

Alle soorten servers maken deel uit van een gemeenschappelijk cluster en gebruiken het interne Cassandra-berichtenprotocol om met elkaar te communiceren praatjes voor het uitwisselen van clusterinformatie. Met Heartbeat leren servers over wederzijdse fouten, onderhouden ze één gegevensschema: tabellen, hun structuur en replicatie; partitieschema, clustertopologie, enz.

Cliënten

NewSQL = NoSQL+ACID

In plaats van standaardstuurprogramma's wordt de Fat Client-modus gebruikt. Zo'n knooppunt slaat geen gegevens op, maar kan optreden als coördinator voor de uitvoering van verzoeken, dat wil zeggen dat de klant zelf optreedt als coördinator van zijn verzoeken: het bevraagt ​​opslagreplica's en lost conflicten op. Dit is niet alleen betrouwbaarder en sneller dan de standaarddriver, waarvoor communicatie met een coördinator op afstand vereist is, maar u kunt ook de verzending van verzoeken controleren. Buiten een op de client geopende transactie worden verzoeken naar repository's verzonden. Als de klant een transactie heeft geopend, worden alle verzoeken binnen de transactie naar de transactiecoördinator gestuurd.
NewSQL = NoSQL+ACID

C*One Transactiecoördinator

De coördinator is iets dat we vanaf nul voor C*One hebben geïmplementeerd. Het is verantwoordelijk voor het beheer van transacties, vergrendelingen en de volgorde waarin transacties worden toegepast.

Voor elke afgehandelde transactie genereert de coördinator een tijdstempel: elke volgende transactie is groter dan de vorige transactie. Omdat het conflictoplossingssysteem van Cassandra gebaseerd is op tijdstempels (van twee conflicterende records wordt degene met de laatste tijdstempel als actueel beschouwd), zal het conflict altijd worden opgelost ten gunste van de daaropvolgende transactie. Zo hebben wij geïmplementeerd Lamport-horloge - een goedkope manier om conflicten in een gedistribueerd systeem op te lossen.

Sloten

Om isolatie te garanderen, hebben we besloten de eenvoudigste methode te gebruiken: pessimistische vergrendelingen op basis van de primaire sleutel van het record. Met andere woorden: bij een transactie moet een record eerst worden vergrendeld en pas daarna worden gelezen, gewijzigd en opgeslagen. Pas na een succesvolle commit kan een record worden ontgrendeld, zodat concurrerende transacties er gebruik van kunnen maken.

Het implementeren van een dergelijke vergrendeling is eenvoudig in een niet-gedistribueerde omgeving. In een gedistribueerd systeem zijn er twee hoofdopties: ofwel gedistribueerde vergrendeling op het cluster implementeren, ofwel transacties distribueren zodat transacties met hetzelfde record altijd door dezelfde coördinator worden afgehandeld.

Omdat in ons geval de gegevens al zijn verdeeld over groepen lokale transacties in SQL, werd besloten om lokale transactiegroepen toe te wijzen aan coördinatoren: de ene coördinator voert alle transacties uit met tokens van 0 tot 9, de tweede - met tokens van 10 tot 19, enzovoort. Als gevolg hiervan wordt elk van de coördinatorinstanties de meester van de transactiegroep.

Vervolgens kunnen sloten worden geïmplementeerd in de vorm van een banale HashMap in het geheugen van de coördinator.

Coördinator mislukt

Omdat één coördinator uitsluitend een groep transacties bedient, is het van groot belang om snel het feit van de mislukking ervan vast te stellen, zodat de tweede poging om de transactie uit te voeren zal mislukken. Om dit snel en betrouwbaar te maken, hebben we een volledig verbonden quorum-hearbeat-protocol gebruikt:

Elk datacenter beschikt over ten minste twee coördinatorknooppunten. Periodiek stuurt elke coördinator een hartslagbericht naar de andere coördinatoren en informeert hen over zijn werking, evenals welke hartslagberichten hij de laatste keer heeft ontvangen van welke coördinatoren in het cluster.

NewSQL = NoSQL+ACID

Door vergelijkbare informatie van anderen te ontvangen als onderdeel van hun hartslagberichten, beslist elke coördinator voor zichzelf welke clusterknooppunten functioneren en welke niet, geleid door het quorumprincipe: als knooppunt X informatie heeft ontvangen van de meerderheid van de knooppunten in het cluster over de normale ontvangst van berichten van knooppunt Y, dan werkt Y. En andersom: zodra de meerderheid ontbrekende berichten van knooppunt Y meldt, heeft Y geweigerd. Het is merkwaardig dat als het quorum knooppunt X informeert dat het niet langer berichten van het quorum ontvangt, knooppunt X zichzelf als gefaald zal beschouwen.

Heartbeat-berichten worden met een hoge frequentie verzonden, ongeveer 20 keer per seconde, met een periode van 50 ms. In Java is het moeilijk om een ​​applicatierespons binnen 50 ms te garanderen vanwege de vergelijkbare duur van de pauzes veroorzaakt door de garbage collector. We hebben deze responstijd kunnen bereiken met behulp van de G1-garbagecollector, waarmee we een doel kunnen specificeren voor de duur van GC-pauzes. Soms overschrijden de collectorpauzes echter zelden meer dan 50 ms, wat kan leiden tot een valse foutdetectie. Om dit te voorkomen rapporteert de coördinator geen storing van een extern knooppunt wanneer het eerste hartslagbericht daarvan verdwijnt, maar alleen als er meerdere achter elkaar zijn verdwenen. Zo zijn we erin geslaagd om in 200 een storing van het coördinatorknooppunt te detecteren. Mevr.

Maar het is niet genoeg om snel te begrijpen welk knooppunt niet meer functioneert. We moeten hier iets aan doen.

езервирование

Het klassieke schema houdt in dat, in het geval van een masterstoring, een nieuwe verkiezing wordt gestart met behulp van een van in de mode universeel algoritmen. Dergelijke algoritmen hebben echter bekende problemen met tijdconvergentie en de lengte van het verkiezingsproces zelf. We hebben dergelijke extra vertragingen kunnen voorkomen door gebruik te maken van een vervangingsschema voor coördinatoren in een volledig verbonden netwerk:

NewSQL = NoSQL+ACID

Laten we zeggen dat we een transactie in groep 50 willen uitvoeren. Laten we vooraf het vervangingsschema bepalen, dat wil zeggen welke knooppunten transacties in groep 50 zullen uitvoeren in het geval van een storing van de hoofdcoördinator. Ons doel is om de systeemfunctionaliteit te behouden in het geval van een datacenterstoring. Laten we vaststellen dat de eerste reserve een knooppunt van een ander datacenter zal zijn, en de tweede reserve een knooppunt van een derde. Dit schema wordt één keer geselecteerd en verandert niet totdat de topologie van het cluster verandert, dat wil zeggen totdat er nieuwe knooppunten binnenkomen (wat zeer zelden gebeurt). De procedure voor het selecteren van een nieuwe actieve master als de oude uitvalt, is altijd als volgt: de eerste reserve wordt de actieve master, en als deze niet meer functioneert, wordt de tweede reserve de actieve master.

Dit schema is betrouwbaarder dan het universele algoritme, omdat het voor het activeren van een nieuwe master voldoende is om het falen van de oude te bepalen.

Maar hoe zullen klanten begrijpen welke meester nu werkt? Het is onmogelijk om in 50 ms informatie naar duizenden klanten te sturen. Er is een situatie mogelijk waarin een cliënt een verzoek verzendt om een ​​transactie te openen, nog niet wetende dat deze master niet meer functioneert, en het verzoek een time-out krijgt. Om dit te voorkomen, sturen klanten speculatief een verzoek om een ​​transactie te openen naar de groepsmaster en zijn beide reserves tegelijk, maar alleen degene die op dat moment de actieve master is, zal op dit verzoek reageren. De klant zal alle daaropvolgende communicatie binnen de transactie alleen met de actieve master uitvoeren.

Back-upmasters plaatsen ontvangen verzoeken voor transacties die niet van hen zijn in de wachtrij van ongeboren transacties, waar ze enige tijd worden bewaard. Als de actieve master sterft, verwerkt de nieuwe master verzoeken om transacties uit zijn wachtrij te openen en reageert op de client. Als de klant al een transactie met de oude master heeft geopend, wordt het tweede antwoord genegeerd (en het is duidelijk dat een dergelijke transactie niet zal worden voltooid en door de klant zal worden herhaald).

Hoe de transactie werkt

Laten we zeggen dat een klant een verzoek naar de coördinator heeft gestuurd om een ​​transactie te openen voor die en die entiteit met die en die primaire sleutel. De coördinator vergrendelt deze entiteit en plaatst deze in de vergrendeltabel in het geheugen. Indien nodig leest de coördinator deze entiteit uit de opslag en slaat de resulterende gegevens in een transactiestatus op in het geheugen van de coördinator.

NewSQL = NoSQL+ACID

Wanneer een klant gegevens in een transactie wil wijzigen, stuurt hij een verzoek naar de coördinator om de entiteit te wijzigen, en de coördinator plaatst de nieuwe gegevens in de transactiestatustabel in het geheugen. Hiermee is de opname voltooid - er wordt geen opname gemaakt in de opslag.

NewSQL = NoSQL+ACID

Wanneer een klant in het kader van een actieve transactie zelf gewijzigde gegevens opvraagt, handelt de coördinator als volgt:

  • als de ID al in de transactie zit, worden de gegevens uit het geheugen gehaald;
  • als de ID zich niet in het geheugen bevindt, worden de ontbrekende gegevens uit de opslagknooppunten gelezen, gecombineerd met de gegevens die al in het geheugen aanwezig zijn, en wordt het resultaat aan de klant gegeven.

De client kan dus zijn eigen wijzigingen lezen, maar andere clients zien deze wijzigingen niet, omdat ze alleen in het geheugen van de coördinator worden opgeslagen; ze bevinden zich nog niet in de Cassandra-knooppunten.

NewSQL = NoSQL+ACID

Wanneer de client een commit verzendt, wordt de status die zich in het geheugen van de service bevond, door de coördinator opgeslagen in een gelogde batch en als een gelogde batch naar Cassandra-opslag verzonden. De winkels doen er alles aan om ervoor te zorgen dat dit pakket atomair (volledig) wordt toegepast en sturen een reactie terug naar de coördinator, die de sloten vrijgeeft en het succes van de transactie aan de klant bevestigt.

NewSQL = NoSQL+ACID

En om terug te draaien hoeft de coördinator alleen het geheugen vrij te maken dat wordt ingenomen door de transactiestatus.

Als gevolg van bovenstaande verbeteringen hebben we de ACID-principes geïmplementeerd:

  • Atomiciteit. Dit is een garantie dat geen enkele transactie gedeeltelijk in het systeem wordt geregistreerd; alle subbewerkingen zullen worden voltooid, of er zal geen enkele worden voltooid. We houden ons aan dit principe door middel van geregistreerde batches in Cassandra.
  • Consistentie. Elke succesvolle transactie registreert per definitie alleen geldige resultaten. Als na het openen van een transactie en het uitvoeren van een deel van de bewerkingen wordt ontdekt dat het resultaat ongeldig is, wordt een rollback uitgevoerd.
  • Isolatie. Wanneer een transactie wordt uitgevoerd, mogen gelijktijdige transacties de uitkomst ervan niet beïnvloeden. Concurrerende transacties worden geïsoleerd met behulp van pessimistische sloten op de coördinator. Voor leesbewerkingen buiten een transactie wordt het isolatieprincipe in acht genomen op het leescommittatieniveau.
  • stabiliteit. Ongeacht problemen op lagere niveaus (systeemuitval, hardwarestoring) moeten wijzigingen die door een succesvol voltooide transactie zijn aangebracht, behouden blijven wanneer de activiteiten worden hervat.

Lezen via indexen

Laten we een eenvoudige tabel nemen:

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

Het heeft een ID (primaire sleutel), eigenaar en wijzigingsdatum. U moet een heel eenvoudig verzoek indienen: selecteer gegevens over de eigenaar met de wijzigingsdatum “voor de laatste dag”.

SELECT *
WHERE owner=?
AND modified>?

Om een ​​dergelijke query snel te kunnen verwerken, moet u in een klassiek SQL DBMS een index opbouwen op basis van kolommen (eigenaar, aangepast). Dit kan vrij eenvoudig, omdat we nu ACID-garanties hebben!

Indexen in C*One

Er is een brontabel met foto's waarin het record-ID de primaire sleutel is.

NewSQL = NoSQL+ACID

Voor een index maakt C*One een nieuwe tabel die een kopie is van het origineel. De sleutel is dezelfde als de indexexpressie en bevat ook de primaire sleutel van het record uit de brontabel:

NewSQL = NoSQL+ACID

Nu kan de zoekopdracht voor “eigenaar voor de laatste dag” worden herschreven als een selectie uit een andere tabel:

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

De consistentie van de gegevens in de brontabelfoto's en de indextabel i1 wordt automatisch bijgehouden door de coördinator. Wanneer een wijziging alleen op basis van het gegevensschema wordt ontvangen, genereert en slaat de coördinator een wijziging op, niet alleen in de hoofdtabel, maar ook in kopieën. Er worden geen aanvullende acties uitgevoerd op de indextabel, logboeken worden niet gelezen en er worden geen vergrendelingen gebruikt. Dat wil zeggen dat het toevoegen van indexen vrijwel geen bronnen verbruikt en vrijwel geen effect heeft op de snelheid van het toepassen van wijzigingen.

Met behulp van ACID konden we SQL-achtige indexen implementeren. Ze zijn consistent, schaalbaar, snel, configureerbaar en ingebouwd in de CQL-querytaal. Er zijn geen wijzigingen in de applicatiecode vereist om indexen te ondersteunen. Alles is net zo eenvoudig als in SQL. En het allerbelangrijkste: indexen hebben geen invloed op de uitvoeringssnelheid van wijzigingen aan de oorspronkelijke transactietabel.

Wat is er gebeurd

We hebben C*One drie jaar geleden ontwikkeld en commercieel in gebruik genomen.

Wat hebben we uiteindelijk gekregen? Laten we dit evalueren aan de hand van het voorbeeld van het subsysteem voor fotoverwerking en -opslag, een van de belangrijkste soorten gegevens in een sociaal netwerk. We hebben het niet over de lichamen van de foto's zelf, maar over allerlei soorten meta-informatie. Nu Odnoklassniki ongeveer 20 miljard van dergelijke records heeft, verwerkt het systeem 80 leesverzoeken per seconde, tot 8 ACID-transacties per seconde die verband houden met gegevenswijziging.

Toen we SQL met replicatiefactor = 1 gebruikten (maar in RAID 10), werd de foto-meta-informatie opgeslagen op een zeer beschikbaar cluster van 32 machines waarop Microsoft SQL Server draaide (plus 11 back-ups). Er werden ook 10 servers toegewezen voor het opslaan van back-ups. In totaal 50 dure auto's. Tegelijkertijd werkte het systeem met nominale belasting, zonder reserve.

Na de migratie naar het nieuwe systeem ontvingen we replicatiefactor = 3 - een kopie in elk datacenter. Het systeem bestaat uit 63 Cassandra-opslagknooppunten en 6 coördinatormachines, voor een totaal van 69 servers. Maar deze machines zijn veel goedkoper; hun totale kosten bedragen ongeveer 30% van de kosten van een SQL-systeem. Tegelijkertijd wordt de belasting op 30% gehouden.

Met de introductie van C*One nam ook de latentie af: in SQL duurde een schrijfbewerking ongeveer 4,5 ms. In C*One - ongeveer 1,6 ms. De transactieduur bedraagt ​​gemiddeld minder dan 40 ms, de commit is in 2 ms voltooid, de lees- en schrijfduur bedraagt ​​gemiddeld 2 ms. 99e percentiel - slechts 3-3,1 ms, het aantal time-outs is met 100 keer afgenomen - allemaal als gevolg van het wijdverbreide gebruik van speculatie.

Inmiddels zijn de meeste SQL Server-nodes buiten gebruik gesteld; nieuwe producten worden uitsluitend met C*One ontwikkeld. We hebben C*One aangepast om in onze cloud te werken één wolk, waardoor het mogelijk werd de implementatie van nieuwe clusters te versnellen, de configuratie te vereenvoudigen en de werking te automatiseren. Zonder de broncode zou dit veel moeilijker en omslachtiger zijn.

Nu zijn we bezig om onze overige opslagfaciliteiten naar de cloud over te brengen, maar dat is een heel ander verhaal.

Bron: www.habr.com

Voeg een reactie