6.1 Battle of Abbreviations: BASE vs. SYRE

"I kjemi måler pH den relative surheten til en vandig løsning. pH-skalaen går fra 0 (sterkt sure stoffer) til 14 (sterkt alkaliske stoffer); rent vann ved 25°C har en pH på 7 og er nøytralt.

Dataingeniører har tatt denne metaforen for å sammenligne databaser angående påliteligheten til transaksjoner."

Sannsynligvis var tanken denne: jo høyere pH, dvs. jo nærmere databasen er "alkalisk" ("BASE"), jo mindre pålitelige er transaksjonene.

Populære relasjonsdatabaser, som MySQL, dukket opp bare på grunnlag av ACID. Men i løpet av de siste ti årene har de såkalte NoSQL-databasene, som kombinerer flere svært forskjellige typer databaser under dette navnet, klart seg ganske bra uten ACID. Faktisk er det et stort antall utviklere som jobber med NoSQL-databaser og ikke bryr seg i det hele tatt om transaksjoner og deres pålitelighet. La oss se om de har rett.

Du kan ikke snakke generelt om NoSQL-databasen, fordi det bare er en god abstraksjon. NoSQL-databaser skiller seg fra hverandre i utformingen av datalagringsundersystemer, og til og med i datamodeller: NoSQL er både dokumentorientert CouchDB og graf Neo4J. Men hvis vi snakker om dem i sammenheng med transaksjoner, har de alle en tendens til å være like på én ting: de gir begrensede versjoner av atomitet og isolasjon, og gir derfor ikke ACID-garantier. For å forstå hva dette betyr, la oss svare på spørsmålet: hva tilbyr de, hvis ikke ACID? Ingenting?

Ikke egentlig. Tross alt må de, i likhet med relasjonsdatabaser, også selge seg selv i en vakker pakke. Og de kom opp med sin egen «kjemiske» forkortelse – BASE.

6.2 BASE som antagonist

Og her vil jeg ikke gå i rekkefølge av bokstaver, men jeg vil begynne med det grunnleggende begrepet - konsistens. Jeg må utjevne gjenkjenningseffekten din, fordi denne konsistensen har lite å gjøre med konsistensen fra ACID. Problemet med begrepet konsistens er at det brukes i for mange sammenhenger. Men denne konsistensen har en mye bredere brukskontekst, og dette er faktisk akkurat den konsistensen som diskuteres når man diskuterer distribuerte systemer.

Relasjonsdatabasene vi snakket om ovenfor gir ulike nivåer av transaksjonsisolasjon, og de strengeste av dem sikrer at en transaksjon ikke kan se ugyldige endringer gjort av en annen transaksjon. Hvis du står ved kassen i en butikk, og i det øyeblikket trekkes pengene for husleien fra kontoen din, men transaksjonen med overføring av penger til husleien mislykkes og kontoen din går tilbake til sin tidligere verdi (pengene er ikke debitert), så vil ikke betalingstransaksjonen din i kassen legge merke til alle disse bevegelsene - tross alt gikk den transaksjonen aldri gjennom, og basert på kravet om transaksjonsisolering, kan dens midlertidige endringer ikke merkes av andre transaksjoner.

Mange NoSQL-databaser gir avkall på isolasjonsgarantien og tilbyr "eventuell konsistens" der du til slutt vil se gyldige data, men det er en sjanse for at transaksjonen vil lese ugyldige verdier - det vil si midlertidig, eller delvis oppdatert eller utdatert. Det er mulig at dataene blir konsistente i "lat"-modus ved lesing ("lat ved lesetid").

NoSQL ble tenkt som en database for sanntidsanalyse, og for å oppnå større hastighet ofret de konsistens. Og Eric Brewer, den samme fyren som laget begrepet BASE, formulerte det såkalte "CAP-teoremet", ifølge hvilket:

For enhver implementering av distribuert databehandling er det ikke mulig å gi mer enn to av følgende tre egenskaper:

  • datakonsistens ( konsistens ) - data på forskjellige noder (forekomster) motsier ikke hverandre;
  • tilgjengelighet ( tilgjengelighet ) - enhver forespørsel til et distribuert system ender med et korrekt svar, men uten garanti for at svarene til alle systemnoder er de samme;
  • partisjonstoleranse (partisjonstoleranse ) - Selv om det ikke er noen forbindelse mellom nodene, fortsetter de å jobbe uavhengig av hverandre.

