Dokumentensammlungen statt Tabellen

MongoDB: erfolgreich ein dokumentenorientiertes Datenbanksystem einführen
Kommentare

Immer wieder wird von nichtrelationalen Datenbankmanagementsystemen wie MongoDB als Ergänzung und nicht als Ersatz relationaler Datenbanken gesprochen. Die neuen Systeme bieten aber neben einem anderen Datenmodell weitere Eigenschaften, die den Einsatz auch im traditionellen Umfeld attraktiv machen. Der Artikel beschreibt Erfahrungen mit der Einführung einer Plattform, die von einem relationalen Datenbankmanagementsystem auf MongoDB umgestellt wurde. Kritisch wird betrachtet, wie sich die Eigenschaften von MongoDB im konkreten Einsatz bewähren. Darüber hinaus werden ausgewählte Stolperfallen und Lösungsansätze erläutert und diskutiert.

Stellen nichtrelationale Datenbankmanagementsysteme (DBMS) eine Alternative zu relationalen DBMS dar, oder sind sie eine Ergänzung? Spichale und Wolff sehen diese vor allem als Ersatz für Einsatzszenarien, in denen relationale DBMS bisher lediglich deshalb eingesetzt wurden, weil es keine Alternativen gab. Dennoch unterstützt eine Reihe von nichtrelationalen DBMS weitere Eigenschaften wie einfache Skalierbarkeit, die auch an anderer Stelle den Einsatz rechtfertigen könnten. Für Einsatzszenarien, in denen zwar strukturierte Daten vorliegen, aber auch die Unterstützung der anderen Eigenschaften nichtrelationaler DBMS von Belang sind, ist dann eine wohlüberlegte Abwägung zwischen sehr unterschiedlichen DBMS erforderlich. Die Entscheidung für eine weniger erprobte Technologie kann dabei mit beträchtlichen Risiken verbunden sein. Im Folgenden sollen daher einige Erfahrungen mit MongoDB in einem Projekt dargestellt werden, in dem das zugrunde liegende DBMS MySQL ersetzt wurde. Zu den wichtigsten Eigenschaften, die für einen Einsatz von MongoDB sprechen, gehören:

  • Performanz: Vom Ansatz her haben dokumentenorientierte DBMS hier einen Vorteil, weil die Daten nicht erst aus mehreren Tabellen zusammengeführt werden
  • Skalierbarkeit: Mit transparentem Sharding – einem Verfahren zur horizontalen Skalierung – kann die Infrastruktur vergleichsweise einfach wachsenden Anforderungen angepasst werden
  • Verfügbarkeit: Auch bei Ausfall einer Datenbankinstanz soll die Applikation weiterhin verfügbar bleiben, d. h. nahtlos – ohne manuellen Eingriff – müssen redundante Instanzen bei einem Ausfall einspringen

Natürlich sind diese drei Eigenschaften nicht unabhängig voneinander. So kann beispielsweise die Performanz von der Möglichkeit der horizontalen Skalierbarkeit profitieren, und aus der Skalierbarkeit resultieren neue Herausforderungen für die Verfügbarkeit. Zudem ist zu bemerken, dass Erweiterungen von MySQL diese Eigenschaften prinzipiell auch bereitstellen. Für MongoDB spricht, dass sie integraler Bestandteil einer durchdachten Gesamtarchitektur sind.

Info: Was ist dokumentenorientiert?

Dokumente sind in diesem Kontext Objekte mit Attributen, die in einer Sammlung (Collection) verwaltet werden. Eine Sammlung entspricht in etwa einer Tabelle in relationalen Datenbanken. Die Objekte einer Sammlung müssen hier aber nicht zwingend strukturell gleich sein. Die Verwendung eines dokumentenorientierten DBMS setzt überhaupt kein zuvor beschriebenes Datenschema voraus. Sie eignen sich daher im Besonderen zur Speicherung divergenter Objekte. Nichtsdestotrotz kann zur Realisierung der Applikation ein Datenschema erforderlich sein.

Eingebettete Dokumente entsprechen einem Dokument, das wiederum einem Attribut des übergeordneten Dokuments als Wert zugewiesen wird. Das heißt, dass Dokumente wiederum als Attribute in einem Dokument gespeichert werden können. Die eingebetteten Dokumente können auch in einer Liste abgelegt werden. Änderungen an den eingebetteten Dokumenten – auch wenn diese in einer Liste organisiert sind – können wie an den übergeordneten Dokumenten zielgerichtet vorgenommen werden. So können komplexe Datenstrukturen als Dokumente gespeichert werden.

