6.1 Battle of Abbreviations: BASE vs. SYRA

"Inom kemin mäter pH den relativa surheten hos en vattenlösning. pH-skalan går från 0 (starkt sura ämnen) till 14 (starkt alkaliska ämnen); rent vatten vid 25°C har ett pH på 7 och är neutralt.

Dataingenjörer har tagit den här metaforen för att jämföra databaser angående tillförlitligheten hos transaktioner."

Förmodligen var tanken denna: ju högre pH, d.v.s. ju närmare databasen är "alkalisk" ("BAS"), desto mindre tillförlitliga är transaktionerna.

Populära relationsdatabaser, som MySQL, dök upp bara på basis av ACID. Men under de senaste tio åren har de så kallade NoSQL-databaserna, som kombinerar flera väldigt olika typer av databaser under detta namn, klarat sig ganska bra utan ACID. Faktum är att det finns ett stort antal utvecklare som arbetar med NoSQL-databaser och som inte bryr sig alls om transaktioner och deras tillförlitlighet. Låt oss se om de har rätt.

Du kan inte prata generellt om NoSQL-databasen, eftersom det bara är en bra abstraktion. NoSQL-databaser skiljer sig från varandra i designen av datalagringsundersystem, och även i datamodeller: NoSQL är både dokumentorienterad CouchDB och graf Neo4J. Men om vi talar om dem i samband med transaktioner, tenderar de alla att vara lika i en sak: de tillhandahåller begränsade versioner av atomicitet och isolering, och ger därför inga ACID-garantier. För att förstå vad detta betyder, låt oss svara på frågan: vad erbjuder de, om inte ACID? Ingenting?

Inte riktigt. De behöver trots allt, liksom relationsdatabaser, också sälja sig själva i ett vackert paket. Och de kom på en egen "kemisk" förkortning - BASE.

6.2 BASE som antagonist

Och här kommer jag inte att gå i bokstäverordning, utan jag börjar med den grundläggande termen - konsekvens. Jag måste utjämna din igenkänningseffekt, eftersom denna konsistens har lite att göra med konsistensen från ACID. Problemet med begreppet konsekvens är att det används i för många sammanhang. Men denna konsistens har ett mycket bredare användningskontext, och det är faktiskt exakt den konsekvens som diskuteras när man diskuterar distribuerade system.

De relationsdatabaser vi pratade om ovan ger olika nivåer av transaktionsisolering, och de strängaste av dem säkerställer att en transaktion inte kan se ogiltiga ändringar gjorda av en annan transaktion. Om du står i kassan i en butik, och i det ögonblicket dras pengarna för hyran från ditt konto, men transaktionen med överföringen av pengar för hyran misslyckas och ditt konto återgår till sitt tidigare värde (pengarna är inte debiteras), så kommer din betalningstransaktion i kassan inte att märka alla dessa gester - trots allt gick den transaktionen aldrig igenom, och baserat på kravet på transaktionsisolering kan dess tillfälliga förändringar inte märkas av andra transaktioner.

Många NoSQL-databaser avstår från isoleringsgarantin och erbjuder "eventuell konsistens" där du så småningom kommer att se giltig data, men det finns en chans att din transaktion kommer att läsa ogiltiga värden - det vill säga tillfälliga, eller delvis uppdaterade eller föråldrade. Det är möjligt att data kommer att bli konsekventa i "lat" läge vid läsning ("lat vid läsningstid").

NoSQL var tänkt som en databas för realtidsanalys, och för att uppnå högre hastighet offrade de konsekvens. Och Eric Brewer, samma kille som myntade termen BASE, formulerade det så kallade "CAP-teoremet", enligt vilket:

För alla implementeringar av distribuerad databehandling är det möjligt att tillhandahålla högst två av följande tre egenskaper:

  • datakonsistens ( konsistens ) - data på olika noder (instanser) motsäger inte varandra;
  • tillgänglighet ( tillgänglighet ) - varje begäran till ett distribuerat system slutar med ett korrekt svar, men utan en garanti för att svaren från alla systemnoder är desamma;
  • partitionstolerans (partitionstolerans ) - Även om det inte finns någon koppling mellan noderna fortsätter de att arbeta oberoende av varandra.