Hvis du vil ha en veldig enkel forklaring på CAP, så her.

Det er oppfatninger om at CAP-teoremet ikke fungerer, og generelt er for abstrakt formulert. På en eller annen måte nekter NoSQL-databaser ofte konsistens i sammenheng med CAP-teoremet, som beskriver følgende situasjon: data har blitt oppdatert i en klynge med flere instanser, men endringene er ennå ikke synkronisert på alle instanser. Husk at jeg nevnte DynamoDB-eksemplet ovenfor, som fortalte meg: endringene dine ble holdbare - her er en HTTP 200 for deg - men jeg så endringene først etter 10 sekunder? Et annet eksempel fra hverdagen til en utvikler er DNS, Domain Name System. Hvis noen ikke vet, så er dette akkurat "ordboken" som oversetter http(e)-adresser til IP-adresser.

Den oppdaterte DNS-posten spres til serverne i henhold til innstillingene for hurtigbufferintervall - så oppdateringer er ikke umiddelbart merkbare. Vel, en lignende tidsmessig inkonsistens (dvs. til slutt konsistens) kan skje med en relasjonsdatabaseklynge (f.eks. MySQL) - denne konsistensen har tross alt ingenting å gjøre med konsistens fra ACID. Derfor er det viktig å forstå at i denne forstand er det usannsynlig at SQL- og NoSQL-databaser er veldig forskjellige når det kommer til flere instanser i en klynge.

I tillegg kan ende-til-ende-konsistens bety at skriveforespørsler vil bli gjort ute av drift: det vil si at alle data vil bli skrevet, men verdien som til slutt vil bli mottatt vil ikke være den siste i skrivekøen.

Non-ACID NoSQL-databaser har en såkalt "soft state" på grunn av ende-til-ende-konsistensmodellen, som betyr at tilstanden til systemet kan endre seg over tid, selv uten input. Men slike systemer streber etter å gi større tilgjengelighet. Å sørge for 100 % tilgjengelighet er ikke en triviell oppgave, så vi snakker om "grunnleggende tilgjengelighet". Og sammen danner disse tre konseptene: «i utgangspunktet tilgjengelig», «myk tilstand» («myk tilstand») og «eventuell konsistens» akronymet BASE.

For å være ærlig virker konseptet BASE for meg å være en mer tom markedsføringsinnpakning enn ACID – fordi det ikke gir noe nytt og ikke karakteriserer databasen på noen måte. Og å feste etiketter (ACID, BASE, CAP) til visse databaser kan bare forvirre utviklere. Jeg bestemte meg for å introdusere deg for dette begrepet uansett, fordi det er vanskelig å omgå det når du studerer databasen, men nå som du vet hva det er, vil jeg at du skal glemme det så snart som mulig. Og la oss gå tilbake til begrepet isolasjon.

6.3 Så BASE-databasene oppfyller ikke ACID-kriteriene i det hele tatt?

I hovedsak, der ACID-databaser skiller seg fra ikke-ACID-er, er at ikke-ACID-er faktisk gir avkall på isolasjon. Dette er viktig å forstå. Men det er enda viktigere å lese databasedokumentasjonen og teste dem slik gutta fra Hermitage-prosjektet gjør. Det er ikke så viktig hvordan nøyaktig skaperne av denne eller den databasen kaller hjernebarnet deres - ACID eller BASE, CAP eller ikke CAP. Det viktige er hva akkurat denne eller den databasen gir.

Hvis skaperne av databasen hevder at den gir ACID-garantier, så er det sannsynligvis en grunn til dette, men det er lurt å teste den selv for å forstå om det er slik og i hvilken grad. Hvis de erklærer at databasen deres ikke gir slike garantier, kan dette bety følgende:

  • DB gir ingen garanti for atomitet. Mens noen NoSQL-databaser tilbyr et eget API for atomoperasjoner (f.eks. DynamoDB);

  • DB gir ingen isolasjonsgaranti. Dette kan for eksempel bety at databasen ikke vil skrive dataene i den rekkefølgen de ble skrevet.

