Sonntag, 24. Februar 2013

Bigdata, NoSQL, was ist das überhaupt? Teil 2: ACID vs NoSQL

Sobald man auf einer Datenbank mehrere Befehle gleichzeitig ausführen will, beispielsweise von mehreren Benutzern, muss man sich Gedanken über die Koordinierung der Zugriffe machen. Glücklicherweise haben dies bereits viele schlaue Leute gemacht und deshalb hat man dieses Thema vor einigen Jahren als erledigt angesehen: ACID lautet die Lösung, die so allgegenwärtig ist, dass man sich lange nicht vorstellen konnte, wie eine Datenbank ohne ACID überhaupt benutzbar sein soll. ACID beschreibt 4 Anforderungen an eine Datenbank, die einen geordneten Betrieb beim gleichzeitigen Zugriff sicher stellen. Wo Licht ist, ist auch Schatten und so hat ACID nicht nur Vorteile, sondern es bremst in der Praxis Datenbanken aus. NoSQL bricht mit diesen Anforderungen und kann so deutlich höhere Leistungszahlen erreichen. Was ist ACID, warum bremst es die Datenbank aus und wie schafft eine NoSQL-Datenbank es, ohne ACID einen geordneten Zugriff sicher zu stellen? Um diese Fragen geht es in den folgenden Absätzen.

A - Atomic

Das A in ACID steht für Atomic, womit gemeint ist, dass jede Operation auf der Datenbank unteilbar ist, eben "atomar". Dies bedeutet, dass jeder Befehl entweder komplett ausgeführt wird oder komplett nicht ausgeführt wird. Aktualisiert man beispielsweise alle Datensätze, so sind entweder alle aktualisiert oder keiner. Über so genannte Transaktionen gilt dies auch für eine Folge von Befehlen. Dies ist wichtig für Umbuchungen, beispielsweise von einem Konto auf ein anderes: der erste Befehl verringert den Kontostand des Ausgangskontos und der zweite Befehl erhöht den des Zielkontos. Es wäre fatal, wenn nur der erste Befehl ausgeführt wird und nicht der zweite, denn dann würden sich Kunden schnell beschweren.

Um diese Funktionalität sicher zu stellen, muss eine Datenbank mit so genannten Sperren arbeiten: Datensätze oder ganze Tabellen werden gegen den Zugriff von anderen Transaktionen gesperrt. Eine triviale Implementation würde im oben genannten Umbuchungs-Beispiel die Datensätze beider Konten sperren, bis die Transaktion abgeschlossen ist, damit bei einem Abbruch der Transaktion keine weitere Transaktion mit einem falschen Kontostand arbeitet. Diese Sperren sind "teuer", denn bei jedem Zugriff muss geprüft werden, ob eine andere Transaktion auf diesen Datensatz zugreift. Noch teurer wird es, wenn die Datenbank auf einem Cluster mit mehreren Servern ausgeführt wird, was auch der Hauptgrund ist, dass klassiche Datenbankcluster selten aus mehr als einer Handvoll Servern bestehen. Auch besteht die Gefahr von so genannten Deadlocks: zwei Transaktionen behindern sich gegenseitig, indem beide einen Teil der Datensätze gesperrt haben und auf die jeweils anderen Datensätze zugreifen möchten.

NoSQL-Datenbanken bieten auch atomare Zugriffe an, jedoch sind die Möglichkeiten für Sperren stark eingeschränkt. So kann eine Sperre beispielsweise nur einen einzigen Datensatz umfassen und nicht mehrere oder gar Datensätze in mehreren Tabellen. Damit löst man zwar das Problem der verteilten Sperren und kann problemlos auf Cluster mit hunderten von Servern skalieren, für den Programmierer erhöht sich aber der Aufwand. In der Praxis ist dies aber auch bei klassischen Datenbanken der Fall, denn wenn Performanceprobleme auftreten, sind Transaktionen einer der ersten Punkte, die optimiert - sprich weg gelassen - werden. So gesehen machen NoSQL-Datenbanken nur das, was man bei Hochleistungsanwendungen sowieso schon immer gemacht hat: möglichst wenig Transaktionen.

C - Consistent

Programmierer wünschen sich, dass Datenbanken konsistent sind, denn mit inkonsistenten Daten besteht die Gefahr von falschen Ergebnissen. Konsistenz bei Datenbanken wird mit so genannten Constraints - auf deutsch "Einschränkungen" - sichergestellt. Diese definieren Anforderungen an Datensätze und das Datenbanksystem verhindert, dass die Tabellen in einen Zustand gerät, der Constraints verletzt. Ein Beispiel für eine solche Einschränkung ist "jede in einer Rechnung angegebene Kundennummer gehört zu einem Kundendatensatz". Gibt es also einen Rechnungsdatensatz mit der Kundennummer 1234, so muss es auch einen Kunden mit dieser Nummer in der Datenbank geben. Soll der Kunde gelöscht werden, so müssen zuerst alle seine Rechnungen entfernt werden, sonst verweigert die Datenbank das Löschen des Kunden.