Om du vill ha en mycket enkel förklaring av CAP, så är det här.

Det finns åsikter om att CAP-satsen inte fungerar, och generellt är alltför abstrakt formulerad. På ett eller annat sätt vägrar NoSQL-databaser ofta konsekvens i samband med CAP-satsen, som beskriver följande situation: data har uppdaterats i ett kluster med flera instanser, men ändringarna har ännu inte synkroniserats på alla instanser. Kom ihåg att jag nämnde DynamoDB-exemplet ovan, som sa till mig: dina ändringar blev hållbara - här är en HTTP 200 för dig - men jag såg ändringarna först efter 10 sekunder? Ett annat exempel från en utvecklares vardag är DNS, Domain Name System. Om någon inte vet, så är detta exakt "lexikonet" som översätter http(s)-adresser till IP-adresser.

Den uppdaterade DNS-posten sprids till servrarna enligt cacheintervallinställningarna - så uppdateringar märks inte direkt. Tja, en liknande tidsmässig inkonsekvens (d.v.s. så småningom konsistens) kan hända med ett relationsdatabaskluster (säg, MySQL) - trots allt har denna konsistens ingenting att göra med konsistens från ACID. Därför är det viktigt att förstå att i denna mening är det osannolikt att SQL- och NoSQL-databaser är väldigt olika när det gäller flera instanser i ett kluster.

Dessutom kan end-to-end-konsistens innebära att skrivförfrågningar kommer att göras ur funktion: det vill säga all data kommer att skrivas, men värdet som så småningom kommer att tas emot kommer inte att vara det sista i skrivkön.

Non-ACID NoSQL-databaser har ett så kallat "soft state" på grund av end-to-end-konsistensmodellen, vilket innebär att systemets tillstånd kan förändras över tid, även utan input. Men sådana system strävar efter att ge större tillgänglighet. Att tillhandahålla 100 % tillgänglighet är inte en trivial uppgift, så vi pratar om "grundläggande tillgänglighet". Och tillsammans bildar dessa tre begrepp: "basically available", "soft state" ("mjukt tillstånd") och "eventuell konsistens" akronymen BASE.

För att vara ärlig så verkar konceptet BASE för mig vara ett mer tomt marknadsföringsomslag än ACID – eftersom det inte ger något nytt och inte kännetecknar databasen på något sätt. Och att fästa etiketter (ACID, BASE, CAP) till vissa databaser kan bara förvirra utvecklare. Jag bestämde mig för att introducera dig för denna term ändå, eftersom det är svårt att kringgå den när du studerar databasen, men nu när du vet vad det är vill jag att du glömmer det så snart som möjligt. Och låt oss gå tillbaka till begreppet isolering.

6.3 Så BASE-databaserna uppfyller inte ACID-kriterierna alls?

I huvudsak, där ACID-databaser skiljer sig från icke-ACID är att icke-ACID faktiskt avstår från isolering. Detta är viktigt att förstå. Men det är ännu viktigare att läsa databasdokumentationen och testa dem som killarna från Eremitageprojektet gör. Det är inte så viktigt hur exakt skaparna av den eller den databasen kallar sin idé - ACID eller BASE, CAP eller inte CAP. Det viktiga är vad exakt den eller den databasen tillhandahåller.

Om skaparna av databasen hävdar att den ger ACID-garantier, så finns det förmodligen en anledning till detta, men det är lämpligt att testa den själv för att förstå om det är så och i vilken utsträckning. Om de förklarar att deras databas inte ger sådana garantier, kan det betyda följande:

  • DB ger ingen garanti för atomicitet. Medan vissa NoSQL-databaser erbjuder ett separat API för atomoperationer (t.ex. DynamoDB);

  • DB ger ingen isoleringsgaranti. Det kan till exempel innebära att databasen inte kommer att skriva data i den ordning som de skrevs.