Erweiterbarkeit und Änderung des Datenschemas

Ein Vorteil nichtrelationaler DBMS ist, dass Änderungen des Datenmodells der Applikation leicht umzusetzen sind. Das heißt zunächst vor allem, dass keine Skripte zur Änderung des Datenschemas gepflegt werden müssen. In dem Fall, dass zur Laufzeit Änderungen an den zu speichernden Attributen einer Entität vorgenommen werden müssen, findet für relationale DBMS das EAV-Prinzip (Entity Attribute Value) Anwendung. Dabei wird durch ein generisches Datenschema Flexibilität gegen Performanz eingetauscht. Dokumentenorientierte DBMS ermöglichen dagegen, dass jedes Dokument einer Sammlung verschiedene Attribute besitzt. Das Hinzufügen, Umbenennen oder Entfernen von Attributen ist daher von Seiten des Datenbanksystems jederzeit möglich. Nichtsdestotrotz muss klar sein, dass entweder auch applikationsseitig diese Flexibilität gewährleistet oder aber zugunsten eines spezifizierten Datenmodells auf die Nutzung dieser Möglichkeit verzichtet werden muss.

Nachträgliches Hinzufügen von Attributen zu Dokumenten führt zur Erhöhung des erforderlichen physischen Speichers für ein Dokument. Das ist deshalb nicht unproblematisch, weil Dokumente einer Sammlung prinzipiell keine feste Größe besitzen. Änderungen können daher dazu führen, dass Dokumente im Speicher in einen größeren freien Bereich verschoben werden müssen. Das Verschieben von Dokumenten wegen Vergrößerung der Dokumente kann das Laufzeitverhalten negativ beeinträchtigen. MongoDB führt daher für jede Sammlung ein Attribut: einen Faktor, der den zu reservierenden Speicher (mit Padding) auf Basis der eigentlichen Dokumentengröße beschreibt. Der Faktor wird von MongoDB aus vorhergehenden Änderungen bestimmt. Werden in einer Sammlung die Dokumente regelmäßig nachträglich vergrößert, nimmt der Faktor einen Wert größer eins an. Einfluss darauf hat auch, wenn einem Attribut, das als Zeichenkette abgelegt ist, eine längere Zeichenkette zugewiesen wird. Ohne Einfluss können Änderungen an Attributen von Typen fester Länge vorgenommen werden. Dazu gehören beispielsweise Zahlen oder Datumsangaben. Die gebotene Flexibilität kann zu teureren Operationen führen, wenn bestimmte Grenzen überschritten werden.

Logische Redundanz im Hinblick auf Konsistenz und Speicherbedarf

Redundanz kann auf zwei Ebenen betrachtet werden. Auf der physischen Ebene erhöht sie Integrität und Verfügbarkeit der Daten. Dazu werden folgend entsprechende Mechanismen von MongoDB diskutiert. Auf logischer Ebene ist Redundanz in der Regel nicht gewollt. Die Vermeidung redundanter Datenhaltung ist wichtig, um sowohl die Erhaltung der Datenkonsistenz zu erleichtern als auch die zu speichernde Datenmenge zu reduzieren. Für relationale DBMS steht dazu die Methode der Normalisierung zur Verfügung. In dokumentenorientierten DBMS wird das Ziel der Speicherbedarfsreduktion mit anderen Methoden verfolgt. Die Reduzierung des erforderlichen Speicherbedarfs wird insbesondere dadurch erreicht, dass nur die Attribute gespeichert werden, die tatsächlich vorhanden sind. Eine Konsequenz daraus ist, dass Dokumente wachsen und schrumpfen können. Hinsichtlich der Datenkonsistenz resultieren aus der fehlenden Normalisierung höhere Anforderungen an die Applikation.

Ein wichtiger Unterschied zwischen relationalen und dokumentenorientierten DBMS ist der Join-Operator, der zwischen zwei Datenbehältern (Tabellen bzw. Sammlungen/Collections) das Kartesische Produkt bildet und der in der Regel in Verbindung mit einer Selektion verwendet wird. In dokumentenorientierten DBMS gibt es keinen vergleichbaren Operator, da die Verbindung von Datensätzen in der Regel durch das Einbetten von Dokumenten erfolgt. Dennoch erlaubt MongoDB auch Fremdschlüssel durch Referenzen in einem Dokument auf andere Dokumente. Diese Referenzen können allerdings nicht automatisiert durch das DBMS, sondern müssen applikationsseitig aufgelöst werden. Das kostet also mindestens eine zusätzliche Datenbankabfrage. Dem steht entgegen, dass die meist unvermeidliche Umorganisation der Dokumente durch MongoDB mit zunehmender Größe auch teurer wird. Dokumente werden dann umorganisiert, wenn sie für den reservierten Speicherplatz zu groß werden oder wenn Daten zu einem anderen Shard verschoben werden (siehe Kasten „Sharding“). Neben den Anforderungen an die Applikation zur Gewährleistung der Datenkonsistenz sind also die Kosten zusätzlicher Abfragen gegen höhere Kosten zur Umorganisation des Speichers abzuwägen.

