6.1 Battle of Abbreviations: BASE vs. SYRE
"I kemi måler pH den relative surhedsgrad af en vandig opløsning. pH-skalaen går fra 0 (stærkt sure stoffer) til 14 (stærkt basiske stoffer); rent vand ved 25°C har en pH-værdi på 7 og er neutralt. Dataingeniører har taget denne metafor til at sammenligne databaser vedrørende pålideligheden af transaktioner." Sandsynligvis var tanken denne: jo højere pH, dvs. jo tættere databasen er på "alkaline" ("BASE"), jo mindre pålidelige er transaktionerne. |
Populære relationelle databaser, såsom MySQL, dukkede kun op på basis af ACID. Men gennem de seneste ti år har de såkaldte NoSQL-databaser, som kombinerer flere vidt forskellige typer databaser under dette navn, klaret sig ret godt uden ACID. Faktisk er der et stort antal udviklere, der arbejder med NoSQL-databaser og er ligeglade med transaktioner og deres pålidelighed. Lad os se om de har ret.
Du kan ikke tale generelt om NoSQL-databasen, for det er bare en god abstraktion. NoSQL-databaser adskiller sig fra hinanden i designet af datalagringsundersystemer og endda i datamodeller: NoSQL er både dokumentorienteret CouchDB og graf Neo4J. Men hvis vi taler om dem i forbindelse med transaktioner, har de alle en tendens til at være ens på én ting: de giver begrænsede versioner af atomicitet og isolation og giver derfor ikke syregarantier. For at forstå, hvad dette betyder, lad os besvare spørgsmålet: hvad tilbyder de, hvis ikke ACID? Ikke noget?
Ikke rigtig. De skal jo ligesom relationelle databaser også sælge sig selv i en smuk pakke. Og de fandt på deres egen "kemiske" forkortelse - BASE.
6.2 BASE som antagonist
Og her vil jeg igen ikke gå i rækkefølge efter bogstaver, men jeg vil starte med det grundlæggende udtryk - konsistens. Jeg bliver nødt til at udjævne din genkendelseseffekt, fordi denne konsistens ikke har meget at gøre med konsistensen fra ACID. Problemet med begrebet konsistens er, at det bruges i alt for mange sammenhænge. Men denne konsistens har en meget bredere brugskontekst, og det er faktisk præcis den konsistens, der diskuteres, når man diskuterer distribuerede systemer.
De relationelle databaser, vi talte om ovenfor, giver forskellige niveauer af transaktionsisolering, og de strengeste af dem sikrer, at en transaktion ikke kan se ugyldige ændringer foretaget af en anden transaktion. Hvis du står ved kassen i en butik, og i det øjeblik hæves pengene til huslejen fra din konto, men transaktionen med overførsel af penge til huslejen mislykkes, og din konto vender tilbage til sin tidligere værdi (pengene er ikke debiteret), så vil din betalingstransaktion ved kassen ikke lægge mærke til alle disse bevægelser - trods alt gik den transaktion aldrig igennem, og baseret på kravet om transaktionsisolering kan dens midlertidige ændringer ikke bemærkes af andre transaktioner.
Mange NoSQL-databaser giver afkald på isolationsgarantien og tilbyder "eventuel konsistens", hvorved du til sidst vil se gyldige data, men der er en chance for, at din transaktion vil læse ugyldige værdier - det vil sige midlertidige, eller delvist opdaterede eller forældede. Det er muligt, at dataene bliver konsistente i "doven"-tilstand, når de læser ("dovent på læsetidspunktet").
NoSQL blev udtænkt som en database til analyse i realtid, og for at opnå større hastighed ofrede de konsistens. Og Eric Brewer, den samme fyr, der opfandt udtrykket BASE, formulerede den såkaldte "CAP-sætning", ifølge hvilken:
For enhver implementering af distribueret databehandling er det muligt at levere ikke mere end to af følgende tre egenskaber:
- datakonsistens ( konsistens ) - data på forskellige noder (instanser) modsiger ikke hinanden;
- tilgængelighed ( tilgængelighed ) - enhver anmodning til et distribueret system ender med et korrekt svar, men uden garanti for, at svarene fra alle systemknudepunkter er de samme;
- partitionstolerance (partitionstolerance ) - Selvom der ikke er nogen forbindelse mellem noderne, fortsætter de med at arbejde uafhængigt af hinanden.
Hvis du vil have en meget enkel forklaring på CAP, så er du her.
Der er meninger om, at CAP-sætningen ikke virker, og generelt er formuleret for abstrakt. På den ene eller anden måde nægter NoSQL-databaser ofte konsistens i sammenhæng med CAP-sætningen, som beskriver følgende situation: data er blevet opdateret i en klynge med flere instanser, men ændringerne er endnu ikke synkroniseret på alle instanser. Husk, jeg nævnte DynamoDB-eksemplet ovenfor, som fortalte mig: dine ændringer blev holdbare - her er en HTTP 200 til dig - men jeg så først ændringerne efter 10 sekunder? Et andet eksempel fra en udviklers dagligdag er DNS, Domain Name System. Hvis nogen ikke ved det, så er dette netop "ordbogen", der oversætter http(s) adresser til IP-adresser.
Den opdaterede DNS-record forplantes til serverne i henhold til caching-intervalindstillingerne - så opdateringer er ikke umiddelbart mærkbare. Nå, en lignende tidsmæssig inkonsistens (dvs. i sidste ende konsistens) kan ske med en relationel databaseklynge (f.eks. MySQL) - denne konsistens har trods alt intet at gøre med konsistens fra ACID. Derfor er det vigtigt at forstå, at i denne forstand er det usandsynligt, at SQL- og NoSQL-databaser er meget forskellige, når det kommer til flere instanser i en klynge.
Derudover kan ende-til-ende-konsistens betyde, at skriveanmodninger vil blive lavet i uorden: det vil sige, at alle data bliver skrevet, men den værdi, der til sidst vil blive modtaget, vil ikke være den sidste i skrivekøen.
Non-ACID NoSQL-databaser har en såkaldt "soft state" på grund af end-to-end-konsistensmodellen, hvilket betyder, at systemets tilstand kan ændre sig over tid, selv uden input. Men sådanne systemer stræber efter at give større tilgængelighed. At sørge for 100% tilgængelighed er ikke en triviel opgave, så vi taler om "grundlæggende tilgængelighed". Og sammen danner disse tre begreber: "basically available", "soft state" ("blød tilstand") og "eventuel konsistens" akronymet BASE.
For at være ærlig forekommer konceptet BASE for mig at være en mere tom markedsføringsindpakning end ACID – fordi det ikke giver noget nyt og på ingen måde kendetegner databasen. Og at vedhæfte etiketter (ACID, BASE, CAP) til visse databaser kan kun forvirre udviklere. Jeg besluttede at introducere dig til dette udtryk alligevel, fordi det er svært at omgå det, når du studerer databasen, men nu hvor du ved, hvad det er, vil jeg gerne have, at du glemmer det så hurtigt som muligt. Og lad os gå tilbage til begrebet isolation.
6.3 Så BASE-databaserne opfylder slet ikke ACID-kriterierne?
I det væsentlige, hvor ACID-databaser adskiller sig fra ikke-ACID'er er, at ikke-ACID'er faktisk giver afkald på isolering. Dette er vigtigt at forstå. Men det er endnu vigtigere at læse databasedokumentationen og teste dem, som fyrene fra Eremitageprojektet gør. Det er ikke så vigtigt, hvordan præcist skaberne af den eller den database kalder deres idé - ACID eller BASE, CAP eller ikke CAP. Det vigtige er, hvad netop denne eller hin database giver.
Hvis skaberne af databasen hævder, at den giver ACID-garantier, så er der sandsynligvis en grund til dette, men det er tilrådeligt at teste det selv for at forstå, om det er tilfældet og i hvilket omfang. Hvis de erklærer, at deres database ikke giver sådanne garantier, kan det betyde følgende ting:
-
DB giver ingen garanti for atomicitet. Mens nogle NoSQL-databaser tilbyder en separat API til atomoperationer (f.eks. DynamoDB);
- DB giver ingen isolationsgaranti. Det kan for eksempel betyde, at databasen ikke vil skrive dataene i den rækkefølge, de er skrevet i.
Hvad angår holdbarhedsgarantien, går mange databaser på kompromis på dette punkt af hensyn til ydeevnen. At skrive til disk er en for lang operation, og der er flere måder at løse dette problem på. Jeg vil ikke gå meget op i databaseteori, men for at du nogenlunde forstår, hvilken vej du skal se, vil jeg beskrive i generelle vendinger, hvordan forskellige databaser løser problemet med holdbarhed.
For at sammenligne forskellige databaser, skal du blandt andet vide, hvilke datastrukturer der ligger til grund for datalagrings- og genfindingsundersystemet i en bestemt database. Kort sagt: Forskellige databaser har forskellige implementeringer af indeksering - altså organisere adgangen til data. Nogle af dem giver dig mulighed for at skrive data hurtigere, andre - hurtigere at læse det. Men det kan ikke siges generelt, at nogle datastrukturer gør holdbarheden højere eller lavere.
6.4 hvordan forskellige databaser indekserer data, og hvordan dette påvirker holdbarheden og mere
Der er to hovedtilgange til at gemme og hente data.
Den nemmeste måde at gemme data på er at tilføje operationer til slutningen af filen på en log-lignende måde (det vil sige, at der altid forekommer en tilføjelseshandling): det er lige meget, om vi vil tilføje, ændre eller slette data - alt CRUD-operationer skrives simpelthen til loggen. At søge i loggen er ineffektivt, og det er her indekset kommer ind – en speciel datastruktur, der gemmer metadata om præcis, hvor dataene er gemt. Den enkleste indekseringsstrategi for logfiler er et hash-kort, der holder styr på nøgler og værdier. Værdierne vil være referencer til byte-offset for de data, der er skrevet inde i filen, som er loggen (loggen) og er gemt på disken. Denne datastruktur gemmes helt i hukommelsen, mens selve dataene er på disken, og kaldes et LSM-træ (log struktureret fletning).
Du undrede dig sikkert: Hvis vi hele tiden skriver vores operationer til journalen, så vil den vokse ublu? Ja, og derfor blev komprimeringsteknikken opfundet, som "rydder op" i dataene med en vis periodicitet, nemlig kun efterlader den mest relevante værdi for hver nøgle eller sletter den. Og hvis vi har mere end én log på disk, men flere, og de er alle sorteret, så får vi en ny datastruktur kaldet SSTable (“sorted string table”), og det vil uden tvivl forbedre vores ydeevne. Hvis vi vil sortere i hukommelsen, får vi en lignende struktur - den såkaldte MemTable, men med den er problemet, at hvis der sker et fatalt databasenedbrud, så er de data, der er skrevet sidst (placeret i MemTable, men endnu ikke skrevet til disk) er tabt. Rent faktisk,
En anden tilgang til indeksering er baseret på B-træer ("B-træer"). I et B-træ skrives data til disk i fast størrelse sider. Disse datablokke er ofte omkring 4 KB store og har nøgle-værdi-par sorteret efter nøgle. Én B-træ node er som en matrix med links til en række sider. Maks. antallet af links i et array kaldes forgreningsfaktoren. Hvert sideområde er en anden B-træ node med links til andre sideområder.
Til sidst vil du på arkniveau finde enkelte sider. Denne idé ligner pointere i programmeringssprog på lavt niveau, bortset fra at disse sidereferencer er gemt på disken i stedet for i hukommelsen. Når INSERT'er og DELETE'er forekommer i databasen, kan nogle knudepunkter opdeles i to undertræer for at matche forgreningsfaktoren. Hvis databasen af en eller anden grund svigter midt i processen, kan integriteten af dataene blive kompromitteret. For at forhindre dette i at ske, vedligeholder databaser, der bruger B-træer, en "write-ahead log" eller WAL, hvor hver enkelt transaktion registreres. Denne WAL bruges til at genoprette tilstanden for B-træet, hvis det er beskadiget. Og det ser ud til, at det er det, der gør databaser, der bruger B-træer, bedre med hensyn til holdbarhed. Men LSM-baserede databaser kan også opretholde en fil, der i det væsentlige udfører den samme funktion som WAL. Derfor vil jeg gentage, hvad jeg allerede har sagt, og måske mere end én gang: forstå driftsmekanismerne for den database, du har valgt.
Hvad der dog er sikkert ved B-træer, er, at de er gode til transaktionalitet: hver nøgle forekommer kun ét sted i indekset, mens journalførte lagerundersystemer kan have flere kopier af den samme nøgle i forskellige shards (f.eks. indtil næste komprimering udføres).
Indeksets design påvirker dog direkte databasens ydeevne. Med et LSM-træ er skrivninger til disk sekventielle, og B-træer forårsager flere tilfældige diskadgange, så skriveoperationer er hurtigere med LSM end med B-træer. Forskellen er især signifikant for magnetiske harddiske (HDD'er), hvor sekventiel skrivning er meget hurtigere end tilfældig skrivning. Læsning er langsommere på LSM-træer, fordi du skal kigge igennem flere forskellige datastrukturer og SS-tabeller, der er på forskellige stadier af komprimering. Mere detaljeret ser det sådan ud. Hvis vi laver en simpel databaseforespørgsel med LSM, vil vi først slå nøglen op i MemTable. Hvis det ikke er der, ser vi på den seneste SSTable; hvis ikke der, så ser vi på den næstsidste SSTable, og så videre. Hvis den ønskede nøgle ikke findes, så ved vi med LSM dette sidst. LSM træer bruges i fx: LevelDB, RocksDB, Cassandra og HBase.
Jeg beskriver det hele så detaljeret, så du forstår, at når du skal vælge en database, skal du overveje mange forskellige ting: forventer du for eksempel at skrive eller læse data mere. Og jeg har endnu ikke nævnt forskellen på datamodeller (skal du krydse dataene, som grafmodellen tillader det? Er der overhovedet sammenhænge mellem forskellige enheder i dine data – så kommer relationsdatabaser til undsætning?), og 2 typer dataskemaer - ved skrivning (som i mange NoSQL) og læsning (som i relationelle).
Hvis vi vender tilbage til aspektet af holdbarhed, så vil konklusionen være som følger: enhver database, der skriver til disk, uanset indekseringsmekanismerne, kan give gode garantier for holdbarheden af dine data, men du skal forholde dig til hver specifik database , hvad det præcist tilbyder.
6.5 Hvordan in-memory DB'er fungerer
Udover databaser, der skriver til disk, findes der i øvrigt også såkaldte "in-memory"-databaser, der primært arbejder med RAM. Kort sagt tilbyder in-memory databaser typisk lavere holdbarhed af hensyn til hurtigere skrive- og læsehastigheder, men dette kan være passende for nogle applikationer.
Faktum er, at RAM-hukommelse længe har været dyrere end diske, men på det seneste er det hurtigt begyndt at blive billigere, hvilket har givet anledning til en ny type database - hvilket er logisk i betragtning af hastigheden af at læse og skrive data fra RAM. Men du vil med rette spørge: hvad med datasikkerheden i disse databaser? Her skal du igen se på detaljerne i implementeringen. Generelt tilbyder udviklerne af sådanne databaser følgende mekanismer:
- Du kan bruge RAM drevet af batterier;
- Det er muligt at skrive ændringslogfiler til disken (noget i stil med WAL'erne nævnt ovenfor), men ikke selve dataene;
- Du kan med jævne mellemrum skrive kopier af databasetilstanden til disk (hvilket, uden at bruge andre muligheder, ikke giver en garanti, men kun forbedrer holdbarheden);
- Du kan replikere RAM-tilstanden til andre maskiner.
For eksempel mangler Redis-databasen i hukommelsen, som hovedsageligt bruges som beskedkø eller cache, holdbarhed fra ACID: den garanterer ikke, at en vellykket udført kommando vil blive lagret på disken, da Redis skyller data til disken (hvis du have persistens aktiveret) kun asynkront med jævne mellemrum.
Dette er dog ikke kritisk for alle applikationer: Jeg fandt et eksempel på EtherPad cooperative online editor, som skyllede hvert 1-2 sekund, og potentielt kunne brugeren miste et par bogstaver eller et ord, hvilket næppe var kritisk. Ellers, da in-memory-databaser er gode, fordi de leverer datamodeller, der ville være svære at implementere med diskindekser, kan Redis bruges til at implementere transaktioner - dens prioritetskø giver dig mulighed for at gøre dette.
GO TO FULL VERSION