När det gäller hållbarhetsgarantin kompromissar många databaser på denna punkt för prestandas skull. Att skriva till disk är en för lång operation och det finns flera sätt att lösa detta problem. Jag vill inte gå in så mycket på databasteorin, men för att du ungefär ska förstå hur du ska se ut så kommer jag att beskriva i generella termer hur olika databaser löser problemet med hållbarhet.

För att bland annat jämföra olika databaser behöver du veta vilka datastrukturer som ligger till grund för datalagrings- och hämtningsundersystemet för en viss databas. Kort sagt: olika databaser har olika implementeringar av indexering - det vill säga organisera åtkomst till data. Vissa av dem låter dig skriva data snabbare, andra - snabbare att läsa den. Men det kan inte sägas generellt att vissa datastrukturer gör hållbarheten högre eller lägre.

6.4 hur olika databaser indexerar data, och hur detta påverkar hållbarheten, med mera

Det finns två huvudsakliga metoder för att lagra och hämta data.

Det enklaste sättet att spara data är att lägga till operationer i slutet av filen på ett loggliknande sätt (det vill säga en append-operation inträffar alltid): det spelar ingen roll om vi vill lägga till, ändra eller ta bort data - allt CRUD-operationer skrivs helt enkelt till loggen. Att söka i loggen är ineffektivt, och det är där indexet kommer in – en speciell datastruktur som lagrar metadata om exakt var datan är lagrad. Den enklaste indexeringsstrategin för loggar är en hashkarta som håller reda på nycklar och värden. Värdena kommer att vara referenser till byteoffset för data som skrivs inuti filen, som är loggen (loggen) och lagras på disken. Denna datastruktur lagras helt och hållet i minnet, medan själva data finns på disk, och kallas ett LSM-träd (log structured merge).

Du undrade säkert: om vi skriver vår verksamhet till journalen hela tiden, kommer den att växa orimligt? Ja, och därför uppfanns komprimeringstekniken, som "rensar upp" data med viss periodicitet, nämligen lämnar bara det mest relevanta värdet för varje nyckel eller raderar det. Och om vi har mer än en inloggningsdisk, men flera, och alla är sorterade, kommer vi att få en ny datastruktur som heter SSTable ("sorterad strängtabell"), och detta kommer utan tvekan att förbättra vår prestanda. Om vi ​​vill sortera i minnet kommer vi att få en liknande struktur - den så kallade MemTable, men med den är problemet att om en fatal databaskrasch inträffar så kommer den data som skrevs sist (finns i MemTable, men ännu inte skriven till disk) går förlorade. Faktiskt,

En annan metod för indexering är baserad på B-träd ("B-träd"). I ett B-träd skrivs data till disk i fast storlek sidor. Dessa datablock är ofta runt 4 KB stora och har nyckel-värdepar sorterade efter nyckel. En B-trädnod är som en array med länkar till en rad sidor. Max. antalet länkar i en array kallas grenfaktorn. Varje sidintervall är en annan B-trädnod med länkar till andra sidintervall.

Så småningom, på arknivå, hittar du enskilda sidor. Denna idé liknar pekare i programmeringsspråk på låg nivå, förutom att dessa sidreferenser lagras på disken snarare än i minnet. När INSERT och DELETE förekommer i databasen, kan någon nod delas upp i två underträd för att matcha förgreningsfaktorn. Om databasen misslyckas av någon anledning mitt i processen, kan dataintegriteten äventyras. För att förhindra detta från att hända upprätthåller databaser som använder B-träd en "write-ahead log", eller WAL, där varje enskild transaktion registreras. Denna WAL används för att återställa tillståndet för B-trädet om det är skadat. Och det verkar som att det är detta som gör databaser som använder B-träd bättre vad gäller hållbarhet. Men LSM-baserade databaser kan också upprätthålla en fil som i huvudsak utför samma funktion som WAL. Därför kommer jag att upprepa det jag redan har sagt, och kanske mer än en gång: förstå funktionsmekanismerna för den databas du har valt.

