NoSQL-Datenbank MongoDB Datenbanken Gigantische Datenbank Die humongous database oder kurz MongoDB hat einen einprägsamen Namen und ist eine vielversprechende NoSQL-Datenbank. MongoDB möchte die Lücke zwischen Key-Value-Stores (die schnell und hochskalierbar sind) und traditionellen relationalen Datenbanken (die mächtige Queries und viel Funktionalität bieten) schließen. Im folgenden Artikel wird diese skalierbare, hochperformante und dokumentenorientierte Datenbank vorgestellt. von Maximilian Weber ongoDB ist eine dokumentenorientierte Datenbank, die mit JSON-ähnlichen Dokumenten arbeitet. Auf Grund des nichtrelationalen Datenmodells wird MongoDB auch als NoSQL-Datenbank bezeichnet. Neben dokumentenorientierten Datenbanken gelten als Hauptkategorien für NoSQL-Datenbanken auch Key-ValueStores, BigTable-Klone und Graphendatenbanken. Beim Vergleich der verschiedenen Ansätze sind zwei Punkte entscheidend: die Größe der zu erwartenden Datenmenge und die Komplexität des Datenmodells [1]. Dabei gilt, je einfacher das Datenmodell, desto eher ist eine Datenbank in der Lage, bei sehr großen Datenmengen zu skalieren. KeyValue-Stores und BigTable-Implementierungen haben ein simples Datenmodell und können dadurch Daten relativ einfach horizontal partitionieren, skalieren also sehr gut bei der Datenmenge. Dokumenten- und Graphendatenbanken (siehe S. 74) haben sich hingegen für ein reicheres Datenmodell entschieden. Artikelserie Teil 1: Was ist MongoDB? Teil 2: Java-MongoDB-Treiber www.JAXenter.de Die Skalierbarkeit für die Datenmenge nimmt dadurch leicht ab, während die Skalierbarkeit für die Komplexität der Daten zunimmt. Komplexere Objektmodelle können auf die beiden letzteren Datenmodelle also besser abgebildet werden. Daten werden bei MongoDB in JSON-ähnlichen Dokumenten abgelegt. Listing 1 zeigt exemplarisch ein solches JSON-Dokument, das einen Blogeintrag repräsentieren soll. Im Allgemeinen kann JSON als Datenaustauschformat komplexe Datenstrukturen abbilden und kennt Objekte, Arrays, Zeichenketten, Zahlen, boolesche Werte und null. MongoDB arbeitet mit dem JSON-ähnlichen BSON-Format [2], was die Kurzform für Binary JSON ist. BSON ist eine binärkodierte Serialisierung von JSONähnlichen Dokumenten und kennt weitere Datentypen, wie z. B. Datumswerte. Es wird sowohl für die Netzwerkkommunikation als auch für die Speicherung der Dokumente auf dem Dateisystem verwendet. Im Folgenden werden die JSONBestandteile wie z. B. in Listing 1 title als Schlüssel und Gigantische Datenbank als Wert bezeichnet, diese wiederum bilden zusammen eine Eigenschaft. MongoDB kann Dokumente wie in Lis- ting 1 in Form von BSON-Dokumenten speichern. Das Datum des Blogeintrags könnte dabei weiterhin ein String bleiben, oder man könnte hierfür den MongoDB-Datentyp Date verwenden. Der Datentyp Date bietet den Vorteil, dass dieser im Gegensatz zum String innerhalb eines MongoDB-Queries mit einem anderem Datumswert verglichen werden kann. Solche dynamischen (ad hoc) Queries sind eine der großen Stärken der MongoDB-Datenbank. Prinzipiell kann innerhalb eines Queries [3] nach jeder Eigenschaft eines BSON-Dokumentes gefiltert werden. Für das Beispiel in Listing 1 wäre es also ohne Weiteres möglich, aus einer MongoDB alle Blogeinträge herauszufiltern, Listing 1 { "title": "Gigantische Datenbank", "tags" : ["MongoDB","NoSQL"], "date" : "Sun Feb 01 2009 01:00:00 GMT+0100 (CET)", "body" : "MongoDB ist eine dokumentenorientierte ..." "comments":[ {"author":"Max Mustermann", "comment":"MongoDB rocks!”} ] } javamagazin 7|2010 79 Datenbanken NoSQL-Datenbank MongoDB die mit „NoSQL“ getaggt und von Max Mustermann kommentiert worden sind. Das Anlegen eines Index [4] für die entsprechenden Eigenschaften ist dafür nicht notwendig, würde allerdings die Geschwindigkeit der Datenbankoperation steigern und empfiehlt sich daher bei häufig verwendeten Eigenschaften. MongoDB besitzt viele weitere mächtige Datenbankoperationen, die in ihrer Gesamtheit dem Funktionsumfang von SQL sehr nahe kommen. Neben dynamischen Queries unterstützt MongoDB auch Map-Reduce-Operationen. Elemente einer MongoDB Eine MongoDB kann eine oder mehrere Datenbanken enthalten, die nicht in Tabellen, sondern in Collections unterteilt. Eine Collection enthält wiederum die (BSON-)Dokumente. Die Dokumente innerhalb einer Collection sind schemafrei, d. h. sie müssen keinem bestimmten Muster folgen, wie das beim relationalen Datenbankschema der Fall ist. Wären Blogeinträge wie aus Listing 1 in einer Collection blogentries gespeichert, dann könnten sich in dieser Collection auch noch andere Dokumente mit beliebigem Dateninhalt befinden. In der Praxis macht das natürlich keinen Sinn, da sich ansonsten Queries und die Definition von Indizes schwieriger gestalten würden. Die Tatsache, dass eine Collection diese Fähigkeit hat, ermöglicht jedoch die Weiterentwicklung des „dynamischen Schemas“ eines Blogeintrags, ohne bestehende Dokumente in der Collection ändern zu müssen. Im Beispiel in Listing 1 könnte die TagFunktion erst später hinzugefügt worden sein, so muss nicht jedes Dokument eine Eigenschaft tags besitzen. mit einer kurzen Beschreibung enthalten sind. Das Zeichen „>“ zeigt an, dass in dieser Zeile ein Befehl auf der Konsole eingegeben wurde. Mit der ersten Zeile wird MongoDB mitgeteilt, dass die Datenbank webshop verwendet werden soll. Datenbanken und Collections müssen bei MongoDB nicht explizit angelegt werden, weil sie bei der ersten Verwendung automatisch erzeugt werden. In Zeile 2 wird ein Dokument mit Kundendaten in die Collection customer gespeichert. Das Dokument wird dabei in einer JSON-Syntax eingegeben. In Zeile 3 wird mit db.customer. findOne() genau ein Dokument in der Collection customer gesucht. Da keine Filterkriterien angeben wurden und der soeben angelegte Kunde das einzige Dokument innerhalb der Collection ist, wird er nun auf der Konsole angezeigt (Zeilen 4 bis 10). Hier finden sich alle zuvor eingegebenen Daten wieder, zzgl. einem automatisch hinzugefügten Schlüssel mit dem Namen _id. Der Wert des Schlüssels ist die eindeutige Kennung für ein Dokument innerhalb einer MongoDB-Collection. In Zeile 11 wird ein weiterer Kunde angelegt, der in Berlin wohnt. Anschließend wird in der Collection customer nach allen Dokumenten gesucht, die die Eigenschaft {city : Berlin} haben. Erwartungsgemäß wird nur der Kunde Otto Normal aus Berlin in der Ergebnismenge angezeigt (Zeile 13). Die Abfrage bezieht nicht nur Kundendatensätze mit ein, sondern alle Dokumente, die sich in der Collection customer befinden. Ein Dokument der Art {id: 1, city : „Berlin“} wäre also auch Teil der Ergebnismenge. In Zeile 14 wird ein Index für das Feld id (die Kundennummer) erzeugt. Die Option unique sorgt dafür, dass es die Eigenschaft mit diesem Wert nur einmal in der Collection customer geben darf. Die „1“ gibt an, dass der Index aufsteigend sein soll (mit „-1“ würde ein absteigend sortierter Index erzeugt). Eigenschaften, die sich in einem eingebetteten Objekt befinden – wie beispielsweise bankCode im Objekt bankData – können über eine Punktnotation innerhalb von Datenbankoperationen Neben dynamischen Queries unterstützt MongoDB auch Map-Reduce-Operationen. Listing 2 1 > use webshop 2 > db.customer.save({id: 4711, name: "Max Mustermann", city: "Cologne", numberOfOrders: 3}); 3 > db.customer.findOne(); 4{ 5 "_id" : ObjectId("4b9945fe1c23e16a3c0a77c1"), 6 "id" : 4711, 7 "name" : "Max Mustermann", 8 "city" : "Cologne", 9 "numberOfOrders" : 3 10 } 11 > db.customer.save({"id" : 1234, "name": "Otto Normal", city: "Berlin", numberOfOrders: 4, bankData: {accountNumber : "9876543210", bankCode : "30020011", accountHolder : "Otto Normal"} }); 12 > db.customer.find({city: "Berlin"}); 13 { "_id" : ObjectId("4b9b7bc4d9433f42225bfbb6"), "id" : 1234, "name" : "Otto Normal", "city" : "Berlin", "numberOfOrders" : 4, "bankData" : { "accountNumber" : "9876543210", "bankCode" : "30020011", "accountHolder" : "Otto Normal" } } 14 > db.customer.ensureIndex({id : 1}, {unique: true}) 15 > db.customer.update({id: 1234}, { $inc : { numberOfOrders : 1} }); 16 > db.customer.find({ numberOfOrders : { $gt: 4} }); 17{ "_id" : ObjectId("4b9947b01c23e16a3c0a77c2"), "id" : 1234, "name" : "Otto Normal", "city" : "Berlin", "numberOfOrders" : 5 } 80 javamagazin 7|2010 Quickstart Eine MongoDB ist schnell aufgesetzt. Der Quickstart [5] des Projekts macht seinem Namen alle Ehre. Ohne jegliche Installation kann aber auch unter http:// try.mongodb.org/ die MongoDB-Konsole direkt im Webbrowser ausprobiert werden. Bei der MongoDB-Konsole handelt es sich um eine interaktive JavaScript-Konsole, in der via JavaScriptKommandos mit einer MongoDBInstanz interagiert werden kann. Die Konsole findet man im Installationsverzeichnis unter bin/mongo, während die Instanz über bin/mongod gestartet wird. Ein ausführliches Beispiel Listing 2 zeigt ein Beispiel, wie man über die MongoDB-Konsole mit einer MongoDB-Instanz (mongod-Prozess) interagieren kann. Als Domäne für das Beispielszenario wurde ein Webshop gewählt. Für einen Kunden des Webshops sind jeweils der Name, eine Kundennummer (id), der Wohnort, die Anzahl der bisherigen Bestellungen und eventuell eine Bankverbindung hinterlegt. Mit dem help()-Befehl lässt sich jeweils eine Liste auf der Konsole anzeigen, in der die zur Verfügung stehenden Kommandos www.JAXenter.de NoSQL-Datenbank MongoDB Datenbanken angesprochen werden. Alle Kunden, die bei Bankverbindung die BLZ 30020011 angegeben haben, können so z. B. mit db.customer.find({"bankData.bankCode" : "30020011"}) gefunden werden. Die Zeile 15 zeigt ein so genanntes Inplace-Update. Diese Art von Operationen ist sehr effizient, da nur die angegebene Eigenschaft manipuliert wird und dafür nicht das gesamte Dokument geladen werden muss. Im Beispiel wird eine Inkrement-Operation für den Wert der Eigenschaft numberOfOrders des Kunden mit der id 1234 (Otto Normal) durchgeführt. Neben der Inkrement-Operation unterstützt MongoDB viele weitere atomare Operationen [6]. Die in Zeile 16 zu sehende Abfrage sucht alle Kunden, die mehr als vier Bestellungen getätigt haben. Durch die zuvor getätigte Inkrement-Operation ist der Kunde Otto Normal nun in der Ergebnismenge der Abfrage enthalten. Das Beispiel in Listing 2 enthält keinerlei Befehle für irgendeine Form der Transaktionssteuerung. Im Gegensatz zu relationalen Datenbanken existieren bei dem überwiegenden Teil der einer MongoDB-Datenbank. Mittels einer DBRef kann direkt zu einem verknüpften Dokument gesprungen werden, ohne dafür einen Query nach der ID des verknüpften Dokuments ausführen zu müssen. Im Webshop-Beispiel könnten so die Bankverbindungsdaten auch in einer eigenen Collection gespeichert sein und nur aus dem jeweiligen Kundendatensatz referenziert werden. Ein Punkt bei der Wahl einer NoSQL-Datenbank ist auch die horizontale Skalierbarkeit. NoSQL-Datenbanken (auch bei MongoDB) keine Transaktionen, Join-Anweisungen oder referenzielle Integrität (Fremdschlüssel). Datenbankoperationen sind bei MongoDB auf der Ebene eines einzelnen Dokuments atomar. Vergleichbar mit einem Fremdschlüssel (ohne referenzielle Integrität) bietet MongoDB die so genannten Database References [7] an. Eine DBRef ist eine Eigenschaft innerhalb eines Dokuments und verweist auf ein anderes Dokument Horizontale Skalierung In den vorangegangen Abschnitten wurden in erster Linie die Vorteile des nichtrelationalen Datenmodells einer MongoDB beleuchtet. Ein weiterer ausschlaggebender Punkt bei der Wahl einer NoSQL-Datenbank ist jedoch auch die horizontale Skalierbarkeit. Idealerweise soll ein Cluster eines NoSQLSystems dabei dynamisch um weitere Rechner wachsen können und für sehr 1/2 EM www.JAXenter.de javamagazin 7|2010 81 Datenbanken NoSQL-Datenbank MongoDB Solche Systeme sind dann in der Regel „Eventually Consistent“ [10]. Auto Sharding Abb. 1: MongoDB Auto-Sharding große Datenmengen skalieren. Ein solches System soll möglichst folgende Eigenschaften erfüllen: ■■ Consistency – Alle Knoten des Clusters sehen zum selben Zeitpunkt die gleichen Daten, auch bei Datenänderungen. ■■ Availability – Beim Absturz von Knoten sollten die verbleibenden Knoten trotzdem weiterarbeiten können, dabei soll immer wenigstens eine Kopie der Daten abrufbar sein. ■■ Partition Tolerance – Das Gesamtsystem sollte seine Eigenschaften behalten, auch wenn es auf vielen Rechnern verteilt läuft. Zwecks horizontaler Skalierung bietet MongoDB das so genannte Auto Sharding an. Abbildung 1 zeigt schematisch, welche Spieler es in einem solchen Szenario gibt. Ein Shard besteht aus einem oder mehreren Servern und speichert Daten mithilfe der mongod-Prozesse (mongod ist der Hauptprozess einer MongoDB). Typischerweise werden pro Shard mehrere Server eingesetzt, um durch Replikation [11] eine höhere Verfügbarkeit zu erreichen. Shards speichern jeweils nur eine gewisse Anzahl von Chunks. Ein Chunk stellt einen zusammenhängenden Bereich von Daten (Dokumenten) dar, die zu einer bestimmten Collection gehören. Erreicht ein Chunk eine Maximalgröße, wird er in zwei neue Chunks aufgeteilt, und diese werden eventuell auf einen anderen Shard verlagert. Die Daten werden im Cluster auf Basis eines so genannten Shard Key verteilt/partitioniert. Ein Shard Key wird für eine Collection de- Szenario mit einem mongos-Prozess und nicht, wie üblicherweise, mit dem mongod-Prozess. Ein mongos-Prozess übernimmt die Koordinationsaufgaben zwischen den einzelnen Komponenten des Clusters und nutzt die Config-Server, sodass es für den Client aussieht, als würde er mit einem einzelnen System kommunizieren. An dieser Stelle ist ein kleiner Einblick in die Fähigkeiten von MongoDB sinnvoll, was Replikation und Auto Sharding angeht. Für die weitere Lektüre sei auf das MongoDB-Wiki [12] verwiesen. Auto Sharding ist zurzeit nur als Version alpha 3 freigegeben und hat kleinere Einschränkungen. Binärdaten GridFS [13] ist eine weitere Besonderheit von MongoDB. Mithilfe von GridFS ist es möglich, Dateien in eine MongoDB zu speichern. GridFS benutzt dabei eine Collection mit dem Namen files, um die Metadaten der Dateien zu speichern (Dateiname, MIME-Type usw.). Die eigentlichen Daten der Datei kommen in die Collection chunks. Wie der Name bereits vermuten lässt, werden die Dateien hier in kleine Stücke bzw. Chunks aufgeteilt, da die Größe eines BSONDokuments auf 4 MB limitiert ist. Dies ist aber kein Nachteil, da es die Chunks möglich machen, auch sehr große Dateien gut zu verwalten. Range-Operationen erlauben es zudem, beispielsweise nur Teile einer Datei zu laden. Innerhalb eines MongoDB-Clusters können die Dateien bzw. Chunks außerdem effizient auf verschiedene Datenbankserver verteilt werden. Auch MongoDB muss sich der Erkenntnis des CAPTheorems beugen. Das CAP-Theorem [8] (auch Brewers-Theorem genannt) besagt, dass ein verteiltes System stets nur zwei dieser Eigenschaften zur selben Zeit erfüllen kann. Auch MongoDB muss sich dieser Erkenntnis beugen. Welche zwei der drei CAP-Eigenschaften erfüllt werden, hängt vom jeweiligen NoSQLSystem und dessen Konfiguration ab. Einen guten Überblick gibt die Artikelserie über Distributed Consistency [9] im Blog von MongoDB. Im Standardmodus bietet MongoDB „Strong Consistency“, d. h. dass atomare Lese- und Schreiboperationen auf der Ebene eines einzelnen Dokuments unterstützt werden. Transaktionen und damit Consistency über mehrere Entitäten hinweg findet man typischerweise bei relationalen Datenbanken. Einige NoSQL-Systeme wie Amazon Dynamo oder CouchDB bieten eine höhere Availability und müssen dafür die Consistency lockern. 82 javamagazin 7|2010 finiert und ist vergleichbar mit einem Index, der sich auf mehrere Schlüssel eines (BSON-)Dokuments bezieht. Enthält eine Query einen Schlüssel, der sich im Shard Key befindet, kann MongoDB gezielt die Shards herausfinden, auf denen sich passende Dokumente befinden müssen. Andernfalls müssen alle Shards befragt werden, weshalb der Shard Key sorgfältig anhand der häufigsten Queries gewählt werden sollte. Das Konzept des Shard Key ähnelt einigen Elementen aus dem Google ­BigTable Design. Auf den Config-Server werden die Metadaten des Clusters verwaltetet, z. B. welcher Chunk sich in welchem Shard befindet. Hierbei verbindet sich der Client beim Auto-Sharding- Alternativen Die bekannteste Alternative zu MongoDB als dokumentenorientierte Datenbank ist die NoSQL-Datenbank CouchDB. Trotz eines sehr ähnlichen Datenmodells bestehen doch einige wesentliche Unterschiede zwischen den beiden Systemen. Die Tatsache, dass MongoDB in C++ geschrieben ist und nicht in Erlang wie CouchDB, ist dabei noch einer der kleineren Unterschiede. www.JAXenter.de NoSQL-Datenbank MongoDB Datenbanken Ein ausführlicher Vergleich zwischen MongoDB und CouchDB findet sich im MongoDB Wiki [14]. Fazit MongoDB ist eine vielversprechende NoSQL-Datenbank, die sich bereits in einigen großen Produktivumgebungen bewiesen hat. In der Blogosphäre sind einige Erfahrungsberichte über MongoDB im Produktiveinsatz zu finden. So beispielsweise von einigen der MongoDB-Referenzkunden [15] wie SourceForge [16], Businessinsider.com [17] und BoxedIce [18]. In die neue MongoDB-Version 1.4 sind viele zusätzliche Erfahrungen aus den zahlreichen Produktiveinsätzen eingeflossen. Einziger Wermutstropfen ist, dass das MongoDB-Auto-Sharding nur in einer frühen Version verfügbar ist. So kann zurzeit nur eine moderate Anzahl von Shards in einem MongoDB-Cluster be- trieben werden. Das Ziel ist allerdings, Cluster zu betreiben, die bis zu 1 000 Shards haben. Wie dieses Ziel erreicht wird bzw. ob alle MongoDB-Operationen auch in einem Cluster zur Verfügung stehen werden, wird die Zukunft zeigen. Der zweite Teil dieser Artikelserie wird demonstrieren, wie mithilfe des Java-MongoDB-Treibers die Web­ shopdaten in eine MongoDB gespeichert werden können. Anschließend soll erläutert werden, wie dies auch mit Scala und einer Scala-Erweiterung des MongoDB-Java-Treibers umgesetzt werden kann. Als weitere ausführliche Einführung sei auf den Vortrag [19] von Dwight Merriman – seines Zeichens CEO von 10gen, der Firma hinter dem MongoDB-Projekt – hingewiesen. 1/3 epress Maximilian Weber ist als Softwareentwickler bei der FlowFact AG in Köln tätig. In einem agilen Team arbeitet er dort an der Entwicklung des eCRM, einem CRM-System für die Immobilienbranche. Seine Themenschwerpunkte sind Architektur und testgetriebene Entwicklung. Links & Literatur [1] http://blogs.neotechnology.com/emil/2009/11/nosql-scaling-to-size-and-scalingto-complexity.html [2] http://bsonspec.org/ [3] http://www.mongodb.org/display/DOCS/Advanced+Queries [4] http://www.mongodb.org/display/DOCS/Indexes [5] http://www.mongodb.org/display/DOCS/Quickstart [6] http://www.mongodb.org/display/DOCS/Atomic+Operations [7] http://www.mongodb.org/display/DOCS/Database+References [8] http://www.royans.net/arch/brewers-cap-theorem-on-distributed-systems/ [9] http://blog.mongodb.org/post/523516007/on-distributed-consistency-part-6consistency-chart [10] http://www.allthingsdistributed.com/2008/12/eventually_consistent.html [11] http://www.mongodb.org/display/DOCS/Replica+Pairs [12] http://www.mongodb.org/display/DOCS/Sharding+Introduction [13] http://www.mongodb.org/display/DOCS/GridFS [14] http://www.mongodb.org/display/DOCS/ Comparing+Mongo+DB+and+Couch+DB [15] http://www.mongodb.org/display/DOCS/Production+Deployments [16] http://us.pycon.org/2010/conference/schedule/event/110/ [17] http://www.businessinsider.com/how-we-use-mongodb-2009-11 [18] http://blog.boxedice.com/2010/02/28/notes-from-a-production-mongodbdeployment/ [19] http://leadit.us/hands-on-tech/MongoDB-High-Performance-SQL-Free-Database www.JAXenter.de javamagazin 7|2010 83