NoSQL Datenbanken
Datenbanken sind Sammlungen von Tabellen. So wird es im Informatikunterricht gelehrt, das stimmt auch für die meisten Geschäftsanwendungen. Die Daten, die in diesen Prozessen anfallen legen diese Art der Datenhaltung auch nahe. Es sind oft gut strukturierte Daten, die sich gut in Tabellen verarbeiten lassen.
In modernen Webanwendungen oder in IoT Devices fallen oft unstrukturierte Daten an, für die man relativ aufwändige Funktionen entwickeln muss, um sie in die strukturierte Form zu bringen, wie relationale Datenbanken brauchen. Für diese Art von Daten wurden NoSQL Datenbanken entwickelt.
Sie speichern die Daten als Objekte und haben keine eigene Abfragesprache, daher der Name dieser Gruppe von Datenbanksystemen. Wir schauen uns die Möglichkeiten von NoSQL Datenbanken anhand von CouchDB an. Einem OpenSource Projekt, das von der Heimanwendung bis zum weltweitverfügbaren hochperformanten Webdienst skaliert.
Was ist NoSQL?
Eine NoSQL Datenbank speichert Daten nicht in Zeilen von Tabellen, sondern in Dokumenten. Diese Dokumente sind JavaScript Objekte, die beliebige Eigenschaften und Verschachtelungen haben können. So kann ein Dokument ein Blogeintrag sein, an dem auch noch die Kommentare für diesen Eintrag gespeichert sind. In einer relationalen Datenbank bräuchte man hierfür zwei Tabellen und bei jeder Abfrage einen JOIN zwischen den Tabellen um die Kommentare zum Blogartikel anzeigen zu können. Die NoSQL Datenbank liefert dieses Ergebnis ohne teure JOINS.
Durch den Einsatz von Map/Reduce haben NoSQL Datenbanken stets einen aktuellen Index und müssen diesen nicht in regelmäßigen Abständen nach dem Einfügen von Daten neuberechnen. Dadurch gibt es Zeiten, in denen die Zugriffe auf die Datenbank langsamer sind, als mit einem frischen Index.
Das erreichen NoSQL Datenbanken durch die sofortige Aktualisierung des Index, sobald neue Daten eingefügt werden. Views, die eine ähnliche Funktion haben, wie in SQL Datenbanken, haben eine Map- sowie eine optionale Reduce-Funktion. Der Index einer View ist ein B-Baum, eine Datenstruktur, die sehr schnelles Suchen erlaubt.
Werden nun neue Daten in die Datenbank hinzugefügt, wird die Map Funktion für dieses Dokument aufgerufen und das Ergebnis in den B-Baum eingefügt. Der Index ist aktualisiert und der nächste Request auf diese View ist so schnell wie zuvor.
Views bieten meistens einen kleineren Ausschnitt von Dokumenten oder auch nur eine Teilmenge von ihnen an, Dokumente können aber auch frei abgefragt werden, was wiederum über JavaScript Objekte passiert.
NoSQL in der Anwendung
Views erstellen
Views stellen bestimmte Ansichten der gespeicherten Daten bereit. Wenn wir wieder an das Blog denken, das Artikel und Kommentare im selben Dokument speichert, ist eine Ansicht, die nur die Blogartikel enthält ein nützliches Werkzeug, damit man nicht in der Übersicht des Blogs alle Kommentare laden muss, die hier ohnehin nicht angezeigt werden.
Die Map Funktion unserer Blogübersicht würde ungefähr so aussehen:
function (doc) { emit(doc.title, { "text": doc.text.substr(0, 500), "author": doc.author, "date": doc.date, "comments": doc.comments.length, }); }
Es wird ein Dokument für den Index bereitgestellt, dass unter dem Titel des Dokuments im B-Baum gespeichert wird. Dieses Dokument ist jedoch nur noch mit den Eigenschaften ausgestattet, die in der Anzeige der Übersicht relevant sind.
Auf dieser View können wir eine Reduce Funktion definieren, die die Gesamtzahl der Kommentare ermittelt oder ähnliches. Diese Funktionen lassen alle möglichen Arten der Aggregation der Daten aus der View zu.
Clientseitige Abfragen
CouchDB erlaubt Abfragen auf Views, die sind z.B. ein einfacher GET-Request auf die View, deren URL wir beim Anlegen der View vergeben.
Die Abfrage der URL http://couchdb.local:5984/blog/_design/blog-view gibt uns ein JSON Objekt zurück, das alle Elemente enthält, die die Map Funktion erstellt hat. Die View kann, so eine Reduce Funktion definiert ist, mit dem Parameter „reduce=true“ aufgerufen werden um die Reduce Funktion zu nutzen.
Freie Abfragen werden an den Endpunkt „_find“ gerichtet und sind POST-Requests, deren Body das Suchobjekt enthält.
Dieses Objekt sieht wie folgt aus:
{ "selector": { "_id": { "$gt": null } } }
Dieser Selektor gibt jedes Dokument zurück, dass eine ID hat, also alle, da die ID das einzige Pflichtfeld in einem Dokument ist. Selektoren können neben einfachen Feldern auch komplexe Objekte beinhalten. Jede Eigenschaft, auf die man mit einem Selektor filtern möchte muss nur in dem Selektor benannt werden und einen Vergleichsoperator zugewiesen bekommen.
Vergleichsoperatoren können, wie im Beispiel, arithmetisch sein oder Literale nutzen.
Neue Dokumente anzulegen und zu ändern ist genau so einfach, wie sie zu löschen, hierzu bedarf es jeweils nur eines HTTP-Requests. Zum Speichern ist es ein POST-Request, welcher als Body das zu speichernde Objekte enthält, zum Löschen ein DELETE-Request auf das Dokument unter seiner URL /datenbank/_id?rev=rev.
Die Revision muss bei jedem schreibenden Befehl mitgeschickt werden um Update-Konflikte zu verhindern.
Eigenheiten von CouchDB
CouchDB wurde mit besonderem Augenmerk auf Skalierbarkeit designt, die Vision war es viele Knoten zu haben, die auf vergleichsweise günstiger Hardware laufen konnten und dennoch eine gute Performance liefern.
Dem wird CouchDB gerecht, es ist sehr einfach eine Datenbank auf einen anderen Knoten zu replizieren bzw. mehrer Knoten zu synchronisieren.
Mit der Verteiltheit ergeben sich jedoch auch ein paar Schwächen. So kann man einige Constrains, die sich in SQL Datenbanken sehr leicht setzen lassen in CouchDB nur schwer oder gar nicht realisieren.
Einzigartigkeit ist eines dieser Constrains. In SQL muss eine Spalte nur als UNIQUE markiert werden oder mehrere zu einem UNIQUE INDEX zusammengefasst werden. Eine Möglichkeit Einzigartigkeit zu für ein einzelnes Feld, wie eine ID zu erreichen ist über das Feld „_id“, welches CouchDB intern als einzigartige ID verwendet. Dieser Ansatz ist jedoch auch nicht davor gefeit, dass die gleiche ID auf zwei Knoten gleichzeitig genutzt wird, um ein Dokument zu erstellen.
CouchDB kann neben Dokumenten auch Anhänge speichern, Dateien, die einem Dokument zugeordnet werden, wenn wir wieder an das Blogbeispiel denken könnten dies zum Beispiel die Bilder sein, die im Beitrag benutzt werden.
Installation und Konfiguration
CouchDB ist der Linuxwelt entsprungen, entsprechend einfach ist hier die Installation. Der Befehl „sudo apt-get install couchdb“ kümmert sich um alles erforderliche um die Software auf der Maschine zu installieren, danach kann die Konfiguration händisch angepasst oder mit einem Template überschrieben werden, wenn es sich um einen Knoten in einem Cluster handelt.
Unter Windows muss man sich selbst um den Download des Setups und dessen Ausführung kümmern.
Auf beiden Systemen ist nach dem Abschluss der Installation unter http://localhost:5984/_utils/ die Administrationsoberfläche von CouchDB erreichbar. Während der sog. Adminparty kann jeder alles in der Datenbank machen, die Party ist vorbei, sobald ein Administrator Account angelegt wurde.
Die Datenbankübersicht mit zwei auf dem Knoten vorhandenen Datenbanken. Sie bietet Shortcuts zur Replikation bzw. Rechteverwaltung der Datenbank und zum Löschen.
Den Ansatz allen Nutzern alles zu erlauben, bis man explizite Berechtigungen vergibt verfolgt CouchDB auch bei einzelnen Datenbanken.
CouchDB unterscheidet zwischen Administration und Nutzung einer Datenbank, hierfür vergibt man getrennt Rechte. Ein User kann Rollen haben, unter denen man Nutzer gruppieren kann. Rechte können explizit an einen Nutzer oder an Rollen vergeben werden. Es ist nicht möglich Nutzerverzeichnisse, wie Active Directory direkt mit CouchDB zu verbinden, hierfür bedarf es individueller Lösungen.
Welche Rechte ein Nutzer auf der Datenbank letztlich hat, also welche Dokumente er schreiben darf wird über das Dokument _design/_auth bestimmt, die in ihm befindliche Funktion nimmt als Parameter das neue Dokument und dessen letzten Stand, den Kontext des Users, sowie ein Security Objekt entgegen, sodass die Funktion Zugriff auf das Userbackend von CouchDB hat.
tl;dr
Für Anwendungen, die komplexe Objekte speichern und durchsuchen müssen, oder eine hohe Leseperformance brauchen ist NoSQL eine interessante Option. Durch ihre REST Schnittstellen fügen sich diese Datenbanken sehr gut in den Entwicklungsworkflow ein, was für eine flache Lernkurve in der Nutzung sorgt.
Wer schon lange mit SQL Datenbanken unterwegs ist, wird Indizes und andere eingebaute Funktionen zunächst vermissen, jedoch für die meisten Anwendungsfälle im NoSQL Ansatz gute Alternativen zu dem finden, was er vermisst.