Vad som är säkert med B-träd är dock att de är bra för transaktionalitet: varje nyckel förekommer endast på ett ställe i indexet, medan journaliserade lagringsundersystem kan ha flera kopior av samma nyckel i olika skärvor (till exempel tills nästa komprimering utförs).

Utformningen av indexet påverkar dock direkt databasens prestanda. Med ett LSM-träd är skrivningar till disk sekventiella, och B-träd orsakar flera slumpmässiga diskåtkomster, så skrivoperationer är snabbare med LSM än med B-träd. Skillnaden är särskilt betydande för magnetiska hårddiskar (HDD), där sekventiell skrivning är mycket snabbare än slumpmässig skrivning. Läsningen går långsammare på LSM-träd eftersom man måste titta igenom flera olika datastrukturer och SS-tabeller som befinner sig i olika stadier av komprimering. Mer detaljerat ser det ut så här. Om vi ​​gör en enkel databasfråga med LSM kommer vi först att slå upp nyckeln i MemTable. Om det inte finns där, tittar vi på den senaste SSTable; om inte där, så tittar vi på den näst sista SSTable, och så vidare. Om den begärda nyckeln inte finns, så kommer vi med LSM att veta detta sist. LSM-träd används i till exempel: LevelDB, RocksDB, Cassandra och HBase.

Jag beskriver det hela så detaljerat så att du förstår att när du väljer en databas måste du tänka på många olika saker: förväntar du dig till exempel att skriva eller läsa data mer. Och jag har ännu inte nämnt skillnaden i datamodeller (behöver du gå igenom datan, som grafmodellen tillåter? Finns det överhuvudtaget samband mellan olika enheter i din data - då kommer relationsdatabaser till undsättning?), och 2 typer av datascheman - vid skrivning (som i många NoSQL) och läsning (som i relationell).

Om vi ​​återvänder till hållbarhetsaspekten blir slutsatsen följande: vilken databas som helst som skriver till disk, oavsett indexeringsmekanismer, kan ge goda garantier för hållbarheten hos dina data, men du måste hantera varje specifik databas , exakt vad den erbjuder.

6.5 Hur in-memory DB:er fungerar

Förresten, förutom databaser som skriver till disk finns det även så kallade "in-memory"-databaser som huvudsakligen arbetar med RAM. Kort sagt, minnesdatabaser erbjuder vanligtvis lägre hållbarhet för snabbare skriv- och läshastigheter, men detta kan vara lämpligt för vissa applikationer.

Faktum är att RAM-minne länge har varit dyrare än diskar, men på senare tid har det snabbt börjat bli billigare, vilket har gett upphov till en ny typ av databas – vilket är logiskt med tanke på hastigheten att läsa och skriva data från RAM. Men du kommer med rätta att fråga: hur är det med datasäkerheten i dessa databaser? Här måste du återigen titta på detaljerna i genomförandet. I allmänhet erbjuder utvecklarna av sådana databaser följande mekanismer:

  • Du kan använda RAM som drivs av batterier;
  • Det är möjligt att skriva ändringsloggar till disk (något liknande WALs som nämns ovan), men inte själva data;
  • Du kan periodiskt skriva kopior av databastillståndet till disken (vilket, utan att använda andra alternativ, inte ger en garanti, utan bara förbättrar hållbarheten);
  • Du kan replikera tillståndet för RAM till andra maskiner.

Till exempel saknar minnesdatabasen Redis, som huvudsakligen används som meddelandekö eller cache, hållbarhet från ACID: den garanterar inte att ett framgångsrikt utfört kommando kommer att lagras på disken, eftersom Redis spolar data till disken (om du har beständighet aktiverad) endast asynkront, med jämna mellanrum.

Detta är dock inte kritiskt för alla applikationer: jag hittade ett exempel på EtherPads kooperativa onlineredigerare, som spolas var 1-2:e sekund, och potentiellt kan användaren förlora ett par bokstäver eller ett ord, vilket knappast var kritiskt. Annars, eftersom in-memory databaser är bra genom att de tillhandahåller datamodeller som skulle vara svåra att implementera med diskindex, kan Redis användas för att implementera transaktioner - dess prioritetskö låter dig göra detta.