Når det gjelder holdbarhetsgarantien, går mange databaser på akkord på dette punktet for ytelsens skyld. Å skrive til disk er en for lang operasjon, og det er flere måter å løse dette problemet på. Jeg ønsker ikke å gå så mye inn i databaseteori, men for at du i grove trekk forstår hvilken vei du skal se, vil jeg beskrive i generelle vendinger hvordan ulike databaser løser problemet med holdbarhet.

For å sammenligne ulike databaser, må du blant annet vite hvilke datastrukturer som ligger til grunn for datalagrings- og gjenfinningsundersystemet til en bestemt database. Kort sagt: ulike databaser har ulike implementeringer av indeksering – det vil si organisere tilgang til data. Noen av dem lar deg skrive data raskere, andre - raskere å lese dem. Men det kan ikke sies generelt at noen datastrukturer gjør holdbarheten høyere eller lavere.

6.4 hvordan ulike databaser indekserer data, og hvordan dette påvirker holdbarheten, med mer

Det er to hovedtilnærminger for å lagre og hente data.

Den enkleste måten å lagre data på er å legge til operasjoner til slutten av filen på en logg-lignende måte (det vil si at en append-operasjon alltid forekommer): det spiller ingen rolle om vi vil legge til, endre eller slette data - alt CRUD-operasjoner skrives ganske enkelt til loggen. Å søke i loggen er ineffektivt, og det er her indeksen kommer inn – en spesiell datastruktur som lagrer metadata om nøyaktig hvor dataene er lagret. Den enkleste indekseringsstrategien for logger er et hash-kart som holder styr på nøkler og verdier. Verdiene vil være referanser til byteforskyvningen for dataene som er skrevet inne i filen, som er loggen (loggen) og er lagret på disken. Denne datastrukturen lagres i sin helhet i minnet, mens selve dataene ligger på disk, og kalles et LSM-tre (log structured merge).

Du lurte sikkert på: hvis vi hele tiden skriver operasjonene våre til journalen, vil den vokse ublu? Ja, og derfor ble komprimeringsteknikken oppfunnet, som "rydder opp" dataene med en viss periodisitet, nemlig bare etterlater den mest relevante verdien for hver nøkkel, eller sletter den. Og hvis vi har mer enn én logg på disk, men flere, og de er alle sortert, vil vi få en ny datastruktur kalt SSTable ("sortert strengtabell"), og dette vil utvilsomt forbedre ytelsen vår. Hvis vi ønsker å sortere i minnet, vil vi få en lignende struktur - den såkalte MemTable, men med den er problemet at dersom det oppstår et fatalt databasekrasj, så er dataene skrevet sist (ligger i MemTable, men ennå ikke skrevet til disk) er tapt. Faktisk,

En annen tilnærming til indeksering er basert på B-trær ("B-trær"). I et B-tre skrives data til disk i fast størrelse sider. Disse datablokkene er ofte rundt 4 KB store og har nøkkel-verdi-par sortert etter nøkkel. Én B-tre node er som en matrise med lenker til en rekke sider. Maks. antall lenker i en matrise kalles grenfaktoren. Hvert sideområde er en annen B-tre node med lenker til andre sideområder.

Etter hvert vil du på arknivå finne enkeltsider. Denne ideen ligner på pekere i programmeringsspråk på lavt nivå, bortsett fra at disse sidereferansene er lagret på disken i stedet for i minnet. Når INSERT- og DELETE-er forekommer i databasen, kan noen noder dele seg i to undertrær for å matche forgreningsfaktoren. Hvis databasen svikter av en eller annen grunn midt i prosessen, kan integriteten til dataene bli kompromittert. For å forhindre at dette skjer, opprettholder databaser som bruker B-trær en "write-ahead-logg", eller WAL, der hver enkelt transaksjon blir registrert. Denne WAL brukes til å gjenopprette tilstanden til B-treet hvis det er ødelagt. Og det ser ut til at det er dette som gjør databaser som bruker B-trær bedre når det gjelder holdbarhet. Men LSM-baserte databaser kan også opprettholde en fil som i hovedsak utfører samme funksjon som WAL. Derfor vil jeg gjenta det jeg allerede har sagt, og kanskje mer enn en gang: forstå driftsmekanismene til databasen du har valgt.