Das Instrument der Normalformen, um aus einem Datenmodell ein Datenschema zu konstruieren, das keine Redundanzen enthält, kann also nicht auf dokumentenorientierte Datenbanken angewendet werden. Letztlich widerspricht streng genommen jede Teilung der Datensätze dem Ansatz dokumentenorientierter DBMS. Es erfordert eine gründliche Abwägung, ob Beziehungen durch Referenzen oder eingebettete Dokumente realisiert werden. Die Verwendung von Referenzen kann unter Berücksichtigung folgender Grundsätze erfolgen:

  • Wenn Entitäten in einer n-zu-1-Beziehung referenziert werden und Änderungen an der zweiten Entität zu erwarten sind, weil sonst die Datenkonsistenz nur schwer zu gewährleisten ist; dagegen werden Kompositionsbeziehungen in der Regel durch eingebettete Dokumente realisiert.
  • Wenn die Anzahl der zweiten Entität in einer 1-zu-n-Beziehung zur Laufzeit stark variiert, weil die Speicherverwaltung sonst ineffizienter wird
  • Wenn im Datenmodell der Applikation zirkulare Beziehungen existieren, was andernfalls zu einer hohen Verschachtelungstiefe eingebetteter Dokumente führen kann.

Der Übergang vom konzeptionellen zum logischen Datenschema resultiert durch den weitgehenden Verzicht auf Referenzen nicht – wie bei der Normalisierung – in einer größeren Zahl von Entitäten, sondern in einer größeren Zahl von Attributen einer Entität (d. h. eines Dokuments) durch redundante oder auch aggregierte Attribute.

Aufmacherbild: database folder file 3d Illustrations on a white background von Shutterstock / Urheberrecht: 3dfoto [ header = Seite 2: Horizontale Skalierung]

Horizontale Skalierung

Eine wesentliche Eigenschaft von MongoDB ist die für die Applikation transparente Shard-Architektur mit automatischer Ausbalancierung. Werden Dokumente initial in eine Sammlung geschrieben, erfolgt dies zunächst ausschließlich in einen Shard, d. h. auf einer Instanz des Datenbanksystems. Wenn in einer Sammlung die maximale Blockgröße erreicht ist, wird der Block geteilt. Die Daten bleiben zunächst auf dem gleichen Shard. Erst wenn die Anzahl der Blöcke auf den Shards sich um einen Schwellwert (in der Regel mehr als acht) unterscheidet, wird ein Block auf einen anderen Shard verschoben. Damit wird erreicht, dass die Last auf mehrere Instanzen des Datenbanksystems verteilt wird, die Datenbank also einfach skalierbar ist.

Info: Was ist Sharding?

Skalierung großer Datenmengen wird nicht durch Verteilung auf verschiedene Sammlungen (vertikale Skalierung), sondern durch Verteilung der Datensätze einer Sammlung auf mehrere Shards (horizontale Skalierung) erreicht. Skalierung ist damit ohne Änderung des Datenschemas möglich. Ein Shard besteht dabei aus einer eigenständigen Instanz des Datenbanksystems. Die Dokumente einer Sammlung werden also auf mehrere Datenbankinstanzen verteilt. Eine Zuordnung zum Shard erfolgt anhand eines Shard-Schlüssels, der aus einem oder mehreren Feldern des Datensatzes besteht. Die Verteilung der Daten auf Shards erfolgt in Blöcken (Chunks). Die Blöcke sind ein Tripel aus Sammlung sowie minimalem und maximalem Wert des Shard-Schlüssels. Zusätzlich zu den Shard-Instanzen benötigt MongoDB daher eine zentrale Instanz mit den Konfigurations- und Verwaltungsdaten. Auf dieser Konfigurationsinstanz wird unter anderem gespeichert, auf welcher Shard-Instanz die jeweiligen Datensätze abgelegt sind.