Constraints verhindern sehr sicher, dass falsche Befehle die Datenbank in einen inkonsistenten Zustand bringen. Sie bedeutet aber auch einen Mehraufwand für die Datenbank, denn bei jedem Einfügen einer Rechnung muss geprüft werden, ob die Kundennummer existiert. Sofern der Programmcode korrekt ist, ist eine solche Prüfung immer erfolgreich. Dies bewegt Datenbankadministratoren dazu, bei Performanceproblemen der Datenbank die Constraints einfach zu entfernen, um die überflüssigen Überprüfungen zu vermeiden. Genau dies ist auch bei NoSQL-Datenbanken der Fall, die auf Constraints größtenteils verzichten. Hierbei nimmt man bewusst in Kauf, dass sich eine Datenbank kurzzeitig in einem inkonsistenten Zustand befindet: soll beispielsweise eine Rechnung mit ihren Rechnungspositionen gespeichert werden, so legt man zuerst die Positionen ab und dann erst den Rechnungskopf. Damit erreicht man, dass bei einer Navigation über den Rechnungskopf die dazu gehörenden Positionen immer vorhanden sind. Geht man dagegen über die Positionen und sucht den Rechnungskopf, so kann es sein, dass dieser (noch) nicht existiert. Dies muss der Programmierer beachten, wenn mit NoSQL gearbeitet wird. Und ebenso der Programmierer einer klassischen SQL-Datenbank, wenn die Constrainst aus Gründen der Geschwindigkeit entfernt wurden.

I - Independent

Mehrere gleichzeitig laufende Transaktionen sollten sich nicht gegenseitig beeinflussen. In der Praxis wird diese Anforderung meist etwas gelockert. Eine Frage ist, ob Änderungen innerhalb einer Transaktion für andere Transaktionen sichtbar sein sollen, bevor die Transaktion abgeschlossen ist. Dies kann zur Folge haben, dass Transaktionen Datensätze sehen, die niemals wirklich in geschrieben werden, da die entsprechende Transaktion abgebrochen wurde - der Fachmann spricht von "Phantom Reads". Wenn im Anwendungsfall Transaktionen sehr selten abgebrochen werden und Phantom Reads toleriert werden können, kann die Transaktions-Isolation entsprechend reduziert werden, was zu mehr Geschwindigkeit führt.

Bei NoSQL-Datenbanken umfassen Transaktionen immer nur einen Datensatz, so dass die angesprochenden Probleme gar nicht erst auftreten können und entsprechend die maximale Geschwindigkeit erreicht wird.

D - Durable

Wenn die Datenbank dem Programm signalisiert, dass eine Transaktion abgeschlossen ist, dann kann man den Stecker ziehen und sich sicher sein, dass nach einem Neustart die Änderungen der Transaktion vorhanden sind. Ebenso, dass das Lesen nach dem Ende der Transaktion die geänderten Daten zurückliefert. In einem Cluster bedeutet dies, dass alle Knoten (zumindest diejenigen mit Kontakt zu Clients) über die Änderung informiert sein müssen, bevor die Datenbank den Erfolg zurückmelden darf, damit auch bei einer Verbindung zu eine anderen Knoten die gleichen Daten zurückgeliefert werden. Dies führt zu Verzögerungen nach Abschluss der Transaktion, so dass der Client weniger Transaktionen pro Sekunde ausführen kann. Auch gibt es sehr hohe Anforderungen an die Übertragungsgeschwindigkeit und Latenz zwischen den einzelnen Datenbankknoten, so dass ein über mehrere Rechenzentren verteilten Betrieb in der Regel nicht machbar ist.

NoSQL garantiert auch, dass Daten nach dem Ende einer Transaktion ausfallsicher geschrieben sind. Es wird jedoch nicht garantiert, dass alle Knoten sofort über die Änderung informiert werden, sondern nur, dass sie irgendwann die Änderung erhalten werden. Man spricht von "eventually consistent", was nicht "eventuell" sondern "schlussendlich" bedeutet. In der Regel handelt es hier um Sekundenbruchteile, bis alle Knoten die Änderung erhalten haben. Damit ist ein Betrieb auch rechnenzentrumsübergreifend möglich, was die Ausfallsicherheit erhöht. Der Programmierer muss jedoch beachten, dass Änderungen nicht sofort für alle sichtbar sind. Dass dies von den Kunden akzeptiert wird, sieht man an Meldungen großer Internetseiten in der Art "es kann einige Minuten dauern, bis Ihre Änderungen in Ihrem Kundenkonto sichtbar werden".

Fazit

NoSQL macht bei den ACID-Kriterien zum Großteil Dinge, die bei klassischen Datenbanken zum Standardrepertoire der Geschwindigkeitsverbesserung gehören. Da hiermit Garantien verwässert werden, bedeutet dies generell einen höheren Aufwand für den Programmierer. Da die Prüfungen aber nicht nur deaktiviert werden, sondern gar nicht vorhanden sind, bieten NoSQL-Datenbanken eine deutlich höhere Geschwindigkeit, als herkömmliche SQL-Datenbanken. Denn wie lautet ein Bekannter Ausspruch eines Google-Entwicklers: kein Code ist schneller als der, der nicht ausgeführt wird.

Keine Kommentare: