Varför använda NoSQL? Vad driver utveckling mot alternativ till de traditionella SQL-databaserna? I denna artikel tittar Citerus konsult Frans Lundberg närmare på de två viktigaste drivkrafterna bakom NoSQL: horisontell skalbarhet och en enklare datamodell.
Traditionella relationsdatabaser med SQL som frågespråk är bra till mycket och har de senaste tre decennierna varit standardvertyget man använder när man vill lagra strukturerad data. Alternativ till SQL har alltid funnits men SQL:s dominans har aldrig tidigare utmanats på samma sätt som nu.
SQL:s dominans
Så varför har SQL-databaser dominerat under så lång tid? Generellt har de bra stöd för samtidiga operationer (genom ACID-transaktioner), de är beprövade och är ofta mycket stabila efter att ha använts i decennier. De har använts både då relationsmodellen verkligen passar men också då den egentligen inte passar så bra, men ändå tillräckligt bra för att inte motivera ett byte. SQL standardiserades 1986 (ANSI), har varit dominerande inom universitetsvärlden och stöds av de största databasleverantörerna.
Ett annat skäl till att SQL-databaser dominerat så länge är att de ofta används som integrationsdatabaser; som ett sätt att dela data mellan flera applikationer. Givet ett sådant upplägg, är det svårt att byta databas till någon helt nytt. En övergång till SOA (service-oriented architecture) gör dock valet av databas friare eftersom datan då inte delas genom gemensam tillgång till en och samma databas.
SQL är bra till mycket och kommer att finnas kvar under överskådlig tid. Däremot är jag övertygad att SQL:s totala dominans håller på att brytas och vi kan se fram emot en mer heterogen och innovativ flora av databaser.
NoSQL och horisontell skalbarhet
NoSQL har fått sitt genombrott mycket genom att stora och coola företag som lagrar riktigt stora datamängder stött på gränsen för vad vanliga relationsdatabaser kan hantera och därför börjat använda NoSQL-databaser som skalar horisontellt. Google, Amazon, Twitter och Facebook — alla använder de NoSQL-databaser.
SQL-databaser är optimerade för att köra på en ensam server. Det fungerar bra för det mesta. Men har man riktigt mycket data eller behöver riktigt hög prestanda på skrivningar och läsningar, kan det vara bättre att skala horisontellt — det vill säga att använda sig av ett kluster av datorer istället för att köpa bättre prestanda på en server. Att skala horisontellt är normalt billigare då man inte behöver köpa dyrare och bättre hårdvara utan istället kan köpa fler datorer av samma typ. Dessutom skalar det längre, man kan ju alltid köpa fler datorer till sitt kluster.
Flera NoSQL-databaser är gjorda för att skala horisontellt genom att köra databasen distribuerat på flera datorer. Apache Cassandra är ett exempel. Cassandra använder sig av så kallad “consistent hashing” (denna sida har en introduktion). Med consistent hashing fördelas datan jämnt över alla datorer i klustret och både läsningar och skrivningar kan skala linjärt med fler datorer i klustret. Man kan alltså dubbla kapaciteten för läsningar, skrivningar och total datamängd genom att helt enkelt dubbla antalet servrar i sitt kluster. Detta är möjligt utan att behöva starta om systemet. Dessutom finns inbyggd redundans genom duplicering av data och ingen single-point-of-failure. Wow! Låter som Universallösningen för databaser… Nja, vi får snart se att det finns en viktig begränsning.
Consistent hashing är ett smidigt sätt att dela upp datan i olika delmängder baserat på ett hashvärde av nyckeln till datan. Delmängderna lagras sedan oberoende av varandra jämnt utspritt på datorerna i klustret. Varje delmängd sparas på flera datorer för redundans. Databasklienten kan själv räkna ut vilken server den ska kontakta för att läsa/skriva datan som hör till nyckeln. Just oberoendet mellan datamängderna är det som gör att systemet skalbart, men samtidigt gör det att systemet inte kan stödja transaktioner. Transaktioner kräver central koordination och perfekt horisontell skalbarhet kräver att man inte har någon central koordination alls.
Man måste alltså välja, antingen stöd för transaktioner, eller horisontell skalbarhet för skrivningar1. Ofta har system många gånger fler läsningar än skrivningar. En bra lösning då, kan vara att bibehålla möjligheten att köra transaktioner genom att skriva till en master, men skala läsningar horisontellt på flera servrar.
Om man verkligen måste skala skrivningar horisontellt av prestandaskäl (vilket sällan är fallet2) så får man överväga hur man kan klara sig utan transaktioner. Det finns många fall där det går, men tänk efter noga först. Det finns två anledningar till att använda transaktioner. Ett, domänen kräver det (tänk betalningar eller aktiehandel). Två, det gör mjukvaruutvecklingen enklare. Den senare anledningen uppmärksammas sällan. Det finns en styrka och enkelhet i att kunna arbeta med konsistent data som går från ett tillstånd till nästa på ett väldefinierat och repeterbart sätt. Om du gillar funktionell programmering förstår du vad jag menar. Repeterbarhet, testbarhet, enkelhet, bevisbarhet.
Enklare datamodell
Relationsmodellen är komplicerad. Här på Citerus kontor i Stockholm finns en bok om JDBC som väger in i heavyweight-klassen med sina 1000+ sidor. Det visar på en viss avsaknad av enkelhet. Och det handlar alltså bara om ett API för att ansluta ett Java-program mot en SQL-databas. Sen måste man ju läsa på om själva databasen också…
Förutom att relationsmodellen ofta (inte alltid, ibland är den riktigt, riktigt bra) är onödigt komplicerad, matchar den dåligt det sätt vi lagrar data i minnet med dataobjekt och pekare mellan dem. Fancy-termen för detta är: “the object-relation impedance mismatch”. En välkänd observation. ORM-verktyg såsom Hibernate försöker överbrygga missmatchningen, men har inte, i min mening, lyckats förenkla situationen och öka produktiviteten på det sätt många hoppats de skulle.
Den enklaste datamodellen för databaser är key-value. Givet en nyckel (key) kan man läsa/skriva motsvarande värde. Modellen fungerar som en hashtabell (till exempel Java’s HashMap). En något mer avancerad modell kan man kalla för navigable-key-value och motsvaras i Java av NavigableMap. Man kan söka ett värde baserat på dess nyckel, men även “nästa” och “föregående” värde givet en nyckel. Detta gör det möjligt att hantera range queries och att iterera genom datan i ordning efter nyckeln. Givet denna model som grund (och stöd för transaktioner) kan man implementera alla funktioner som ingår i en relationsdatabas.
Till min stora belåtenhet, ser jag allt oftare att vikten av datastrukturer som är immutable / persistent uppmärksammas (till exempel: Rich Hickey, Dr Dobb’s). Funktionell programmering, som betonar immutable data har fått en renässans. Däremot har vi ännu inte sett motsvarande utveckling för databaser. Men jag tror den kommer på sikt. Fler databaser kommer att komma ihåg vad som hänt tidigare, det vill säga ha en datamodell som gör tidigare tillstånd tillgängliga för frågor på samma sätt som det senaste tillståndet. Givet en specifik tid, ger en databasfråga då alltid samma svar (och är alltså en funktion). Rätt naturlig modell egentligen: det som hänt har hänt och ändras aldrig.
Inom datavetenskapen kallas en sådan modell för en partially persistent data structure. Två databaser3 med en sådan datamodell är: Datomic och BergDB. De stödjer historiska frågor fullt ut: allt som går att fråga det senaste tillståndet om, kan man också fråga tidigare tillstånd om. Jag instämmer med Fowler och Sadalage4 som skriver att många situationer kräver historiska frågor och att det är förvånande att databaser sällan stödjer detta. BergDB har jag skrivit själv. En viktigt drivkraft för projektet är just bristen på stöd för historiska frågor hos andra databaser. BergDB är en enkel in-process databas som inte gör mycket väsen av sig. Den är gjord för oss som vill fokusera vår utvecklingstid på vårt specifika problem istället för hur man ska sparar sin data. BergDB stöder transaktioner (effektivt genom STM), historiska frågor, replikering och är 10 till 100 gånger snabbare för transaktioner än en typisk SQL-databas. Mer om den en annan gång.
Sammanfattningsvis
Alternativ till relationsdatabaser är här för att stanna. I många fall behövs inte den komplexitet relationsmodellen innebär. En enklare datamodell och mindre kontroll av dataintegritet är ofta fördelaktigt för ett snabbt och agilt sätt att jobba. NoSQL-databaser saknar ofta stöd för transaktioner, men en del har istället möjlighet att skala horisontellt.
Slutligen, tre tips till dig som ska välja databas:
1. Testa själv. Fokusera inte på vad skaparna av databaser skriver om sina egna fantastiska kreationer. Testa själv! NoSQL-databaser är ofta open-source och enkla att komma igång med.
2. Tänk igenom databasfrågorna. Tänk noga och tidigt igenom vilka frågor du kommer behöva ställa till databasen. NoSQL-databaser har generellt inte samma flexibla stöd för databasfrågor som relationsdatabaser har.
3. Reversibelt beslut. Välj den databas som passar bäst med de förutsättningar som finns nu, men gör det möjligt att byta. Tänk på att tidig prestandaoptimering sällan lönar sig.
Lycka till i NoSQL-djungeln!
Fotnoter:
1. Men systemet som helhet kan ju använda flera databaser. Kanske kan man dela upp datan så att bara en mindre del kräver transaktioner.
2. Med en effektiv databas och normal server-hårdvara kan man hantera i storleksordningen 10 000 transaktioner per sekund.
3. Läsaren får gärna upplysa mig om fler sådana databaser. CouchDB har stöd för att i vissa fall kunna hitta en äldre version av ett dokument, med det finns inget generellt stöd och tidigare versioner av dokument replikeras inte.
4. NoSQL Distilled av Pramod J. Sadalagej och Marin Fowler, 2013, sida 145.
Om du är intresserad vill lära dig mer om NoSQL kan vi tipsa om Citerus frukostseminarium i ämnet som kommer att hållas i början av 2014. Håll ögonen öppna!