Die Shard-Instanzen und die zentrale Instanz laufen im Wirkbetrieb in der Regel auf verschiedenen Maschinen. Die Applikation greift nicht direkt darauf zu, sondern auf einen Proxy, der mithilfe der Konfigurationsinstanz die Anfragen an die Shards weiterleitet und – sofern die Anfrage nicht einem Shard alleine zugeordnet werden konnte – die Antworten zusammenführt und an die Applikation zurückgibt.

 

Zugriffe werden im Hinblick auf Shards nach globalen und zielgerichteten Zugriffen unterschieden. Globale Zugriffe erfolgen dann, wenn in der Anfrage nicht der vollständige Shard-Schlüssel bestimmt ist. Die Abfrage wird in diesem Fall an alle Shards weitergeleitet. Ist jedoch der Shard-Schlüssel in der Anfrage bestimmt, wird die Anfrage zielgerichtet an den betroffenen Shard bzw. die betroffenen Shards gesendet. Mit dem Begriff der Lokalität wird beschrieben, inwieweit Dokumente auf einem Shard abgelegt werden. Häufig zusammenhängend abzurufende Daten werden bevorzugt mit hoher Lokalität auf Shards abgelegt, Datensätze, auf die gleichzeitig schreibend zugegriffen wird, sollten dagegen, wegen der Shard-bezogenen Sperre für Schreiboperationen, möglichst gleichmäßig auf viele Shards verteilt werden.

Hohe Lokalität wird für Datensätze angestrebt, von denen erwartet wird, dass sie gemeinsam abgerufen werden. Eine entsprechende Auswahl des Shard-Schlüssels kann allerdings dazu führen, dass Blöcke – die anhand des Shard-Schlüssels gebildet werden – nicht mehr teilbar sind. Zu große Blöcke führen zwar nicht zu inkonsistenten oder fehlerhaften Daten, machen aber die im Hintergrund laufende, automatische Ausbalancierung der Blöcke auf den Shards teuer. Ein Lösungsansatz besteht darin, zusammengesetzte Shard-Schlüssel zu verwenden, die so aufgebaut werden, dass mit dem ersten Feld des Schlüssels gemeinsam abzurufende Datensätze zusammengefasst und mit weiteren Feldern die Teilbarkeit im Falle zu großer Blöcke ermöglicht wird. Somit kann hohe Lokalität ohne Einschränkung der Ausbalancierung realisiert werden.

Ein typischer Kandidat für den ersten Teil des Shard-Schlüssels ist die Verwendung einer Kennung (Identifier) eines Dokuments. Häufig besteht eine solche Nummer aus einer aufsteigenden Zahlenfolge, um auf einfache Weise Eindeutigkeit zu gewährleisten. Eine Schwierigkeit im Hinblick auf die Verteilung der Blöcke ist mit dem Fall verbunden, dass eine solche Kennnummer für neue Einträge sequenziell vergeben wird. Denn in diesem Fall wird ein Block nach dem anderen befüllt und bei Überschreitung der Maximalgröße geteilt und – wenn die Differenz der Blockanzahl auf den Shards zu groß wird – auf eine andere Datenbankinstanz verschoben. Weitere Zugriffe auf diese Blöcke beim Einfügen neuer Dokumente erfolgen nicht, weil neue Dokumente immer einen höheren Schlüsselwert haben. Mit anderen Worten: Der Schlüsselbereich dieses Blocks umfasst nur gerade die bereits vorhandenen Werte, obwohl der Block nur die Hälfte der zulässigen Größe aufweist. Es werden so maximal viele Blockteilungen und Blockverschiebungen ausgeführt. Außerdem werden alle aufeinander neu eingefügten Dokumente initial im gleichen Shard angelegt.

Die Verwendung der durch MongoDB als Vorgabe verwendeten, automatisch generierten Kennung ist für diese Fälle auch nicht geeignet, weil dort ein Zeitstempel mit integriert wird, der ebenso zu sequenziell aufsteigenden Kennungen führt. Ein erfolgreich angewandter Lösungsansatz ist, numerische Kennungen aus einem vorgegebenen Intervall auf eine Permutation aller Zahlen dieses Intervalls abzubilden, die in ihrer Abfolge eine möglichst gleichmäßige Verteilung innerhalb des gesamten Intervalls aufweist. Ein mathematisch etabliertes Verfahren dazu ist der lineare Kongruenzgenerator (siehe dazu auch gleichnamigen Kasten), der unter Einhaltung bestimmter Randbedingungen eine solche Permutation realisiert. Da so eine gleichmäßige Verteilung der Dokumente über den gesamten Zahlenbereich des Shard-Schlüssels erreicht werden kann, ist es möglich, vorab konfigurativ eine Verteilung der Schlüsselwerte auf die Shards zu bestimmen (Pre-Splitting). Damit kann dann eine niedrige Lokalität für nicht zusammenhängende Datensätze mit möglichst geringem Eingreifen des Mechanismus zur automatischen Ausbalancierung erreicht werden, weil die Dokumente schon initial gleichmäßig auf die Shards verteilt werden und geteilte Blöcke weiter befüllt werden können.