Det som er sikkert med B-trær, er imidlertid at de er gode for transaksjonalitet: hver nøkkel forekommer kun på ett sted i indeksen, mens journalførte lagringsundersystemer kan ha flere kopier av samme nøkkel i forskjellige shards (for eksempel inntil neste komprimering utføres).

Imidlertid påvirker utformingen av indeksen direkte ytelsen til databasen. Med et LSM-tre er skriving til disk sekvensielle, og B-trær forårsaker flere tilfeldige disktilganger, så skriveoperasjoner er raskere med LSM enn med B-trær. Forskjellen er spesielt betydelig for magnetiske harddisker (HDDer), der sekvensiell skriving er mye raskere enn tilfeldig skriving. Lesing er tregere på LSM-trær fordi du må se gjennom flere forskjellige datastrukturer og SS-tabeller som er på forskjellige stadier av komprimering. Mer detaljert ser det slik ut. Hvis vi lager en enkel databasespørring med LSM, vil vi først slå opp nøkkelen i MemTable. Hvis den ikke er der, ser vi på den nyeste SSTable; hvis ikke der, så ser vi på den nest siste SSTable, og så videre. Hvis den forespurte nøkkelen ikke eksisterer, vil vi med LSM vite dette sist. LSM-trær brukes i for eksempel: LevelDB, RocksDB, Cassandra og HBase.

Jeg beskriver det hele så detaljert slik at du forstår at når du velger en database, må du vurdere mange forskjellige ting: forventer du for eksempel å skrive eller lese data mer. Og jeg har ennå ikke nevnt forskjellen i datamodeller (må du krysse dataene, slik grafmodellen tillater det? Er det noen relasjoner mellom ulike enheter i dataene dine i det hele tatt - da kommer relasjonsdatabaser til unnsetning?), og 2 typer dataskjemaer - ved skriving (som i mange NoSQL) og lesing (som i relasjons).

Hvis vi går tilbake til aspektet av holdbarhet, vil konklusjonen være som følger: enhver database som skriver til disk, uavhengig av indekseringsmekanismene, kan gi gode garantier for holdbarheten til dataene dine, men du må forholde deg til hver spesifikke database , hva den tilbyr.

6.5 Hvordan DB-er i minnet fungerer

For øvrig, i tillegg til databaser som skriver til disk, finnes det også såkalte «in-memory»-databaser som hovedsakelig jobber med RAM. Kort sagt, databaser i minnet tilbyr vanligvis lavere holdbarhet av hensyn til raskere skrive- og lesehastigheter, men dette kan være passende for noen applikasjoner.

Faktum er at RAM-minne lenge har vært dyrere enn disker, men nylig har det begynt å raskt bli billigere, noe som har gitt opphav til en ny type database - som er logisk, gitt hastigheten på lesing og skriving av data fra RAM. Men du vil med rette spørre: hva med datasikkerheten til disse databasene? Her må du igjen se på detaljene i implementeringen. Generelt tilbyr utviklerne av slike databaser følgende mekanismer:

  • Du kan bruke RAM drevet av batterier;
  • Det er mulig å skrive endringslogger til disk (noe sånt som WALene nevnt ovenfor), men ikke selve dataene;
  • Du kan med jevne mellomrom skrive kopier av databasetilstanden til disk (som, uten å bruke andre alternativer, ikke gir noen garanti, men bare forbedrer holdbarheten);
  • Du kan replikere tilstanden til RAM til andre maskiner.

For eksempel mangler Redis-databasen i minnet, som hovedsakelig brukes som meldingskø eller cache, holdbarhet fra ACID: den garanterer ikke at en vellykket utført kommando vil bli lagret på disken, siden Redis skyller data til disk (hvis du har utholdenhet aktivert) bare asynkront, med jevne mellomrom.

Dette er imidlertid ikke kritisk for alle applikasjoner: Jeg fant et eksempel på EtherPads samarbeidende online editor, som spyles hvert 1-2 sekund, og potensielt kan brukeren miste et par bokstaver eller et ord, noe som neppe var kritisk. Ellers, siden in-memory-databaser er gode ved at de gir datamodeller som ville være vanskelige å implementere med diskindekser, kan Redis brukes til å implementere transaksjoner - dens prioritetskø lar deg gjøre dette.