6.1 Slag om afkortingen: BASE vs. ZUUR
"In de chemie meet pH de relatieve zuurgraad van een waterige oplossing. De pH-schaal loopt van 0 (sterk zure stoffen) tot 14 (sterk alkalische stoffen); zuiver water van 25°C heeft een pH van 7 en is neutraal. Data-engineers hebben deze metafoor gebruikt om databases te vergelijken met betrekking tot de betrouwbaarheid van transacties." Waarschijnlijk was het idee dit: hoe hoger de pH, d.w.z. hoe dichter de database bij "alkaline" ("BASE") is, hoe minder betrouwbaar de transacties. |
Populaire relationele databases, zoals MySQL, verschenen net op basis van ACID. Maar de afgelopen tien jaar hebben de zogenaamde NoSQL-databases, die verschillende zeer verschillende soorten databases onder deze naam combineren, het redelijk goed gedaan zonder ACID. In feite zijn er een groot aantal ontwikkelaars die met NoSQL-databases werken en helemaal niets geven om transacties en hun betrouwbaarheid. Laten we eens kijken of ze gelijk hebben.
Je kunt niet in het algemeen praten over de NoSQL-database, omdat het gewoon een goede abstractie is. NoSQL-databases verschillen van elkaar in het ontwerp van subsystemen voor gegevensopslag en zelfs in gegevensmodellen: NoSQL is zowel documentgeoriënteerde CouchDB als grafiek Neo4J. Maar als we erover praten in de context van transacties, lijken ze allemaal in één ding op elkaar: ze bieden beperkte versies van atomiciteit en isolatie, en bieden daarom geen ACID-garanties. Om te begrijpen wat dit betekent, laten we de vraag beantwoorden: wat bieden ze, zo niet ACID? Niets?
Niet echt. Ze moeten immers, net als relationele databases, zichzelf ook in een mooi pakket verkopen. En ze bedachten hun eigen "chemische" afkorting - BASE.
6.2 BASE als antagonist
En ook hier zal ik niet in volgorde van letters gaan, maar ik zal beginnen met de fundamentele term - consistentie. Ik zal je herkenningseffect moeten nivelleren, omdat deze consistentie weinig te maken heeft met de consistentie van ACID. Het probleem met de term consistentie is dat het in te veel contexten wordt gebruikt. Maar deze consistentie heeft een veel bredere gebruikscontext, en inderdaad, dit is precies de consistentie die wordt besproken bij het bespreken van gedistribueerde systemen.
De relationele databases waar we het hierboven over hadden, bieden verschillende niveaus van transactie-isolatie, en de strengste zorgen ervoor dat de ene transactie geen ongeldige wijzigingen kan zien die door een andere transactie zijn aangebracht. Als u in een winkel bij de kassa staat en op dat moment wordt het geld voor de huur van uw rekening afgeschreven, maar de transactie met het overmaken van geld voor de huur mislukt en uw rekening keert terug naar de vorige waarde (het geld is niet afgeschreven), dan zal uw betalingstransactie aan de kassa niet iedereen deze gebaren opmerken - die transactie is immers nooit doorgegaan en op basis van de vereiste van transactie-isolatie kunnen de tijdelijke wijzigingen ervan niet worden opgemerkt door andere transacties.
Veel NoSQL-databases zien af van de isolatiegarantie en bieden "eventuele consistentie" waarbij u uiteindelijk geldige gegevens zult zien, maar de kans bestaat dat uw transactie ongeldige waarden zal lezen - dat wil zeggen tijdelijk of gedeeltelijk bijgewerkt of verouderd. Het is mogelijk dat de gegevens tijdens het lezen consistent worden in de "luie" modus ("lui tijdens het lezen").
NoSQL is ontworpen als een database voor real-time analyse, en om meer snelheid te bereiken, offerden ze consistentie op. En Eric Brewer, dezelfde man die de term BASE bedacht, formuleerde de zogenaamde "CAP-stelling", volgens welke:
Voor elke implementatie van gedistribueerd computergebruik is het mogelijk om niet meer dan twee van de volgende drie eigenschappen op te geven:
- gegevensconsistentie ( consistentie ) - gegevens over verschillende knooppunten (instanties) spreken elkaar niet tegen;
- beschikbaarheid ( beschikbaarheid ) - elk verzoek aan een gedistribueerd systeem eindigt met een correct antwoord, maar zonder een garantie dat de antwoorden van alle systeemknooppunten hetzelfde zijn;
- partitietolerantie (partitietolerantie ) - Zelfs als er geen verbinding is tussen de knooppunten, blijven ze onafhankelijk van elkaar werken.
Als je een heel eenvoudige uitleg van CAP wilt, dan is het hier.
Er zijn meningen dat de CAP-stelling niet werkt en over het algemeen te abstract is geformuleerd. Op de een of andere manier weigeren NoSQL-databases vaak consistentie in de context van de CAP-stelling, die de volgende situatie beschrijft: gegevens zijn bijgewerkt in een cluster met verschillende instanties, maar de wijzigingen zijn nog niet op alle instanties gesynchroniseerd. Weet je nog dat ik het DynamoDB-voorbeeld hierboven noemde, dat me vertelde: je wijzigingen werden duurzaam - hier is een HTTP 200 voor jou - maar ik zag de wijzigingen pas na 10 seconden? Een ander voorbeeld uit het dagelijkse leven van een ontwikkelaar is DNS, het Domain Name System. Als iemand het niet weet, dan is dit precies het “woordenboek” dat http(s)-adressen vertaalt naar IP-adressen.
Het bijgewerkte DNS-record wordt doorgegeven aan de servers volgens de caching-intervalinstellingen - dus updates zijn niet onmiddellijk merkbaar. Welnu, een vergelijkbare tijdelijke inconsistentie (d.w.z. uiteindelijk consistentie) kan gebeuren met een relationele databasecluster (bijvoorbeeld MySQL) - deze consistentie heeft tenslotte niets te maken met consistentie van ACID. Daarom is het belangrijk om te begrijpen dat SQL- en NoSQL-databases in deze zin waarschijnlijk niet erg verschillen als het gaat om meerdere instanties in een cluster.
Bovendien kan end-to-end-consistentie betekenen dat schrijfverzoeken niet in de juiste volgorde worden gedaan: dat wil zeggen dat alle gegevens worden geschreven, maar de waarde die uiteindelijk wordt ontvangen, zal niet de laatste in de schrijfwachtrij zijn.
Niet-ACID NoSQL-databases hebben een zogenaamde "soft state" vanwege het end-to-end consistentiemodel, wat betekent dat de status van het systeem in de loop van de tijd kan veranderen, zelfs zonder input. Maar dergelijke systemen streven naar een grotere toegankelijkheid. Het bieden van 100% beschikbaarheid is geen triviale taak, we spreken dan ook over “basisbeschikbaarheid”. En samen vormen deze drie concepten: “basical available”, “soft state” (“soft state”) en “eventual consistentie” het acroniem BASE.
Om eerlijk te zijn, lijkt het concept van BASE me een leeg marketingomhulsel dan ACID - omdat het niets nieuws geeft en de database op geen enkele manier kenmerkt. En het toevoegen van labels (ACID, BASE, CAP) aan bepaalde databases kan ontwikkelaars alleen maar in verwarring brengen. Ik heb besloten om je toch kennis te laten maken met deze term, omdat het moeilijk is om hem te omzeilen bij het bestuderen van de database, maar nu je weet wat het is, wil ik dat je hem zo snel mogelijk vergeet. En laten we teruggaan naar het concept van isolatie.
6.3 De BASE-databases voldoen dus helemaal niet aan de ACID-criteria?
Waar ACID-databases in wezen verschillen van niet-ACID's, is dat niet-ACID's eigenlijk afzien van isolatie. Dit is belangrijk om te begrijpen. Maar het is nog belangrijker om de databasedocumentatie te lezen en te testen zoals de jongens van het Hermitage-project dat doen. Het is niet zo belangrijk hoe de makers van deze of gene database hun geesteskind noemen - ACID of BASE, CAP of niet CAP. Het belangrijkste is wat deze of gene database precies biedt.
Als de makers van de database beweren dat deze ACID-garanties biedt, dan is daar waarschijnlijk een reden voor, maar het is raadzaam om het zelf te testen om te begrijpen of dit zo is en in welke mate. Als ze verklaren dat hun database dergelijke garanties niet biedt, kan dat de volgende dingen betekenen:
-
De DB geeft geen garantie op atomiciteit. Hoewel sommige NoSQL-databases een aparte API bieden voor atomaire bewerkingen (bijv. DynamoDB);
- De DB biedt geen isolatiegarantie. Dit kan bijvoorbeeld betekenen dat de database de gegevens niet wegschrijft in de volgorde waarin ze geschreven zijn.
Wat de duurzaamheidsgarantie betreft, sluiten veel databases op dit punt compromissen omwille van de prestaties. Schrijven naar schijf is een te lange operatie en er zijn verschillende manieren om dit probleem op te lossen. Ik wil niet veel ingaan op de databasetheorie, maar zodat je ongeveer begrijpt hoe je moet kijken, zal ik in algemene termen beschrijven hoe verschillende databases het probleem met duurzaamheid oplossen.
Om onder andere verschillende databases te kunnen vergelijken, moet u weten welke datastructuren ten grondslag liggen aan het dataopslag- en opvraagsubsysteem van een bepaalde database. Kortom: verschillende databases hebben verschillende implementaties van indexering - dat wil zeggen het organiseren van toegang tot gegevens. Met sommige kunt u sneller gegevens schrijven, met andere sneller om ze te lezen. Maar in het algemeen kan niet gezegd worden dat sommige datastructuren de duurzaamheid hoger of lager maken.
6.4 hoe verschillende databases gegevens indexeren, en hoe dit de duurzaamheid beïnvloedt, en meer
Er zijn twee hoofdbenaderingen voor het opslaan en ophalen van gegevens.
De gemakkelijkste manier om gegevens op te slaan, is door bewerkingen aan het einde van het bestand toe te voegen op een logachtige manier (dat wil zeggen, er vindt altijd een toevoegbewerking plaats): het maakt niet uit of we gegevens willen toevoegen, wijzigen of verwijderen - allemaal CRUD-bewerkingen worden gewoon naar het logboek geschreven. Het doorzoeken van het logboek is inefficiënt, en daar komt de index om de hoek kijken - een speciale gegevensstructuur die metagegevens opslaat over waar de gegevens precies zijn opgeslagen. De eenvoudigste indexeringsstrategie voor logboeken is een hash-kaart die sleutels en waarden bijhoudt. De waarden zijn verwijzingen naar de byte-offset voor de gegevens die in het bestand zijn geschreven, dat is het logboek (logboek) en wordt op schijf opgeslagen. Deze gegevensstructuur wordt volledig in het geheugen opgeslagen, terwijl de gegevens zelf op schijf staan, en wordt een LSM-boom (log structured merge) genoemd.
Je hebt je waarschijnlijk afgevraagd: als we onze operaties de hele tijd naar het tijdschrift schrijven, zal het dan exorbitant groeien? Ja, en daarom is de verdichtingstechniek uitgevonden, die de gegevens met enige regelmaat "opruimt", namelijk alleen de meest relevante waarde voor elke sleutel achterlaten, of deze verwijderen. En als we meer dan één inlogschijf hebben, maar meerdere, en ze zijn allemaal gesorteerd, dan krijgen we een nieuwe gegevensstructuur genaamd SSTable ("gesorteerde tekenreekstabel"), en dit zal ongetwijfeld onze prestaties verbeteren. Als we in het geheugen willen sorteren, krijgen we een vergelijkbare structuur - de zogenaamde MemTable, maar daarmee is het probleem dat als er een fatale databasecrash optreedt, de gegevens die als laatste zijn geschreven (in MemTable staan, maar nog niet naar schijf) verloren gaan. Eigenlijk,
Een andere benadering van indexering is gebaseerd op B-trees (“B-trees”). In een B-tree worden gegevens naar schijf geschreven in pagina's met een vast formaat. Deze gegevensblokken zijn vaak ongeveer 4 KB groot en hebben sleutel-waardeparen gesorteerd op sleutel. Eén B-tree-knooppunt is als een array met links naar een reeks pagina's. Max. het aantal links in een array wordt de vertakkingsfactor genoemd. Elk paginabereik is een ander B-tree-knooppunt met koppelingen naar andere paginabereiken.
Uiteindelijk vindt u op bladniveau afzonderlijke pagina's. Dit idee is vergelijkbaar met verwijzingen in programmeertalen op laag niveau, behalve dat deze paginaverwijzingen op schijf worden opgeslagen in plaats van in het geheugen. Wanneer INSERT's en DELETE's in de database voorkomen, kan een knooppunt in twee subbomen worden gesplitst om overeen te komen met de vertakkingsfactor. Als de database om welke reden dan ook midden in het proces uitvalt, kan de integriteit van de gegevens in gevaar komen. Om dit te voorkomen, houden databases die gebruikmaken van B-trees een "write-ahead log", of WAL, waarin elke afzonderlijke transactie wordt vastgelegd. Deze WAL wordt gebruikt om de status van de B-tree te herstellen als deze beschadigd is. En het lijkt erop dat dit is wat databases die B-trees gebruiken beter maakt in termen van duurzaamheid. Maar op LSM gebaseerde databases kunnen ook een bestand onderhouden dat in wezen dezelfde functie vervult als WAL. Daarom zal ik herhalen wat ik al heb gezegd, en misschien meer dan eens: begrijp de werkingsmechanismen van de database die u hebt gekozen.
Wat zeker is aan B-trees, is echter dat ze goed zijn voor de transactie: elke sleutel komt slechts op één plaats in de index voor, terwijl gejournaliseerde opslagsubsystemen meerdere kopieën van dezelfde sleutel in verschillende shards kunnen hebben (bijvoorbeeld totdat de volgende verdichting wordt uitgevoerd).
Het ontwerp van de index is echter rechtstreeks van invloed op de prestaties van de database. Met een LSM-boom is schrijven naar schijf sequentieel en B-bomen veroorzaken meerdere willekeurige schijftoegangen, dus schrijfbewerkingen zijn sneller met LSM dan met B-bomen. Het verschil is vooral significant voor magnetische harde schijven (HDD's), waar sequentiële schrijfbewerkingen veel sneller zijn dan willekeurige schrijfbewerkingen. Lezen gaat langzamer op LSM-bomen omdat je door verschillende datastructuren en SS-tabellen moet kijken die zich in verschillende stadia van verdichting bevinden. Meer in detail ziet het er zo uit. Als we met LSM een eenvoudige databasequery maken, zoeken we eerst de sleutel op in de MemTable. Als die er niet is, kijken we naar de meest recente SSTable; zo niet, dan kijken we naar de voorlaatste SSTable, enzovoort. Als de gevraagde sleutel niet bestaat, weten we dit met LSM als laatste. LSM-bomen worden gebruikt in bijvoorbeeld: LevelDB, RocksDB, Cassandra en HBase.
Ik beschrijf het allemaal zo gedetailleerd dat je begrijpt dat je bij het kiezen van een database met veel verschillende dingen rekening moet houden: verwacht je bijvoorbeeld meer data te schrijven of te lezen. En dan heb ik het nog niet gehad over het verschil in datamodellen (moet je de data doorkruisen, zoals het grafiekmodel toelaat? Zijn er überhaupt relaties tussen verschillende eenheden in je data - dan komen relationele databases te hulp?), en 2 soorten gegevensschema's - bij schrijven (zoals in veel NoSQL) en lezen (zoals in relationeel).
Als we terugkeren naar het aspect duurzaamheid, dan is de conclusie als volgt: elke database die naar schijf schrijft, ongeacht de indexeringsmechanismen, kan goede garanties bieden voor de duurzaamheid van uw gegevens, maar u moet met elke specifieke database te maken hebben , wat het precies biedt.
6.5 Hoe DB's in het geheugen werken
Trouwens, naast databases die naar schijf schrijven, zijn er ook zogenaamde "in-memory" databases die voornamelijk met RAM werken. Kortom, databases in het geheugen bieden doorgaans een lagere duurzaamheid vanwege hogere schrijf- en leessnelheden, maar dit kan geschikt zijn voor sommige toepassingen.
Het feit is dat RAM-geheugen lange tijd duurder is geweest dan schijven, maar de laatste tijd is het snel goedkoper geworden, wat aanleiding heeft gegeven tot een nieuw type database - wat logisch is gezien de snelheid van het lezen en schrijven van gegevens uit RAM. Maar je vraagt je terecht af: hoe zit het met de dataveiligheid van deze databases? Ook hier moet u kijken naar de details van de implementatie. Over het algemeen bieden de ontwikkelaars van dergelijke databases de volgende mechanismen:
- U kunt RAM gebruiken dat wordt aangedreven door batterijen;
- Het is mogelijk om wijzigingslogboeken naar schijf te schrijven (zoiets als de hierboven genoemde WAL's), maar niet de gegevens zelf;
- U kunt periodiek kopieën van de databasestatus naar schijf schrijven (wat, zonder andere opties te gebruiken, geen garantie geeft, maar alleen de duurzaamheid verbetert);
- U kunt de status van RAM repliceren naar andere machines.
De in-memory Redis-database, die voornamelijk wordt gebruikt als berichtenwachtrij of cache, mist bijvoorbeeld duurzaamheid door ACID: het garandeert niet dat een succesvol uitgevoerde opdracht op schijf wordt opgeslagen, aangezien Redis gegevens naar schijf spoelt (als u hebben persistentie ingeschakeld) alleen asynchroon, met regelmatige tussenpozen.
Dit is echter niet essentieel voor alle toepassingen: ik vond een voorbeeld van de EtherPad-coöperatieve online-editor, die elke 1-2 seconden doorspoelde, en mogelijk kon de gebruiker een paar letters of een woord verliezen, wat nauwelijks kritiek was. Anders, omdat in-memory databases goed zijn omdat ze gegevensmodellen bieden die moeilijk te implementeren zijn met schijfindexen, kan Redis worden gebruikt om transacties te implementeren - de prioriteitswachtrij stelt u in staat dit te doen.