Info: Was ist ein Kongruenzgenerator?

Ein Kongruenzgenerator wird durch einen Algorithmus beschrieben, der beispielsweise zur Generierung von Pseudozufallszahlen genutzt werden kann. Ein linearer Kongruenzgenerator kann so parametrisiert werden, dass er eine Permutation für einen nach oben begrenzten Zahlenbereich liefert. Damit hat er gegenüber einem Hash-Algorithmus – der ebenfalls eine gute Verteilung der Werte in einem vorgegebenen Intervall ermöglicht – den Vorteil, dass Kollisionen ausgeschlossen sind. Damit erfüllt er die Voraussetzung, um zur Erzeugung einer eindeutigen Kennung genutzt zu werden. Eine Kennnung IDn wird damit wie folgt berechnet:

IDn = (IDn-1 * b + a) mod m

Wobei folgende Einschränkungen zur Wahl von a, b und m zu berücksichtigen sind:

  • m bestimmt den Definitions- und Wertebereich der Abbildung. Die Permutation erfolgt für das Intervall 0..m-1
  • b und m müssen Teiler-fremd sein. Das kann beispielsweise dadurch gewährleistet werden, dass b eine Primzahl ist
  • Alle Primfaktoren von m teilen auch a-1; in der Informatik ist es naheliegend eine Zweierpotenz für m zu wählen, dann muss a-1 durch den einzigen Primfaktor 2 teilbar, also a eine ungerade Zahl sein
  • Wenn m durch vier teilbar ist, dann muss auch a-1 durch vier teilbar sein

Außerdem muss ein beliebiger Startwert für ID0 gewählt werden.

Replikationsmechanismus – hohe Verfügbarkeit vs. Dauerhaftigkeit

Während der Ausführung von Schreiboperationen auf traditionelle DBMS – wie beispielsweise MySQL – wird der Client so lange blockiert, bis die Operation ins Journal geschrieben wurde, um auch bei Ausfall der Maschine die verlustfreie Wiederherstellung gewährleisten zu können. Mit MongoDB wird zunächst ein anderer Ansatz verwirklicht. Schreiboperationen werden an das DBMS geschickt, ohne dass der Client blockiert. Der Kontrollfluss wird umgehend zurückgegeben (fire-and-forget). Aus Sicht der Applikation bedeutet dies allerdings, dass keinerlei Aussage darüber getroffen werden kann, ob die Operation erfolgreich ausgeführt wurde, die Dauerhaftigkeit (Durability) also gewährleistet ist. Um den Erfolg einer Operation zu prüfen, steht jedoch ein Befehl zum Abruf des letzten Verarbeitungsergebnisses zur Verfügung. Dieser kann so parametrisiert werden, dass er den aufrufenden Client blockiert, bis die Daten ins Journal, auf das Speichermedium und bzw. oder auf mehrere Datenbankinstanzen im Replikationssatz (Replica Set) geschrieben wurden (siehe auch Kasten „Was ist ein Replikationssatz?“). Treiber für verschiedene Programmiersprachen führen diese Abfrage bei entsprechender Konfiguration transparent für die Applikation aus. Außerdem kann als Parameter eine maximale Wartezeit (Timeout) bestimmt werden, nach der der Kontrollfluss mit einer entsprechenden Fehlermeldung an den Client zurückgegeben wird. Bei einer solchen Zeitüberschreitung kann durch die Applikation keine definitive Aussage über die Dauerhaftigkeit getroffen werden, also ob die Operation ausgeführt werden konnte oder noch ausgeführt wird.

Die Applikation muss also zumindest mit der Situation der Zeitüberschreitung – und daraus resultierend mit einem unbestimmten Zustand der Datenbank – umgehen können. Daraus ergeben sich zwei Anforderungen an die Applikation:

  1. Nach jeder schreibenden Operation sollte ein konsistenter Datenbestand hergestellt sein, und ein auftretender Fehler darf nicht als Nichtausführung der Operation interpretiert werden
  2. Für die Fälle, in denen eine Operation der Applikation aus mehreren schreibenden Zugriffen besteht, muss die Applikation so robust sein, dass sie mit den möglichen Inkonsistenzen, die durch Abbruch während der Operation entstehen können, umgehen kann

Info: Was ist ein Replikationssatz?

Ein Replikationssatz (Replica Set) besteht aus mehreren Instanzen des Datenbanksystems, die für einen Shard – oder die Konfigurations- und Verwaltungsdaten – genutzt werden. Um eine wichtige Eigenschaft von MongoDB – nämlich Ausfallsicherheit und automatische Wiederherstellung der Betriebsbereitschaft im Falle von Hardware-Fehlern – in Anspruch zu nehmen, müssen Replikationssätze mit mindestens zwei Maschinen vorhanden sein. Die Synchronisation zwischen den Instanzen erfolgt über das Operations-Log der Primärinstanz (Primary). Durch die verzögerte Ausführung auf der Sekundärinstanz (Secondary) kann ein Replikationsrückstand (Replication Lag) entstehen. Diese Daten können im Falle eines Ausfalls der Primärinstanz nur durch manuellen Eingriff wieder eingespielt werden. Die automatische Wiederherstellung der Betriebsbereitschaft bei einem Ausfall der Primärinstanz erfolgt, indem eine der Sekundärinstanzen zur neuen Primärinstanz bestimmt wird.

Um Datenverluste und inkonsistente Daten mit sehr hoher Wahrscheinlichkeit auch im Falle des Ausfalls der Primärinstanz im Replikationssatz auszuschließen, muss nach jedem Aufruf gewartet werden, bis die Schreiboperation auch auf der Sekundärinstanz angekommen ist. Fällt tatsächlich eine Instanz aus, kann nicht mehr auf zwei Instanzen geschrieben werden. Der Client ist blockiert – und das Datenbanksystem ist faktisch nur noch lesend zugreifbar. Um die Applikation nicht vollständig zu blockieren, ist eine dritte vollständig synchrone Datenbankinstanz im Replikationssatz erforderlich, die im Falle eines Ausfalls einspringen kann. Um Fehler wegen Zeitüberschreitung zu vermeiden, muss unbedingt gewährleistet werden, dass Abfragen auf allen Instanzen, auf die gewartet werden soll, umgehend verarbeitet werden können. Das heißt insbesondere, dass diese Instanzen synchron laufen müssen, also kein nennenswerter Replikationsrückstand bestehen darf.

Zu beobachten war, dass bei aktivem automatischem Ausbalancieren der Shards und hoher Last durch die Applikation die Synchronität der Instanzen eines Replikationssatzes nicht durchgehend gewährleistet werden konnte. Dies hängt mit den erforderlichen Locks der Datenbank zusammen, die auch beim Verschieben der Blöcke erforderlich sind. Ein Blockieren der Applikation bis zum Schreiben auf zwei Instanzen hat in diesem Fall großen Einfluss auf die Performanz der Applikation. Die Locks für lesende und schreibende Datenbankzugriffe werden aktuell grundlegend überarbeitet. Gegebenenfalls stellt sich die Situation nach Weiterentwicklung von MongoDB grundlegend anders dar. Nichtsdestotrotz kann vergleichbare Ausfallsicherheit wie mit einem herkömmlichen DBMS realisiert werden mit der Möglichkeit, jederzeit eine Sicherung des Datenbestands zu einem definierten Zeitpunkt erstellen zu können. Mit Replikationssätzen aus drei Instanzen können dann folgende Anforderungen realisiert werden: Primär- und eine Sekundärinstanz stehen ausschließlich für den Wirkbetrieb zur Verfügung, um auch im Falle eines Ausfalls die Betriebsbereitschaft mit möglichst wenig Datenverlust wieder herstellen zu können. Schreibende Zugriffe blockieren die Applikation, bis die Operationen im Journal der Primärinstanz gespeichert wurden, um Dauerhaftigkeit zu erreichen. Eine zweite Sekundärinstanz kann jederzeit aus der Synchronisation genommen werden, um davon eine Sicherung zu erstellen. Ein Ausfall der Primärinstanz erfordert in dieser Konfiguration einen manuellen betrieblichen Eingriff. Die automatische Wiederherstellung des Betriebszustands bei Ausfall einer Datenbankinstanz kann so nicht ohne Datenverlust erreicht werden. Dies kann aber konfigurativ unterbunden werden.

[ header = Seite 3: Funktionale Anforderung Sortierung]

Funktionale Anforderung Sortierung

MongoDB erlaubt effiziente Zugriffe auch auf sehr große Datensammlungen durch Traversierung der Dokumente mit einem Zeiger (Cursor). In verteilten Anwendungen wird der Zugriff über eine Schnittstelle auf eine prinzipiell unbeschränkte Anzahl von Entitäten durch Paginierung realisiert. Applikationsseitig werden dazu ein Startwert und eine Anzahl von Entitäten bestimmt. Dazu ist es erforderlich, dass die Entitäten über mehrere Seitenabrufe nach einem eindeutigen Kriterium sortiert sind. Und zwar so, wie sie durch die Anwendung – ggf. für Anwender – ausgegeben werden sollen. Eine effiziente Realisierung erfordert die Sortierung durch das DBMS.

MongoDB unterstützt für Zeichenketten nicht die Angabe einer Sortierreihenfolge für eine bestimmte Enkodierung des Zeichensatzes oder für lokale Besonderheiten. MongoDB sortiert binär. Das heißt insbesondere, dass Ziffern vor Großbuchstaben und diese vor Kleinbuchstaben eingereiht und auch Umlaute nicht korrekt einsortiert werden. Mit anderen Worten: Die Zeichen werden anhand ihres Codes sortiert.

Ein Lösungsansatz zur Realisierung einer Sortierreihenfolge auf der Basis der binären Sortierung besteht darin, unter Berücksichtigung der Regeln für die gewünschte alphabetische Sortierung eine Abbildung von Originalzeichen zu neuer Kodierung anzuwenden, wobei die neue Kodierung so gewählt wird, dass ein binärer Vergleich dieser Zeichen der alphabetischen Sortierfolge der Ausgangszeichen entspricht. Java kennt zur Sortierung von Zeichenketten nach verschiedenen Sortierreihenfolgen die Klasse Collator. Hiermit kann aus einer Zeichenkette eine Bytefolge generiert werden, die dann binär sortiert werden kann und zu dem gewünschten Sortierergebnis der Ausgangszeichenketten führt.

Da MongoDB eingebettete Dokumente unterstützt, besteht eine Möglichkeit darin, die Attribute, die zur Sortierung genutzt werden sollen, in einem eingebetteten Dokument sort redundant als eine solche Bytefolge abzulegen. Wenn dann applikationsseitig nach einem dieser Attribute, z. B. name, sortiert werden soll, wird die Sortierung stattdessen nach sort.name durchgeführt. Wichtig ist, dass dann ggf. auch für diese Attribute ein Index erforderlich ist. Außerdem muss applikationsseitig bei Änderung der Originalzeichenketten auch die Änderung der Bytefolge berücksichtigt werden.

Funktionale Anforderung Aggregation

Aggregationsfunktionen werden beispielsweise für statistische Zwecke benötigt, können aber auch Teil der Anwendungslogik sein. MongoDB stellt dafür ab Version 2.1 eine Programmierschnittstelle bereit. Damit ist es möglich, nach dem Ansatz von Pipes Dokumente durch mehrere Filter verarbeiten zu lassen. Bis dahin bestand die Möglichkeit, solche Funktionen mithilfe von Map-Reduce zu realisieren. Die Map-Funktion realisiert dabei eine Projektion und die Reduce-Funktion eine Aggregation. Dazu werden zwei JavaScript-Funktionen an den Server übergeben, der diese dann mit dem Interpreter serverseitig verarbeitet und das Ergebnis entweder in einer Sammlung speichert oder direkt zurückgibt. Die Ausführung mit dem JavaScript-Interpreter der MongoDB ist mit dem Nachteil verbunden, dass die Verarbeitung auf jeder Instanz des Datenbanksystems allein durch einen Thread erfolgt. Solche Abfragen können also nicht parallelisiert verarbeitet werden. Darüber hinaus kann die Verarbeitung nicht – wie sonst für rein lesende Zugriffe möglich – auf Sekundärinstanzen erfolgen. Dieser bei anderen nichtrelationalen DBMS intensiv genutzte Mechanismus kann mit MongoDB daher in der Regel nicht empfohlen werden.

Eine weitere Möglichkeit zur Aggregation besteht darin, clientseitig die Daten zu traversieren. Vorteile davon sind, dass die Anfragen an Sekundärinstanzen gerichtet werden können und den Wirkbetrieb bei entsprechender Verwendung der Instanzen eines Replikationssatzes nicht beeinträchtigen. Bei sehr großen Datenbeständen und wenn dazu keine Indexe für diese Abfragen bereitgestellt werden können, kommt es dann aber leicht zu Verbindungsabbrüchen wegen zu langer Verarbeitungsdauer. Die Abbrüche können durch den Abruf in Blöcken vermieden werden. Dieser Ansatz ist für statistische Zwecke anwendbar, weil hier die Zeit zur Ausführung ein untergeordnetes Kriterium darstellt.

Gerade für solche aggregierten Daten, die im Rahmen der Anwendungslogik benötigt werden, gibt es eine dritte Möglichkeit. Dazu werden zusätzliche redundante Attribute geführt, die Information zu anderen Dokumenten aggregieren. Nachteilig ist zum einen, dass bei Änderungen der Originaldokumente ggf. auch die aggregierten Attribute aktualisiert werden müssen und damit zusätzliche Datenbankzugriffe erforderlich sind. Zum anderen muss in diesem Fall durch die Applikationslogik die Datenkonsistenz gewährleistet werden. Ein wichtiges Mittel, um Fehler durch Nebenläufigkeit zu vermeiden, sind die gezielten Aktualisierungen von Dokumentattributen mit Operatoren, die auf aktuelle Attributwerte angewendet werden oder diese überschreiben. Ein Beispiel ist ein Attribut, in dem die Anzahl der referenzierenden Dokumente gespeichert ist. Wird ein neues referenzierendes Dokument generiert, kann eine gezielte Aktualisierung auf genau dieses Attribut des referenzierten Dokuments mit einem Inkrement-Operator ausgeführt werden. Serverseitig wird der aktuelle Wert dann um den übergebenen Wert erhöht – in diesem Fall um eins. Im Falle von Nebenläufigkeit wäre es dagegen problematisch, zuerst den Wert auszulesen, diesen clientseitig zu aktualisieren und anschließend wieder zu schreiben. Mit den serverseitig ausführbaren Operatoren besteht diese Problematik nicht. Aggregierte Felder sind empfehlenswert, wenn die enthaltene Information in Anwenderoperationen erforderlich ist, weil sie dann nicht zur Laufzeit erst berechnet werden muss.

Fazit

MongoDB kann als dokumentenorientiertes DBMS die Erwartungen an hohe Performanz erfüllen. Erkauft wird dies durch den Verzicht auf ein Datenschema in Normalform und damit durch zusätzliche Datenredundanz. Eine Herausforderung stellt der Umstand dar, dass für die Applikation nicht immer der tatsächliche Datenbankzustand bekannt ist, weil die Dauerhaftigkeit nicht in allen Fällen gewährleistet werden kann. Aus diesen beiden Punkten und aus der Einführung aggregierter Datenfelder resultieren höhere Anforderungen an die Applikation, die Konsistenz der Daten zu gewährleisten beziehungsweise mit inkonsistenten Daten umzugehen. Außerdem müssen bei der Entwicklung des Datenmodells andere Prinzipien als bei relationalen DBMS zugrunde gelegt werden. Von Entwicklern erfordert dies einerseits ein Umdenken, und andererseits stellt es höhere Anforderungen an die Entwicklung der Applikation, weil die sonst teilweise durch das DBMS gewährleistete Datenkonsistenz allein durch die Applikation garantiert werden muss.

Was die Skalierbarkeit angeht, kann MongoDB die Erwartungen erfüllen. Das transparente Sharding leistet neben einer Lastverteilung auch einen Beitrag zur guten Performanz. Bei Berücksichtigung der oben angeführten Aspekte kann eine ausgewogene Lastverteilung mit Sharding erreicht werden. Es kommt dabei darauf an, Shard-Schlüssel – auch im Hinblick auf die Verteilung der Schlüsselwerte – sorgfältig auszuwählen beziehungsweise Kennungen geeignet zu generieren.

Hohe Verfügbarkeit durch automatische Wiederherstellung kann auch durch MongoDB derzeit nur zum Preis von Performanzeinbußen oder der Inkaufnahme von vermeidbarem Datenverlust erreicht werden. Die zum automatischen Wiederherstellen eines lauffähigen Replikationssatzes erforderliche Garantie, dass Schreiboperationen auch auf der Sekundärinstanz geschrieben worden sind, konnte bei aktiver automatischer Ausbalancierung nur mit Inkaufnahme langer Antwortzeiten realisiert werden. Anforderungen an Verfügbarkeit, Performanz und Dauerhaftigkeit sind also abzuwägen.

Insgesamt stellt MongoDB überzeugende Ansätze zu den oben angeführten Anforderungen Performanz, Skalierbarkeit und Verfügbarkeit dar. Als verhältnismäßig neue Technologie ist MongoDB jedoch noch nicht überall gleich ausgereift. Nicht zuletzt mit Unterstützung durch 10gen konnte MongoDB dennoch erfolgreich eingeführt werden

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -