Der Talk wurde am 11.12.2013 auf der Java User Group Karlsruhe gehalten und gibt einen Überblick und Einstieg in MongoDB aus der Sicht eines Java-Programmierers.
Dabei werden folgende Themen behandelt:
- Buzzword Bingo: NoSQL, Big Data, Horizontale Skalierung, CAP-Theorem, Eventual Consistency
- Übersicht über MongoDB
- Datenmanipulation: CRUD, Aggregation Framework, Map/Reduce
- Indexing
- Konsistenz beim Schreiben und Lesen von Daten
- Java API & Frameworks
20. Das CAP Theorem
Availability
Jede Anfrage
bekommt eine
Antwort
Consistency
Alle Knoten
haben
jederzeit die
gleichen
Informationen
Partition
Tolerance
Trotz
Knotenausfall
funktioniert
das System
21. Überblick über NoSQL Systeme
Availability
Jeder Client
kann immer
schreiben und
lesen
C
onsistency
Alle Knoten
haben
jederzeit die
gleichen
Informationen
Partition
Tolerance
Trotz
Knotenausfall
funktioniert
das System
27. ACID vs. BASE
ACID
BASE
-
-
Starke Konsistenz
Isolation
Zwei-Phasen-Commit
Komplexe Entwicklung
Zuverlässiger
Schwache Konsistenz
Verfügbarkeit
"Fire-and-forget"
Leichtere Entwicklung
Schneller
31. Open Source Datenbank
•
MongoDB ist ein Open Source Projekt
•
Auf GitHub
– https://github.com/mongodb/mongo
•
Steht unter der AGPL Lizenz
•
Gestartet und gesponsert von MongoDB
Inc. (früher: 10gen)
•
Kommerzielle Lizenzen sind verfügbar
•
Jeder darf mitmachen!
– https://jira.mongodb.org
35. Hochverfügbarkeit
• Automatische Replikation und Failover
• Unterstützung für mehrere Datenzentren
• Ausgelegt auf möglichst einfachen Betrieb
• Beständigkeit und Konsistenz der Daten
46. Anlegen einer Datenbank
// Anzeigen aller Datenbanken
> show dbs
digg 0.078125GB
enron 1.49951171875GB
// Wechsel in eine Datenbank
> use blog
// Erneutes Anzeigen aller Datenbanken
> show dbs
digg 0.078125GB
enron 1.49951171875GB
47. Anlegen einer Collection I
// Anzeigen aller Collections
> show collections
// Einfügen eines Benutzers
> db.user.insert(
{ name : “Sheldon“,
mail : “sheldon@bigbang.com“ }
)
Beim Einfügen erfolgt kein Feedback über
den Erfolg der Operation, Abfrage über:
db.runCommand( { getLastError: 1} )
48. Anlegen einer Collection II
// Anzeigen aller Collections
> show collections
system.indexes
user
// Anzeigen aller Datenbanken
> show dbs
blog 0.0625GB
digg 0.078125GB
enron 1.49951171875GB
Datenbank und Collection
werden automatisch beim
ersten Insert anlegt.
49. Lesen aus einer Collection
// Anzeigen des ersten Dokuments
> db.user.findOne()
{
"_id" : ObjectId("516684a32f391f3c2fcb80ed"),
"name" : "Sheldon",
"mail" : "sheldon@bigbang.com"
}
// Alle Dokumente einer Collection anzeigen
> db.user.find()
{
"_id" : ObjectId("516684a32f391f3c2fcb80ed"),
"name" : "Sheldon",
"mail" : "sheldon@bigbang.com"
}
50. Filtern von Dokumenten
// Filtern von bestimmten Dokumenten
> db.user.find( { name : ”Penny” } )
{
"_id" : ObjectId("5166a9dc2f391f3c2fcb80f1"),
"name" : "Penny",
"mail" : "penny@bigbang.com"
}
// Nur bestimmte Felder anzeigen
> db.user.find( { name : ”Penny” },
{_id: 0, mail : 1} )
{ "mail" : "sheldon@bigbang.com" }
51. _id
•
_id ist der primäre Schlüssel in MongoDB
•
Index auf _id wird automatisch erzeugt
•
Wenn nicht anders angegeben, handelt es
sich dabei um eine ObjectId
•
_id kann auch selbst beim Einfügen von
Dokumenten vergeben werden, jeder
einzigartige unveränderbare Wert kann
dabei verwendet werden
52. ObjectId
•
Eine ObjectId ist ein spezieller 12 Byte
Wert
•
Ihre Einzigartigkeit über den gesamten
Cluster ist durch die Zusammensetzung
garantiert:
ObjectId("50804d0bd94ccab2da652599")
|-------------||---------||-----||----------|
ts
mac pid inc
53. Cursor
// Benutzen eines Cursors für die Dokumente
> var myCursor = db.user.find( )
// Nächstes Dokument holen und Mail anzeigen
> var myDocument =
myCursor.hasNext() ? myCursor.next() : null;
> if (myDocument) { printjson(myDocument.mail); }
// Restliche Dokumente anzeigen
> myCursor.forEach(printjson);
In der Shell werden per Default
20 Dokumente angezeigt.
65. Das Aggregation Framework
•
Wurde eingeführt, um Aggregationen
ohne Map/Reduce berechnen zu können
•
Framework von Methoden & Operatoren
– Deklarativ
– Kein eigener JavaScript-Code mehr nötig
– Framework kann erweitert werden
•
Implementiert in C++
– Overhead der JavaScript-Engine wird vermieden
– Höhere Performance
67. Aggregation Pipeline
•
Verarbeitet einen Strom von Dokumenten
– Eingabe ist eine Collection
– Ausgabe ist ein Ergebnisdokument
•
Aneinanderreihung von PipelineOperatoren
– Jede Stufe filtert oder transformiert die Dokumente
– Ausgabedokumente einer Stufe sind die Eingabe-
dokumente der nächsten Stufe
73. Was ist Map/Reduce?
•
Programmiermodel aus der funktionalen
Welt
•
Framework zur
– parallelen Verarbeitung
– von großen Datenmengen
– mittels verteilter Systeme
•
Populär geworden durch Google
– Wird zur Berechnung des Suchindex verwendet,
welcher Seiten zu Keywords zuordnet (Page Rank)
– http://research.google.com/archive/mapreduce.html
75. Word Count: Problemstellung
INPUT
{
MongoDB
uses
MapReduce
}
{
There is a
map phase
}
{
There is a
reduce
phase
}
MAPPER
GROUP/SORT
REDUCER
OUTPUT
a: 2
is: 2
map: 1
Problem:
Wie häufig kommt
ein Wort in allen
Dokumenten vor?
mapreduce: 1
mongodb: 1
phase: 2
reduce: 1
there: 2
uses: 1
76. Word Count: Tweets
// Beispiel: Twitter-Datenbank mit Tweets
> db.tweets.findOne()
{
"_id" : ObjectId("4fb9fb91d066d657de8d6f38"),
"text" : "RT @RevRunWisdom: The bravest thing that men do is
love women #love",
"created_at" : "Thu Sep 02 18:11:24 +0000 2010",
…
"user" : {
"friends_count" : 0,
"profile_sidebar_fill_color" : "252429",
"screen_name" : "RevRunWisdom",
"name" : "Rev Run",
},
…
77. Word Count: Map Funktion
// Map Funktion mit Bereinigung der Daten
map = function() {
this.text.split(' ').forEach(function(word) {
// Entfernen von Whitespace
word = word.replace(/s/g, "");
// Entfernen alle Non-Word-Characters
word = word.replace(/W/gm,"");
// Finally emit the cleaned up word
if(word != "") {
emit(word, 1)
}
});
};
85. Wie lege ich Indexe an?
// Anlegen eines Index, wenn er noch nicht existiert
> db.recipes.createIndex({ main_ingredient: 1 })
// Der Client merkt sich den Index und wirft keinen Fehler
> db.recipes.ensureIndex({ main_ingredient: 1 })
* 1 für aufsteigend, -1 für absteigend
86. Was kann indexiert werden?
// Mehrere Felder (Compound Key Indexes)
> db.recipes.ensureIndex({
main_ingredient: 1,
calories: -1
})
// Arrays mit Werten (Multikey Indexes)
{
name: 'Chicken Noodle Soup’,
ingredients : ['chicken', 'noodles']
}
> db.recipes.ensureIndex({ ingredients: 1 })
87. Was kann indexiert werden?
// Subdokumente
{
name : 'Apple Pie',
contributor: {
name: 'Joe American',
id: 'joea123'
}
}
db.recipes.ensureIndex({ 'contributor.id': 1 })
db.recipes.ensureIndex({ 'contributor': 1 })
88. Wie verwalte ich Indexe?
// Auflisten aller Indexe einer Collection
> db.recipes.getIndexes()
> db.recipes.getIndexKeys()
// Löschen eines Index
> db.recipes.dropIndex({ ingredients: 1 })
// Löschen und Neuerzeugung aller Indexe
db.recipes.reIndex()
// Defaultindex auf _id
89. Weitere Optionen
•
Unique Indexe
– Nur eindeutige Werte erlaubt
•
Sparse Indexe
– Für Felder, die nicht in allen Dokumenten
vorkommen
•
Geospatial Indexe
– Zur Modellierung von Geoinformationen
•
TTL Collections
– Verfallen nach x Sekunden
90. Unique Indexe
// Der Name eines Rezepts muss eindeutig sein
> db.recipes.ensureIndex( { name: 1 }, { unique: true } )
// Erzwingen eines Index auf einer Collection mit nicht eindeutigen
// Namen – Die Duplikate werden gelöscht
> db.recipes.ensureIndex(
{ name: 1 },
{ unique: true, dropDups: true }
)
* dropDups bitte mit sehr viel Vorsicht anwenden!
91. Sparse Indexe
// Nur Dokumente mit dem Feld calories werden indexiert
> db.recipes.ensureIndex(
{ calories: -1 },
{ sparse: true }
)
// Kombination mit einem Unique Index möglich
> db.recipes.ensureIndex(
{ name: 1 , calories: -1 },
{ unique: true, sparse: true }
)
* Fehlende Felder werden im Index als null gespeichert
92. Geospatial Indexe
// Hinzufügen von Längen- und Breitengraden
{
name: ‚codecentric Frankfurt’,
loc: [ 50.11678, 8.67206]
}
// Indexierung der Koordinaten
> db.locations.ensureIndex( { loc : '2d' } )
// Abfrage nach Orten in der Nähe von codecentric Frankfurt
> db.locations.find({
loc: { $near: [ 50.1, 8.7 ] }
})
93. TTL Collections
// Die Dokumente müssen ein Datum des Typs BSON UTC haben
{ ' submitted_date ' : ISODate('2012-10-12T05:24:07.211Z'), … }
// Dokumente werden automatisch nach 'expireAfterSeconds'
// Sekunden gelöscht
> db.recipes.ensureIndex(
{ submitted_date: 1 },
{ expireAfterSeconds: 3600 }
)
94. Limitierungen von Indexen
•
Collections können nicht mehr als 64 Indexe haben.
•
Indexschlüssel können nicht größer als 1024 Byte
sein.
•
Der Name eines Index inklusive Namespace muss
kleiner als 128 Zeichen sein.
•
Abfragen können nur einen Index verwenden
– Ausnahme: Abfragen mit $or
•
Indexe verbrauchen Speichern und verlangsamen
das Schreiben von Daten
96. Vorgehensweise
1. Langsame Abfragen identifizieren
2. Mittels explain() mehr über die
langsame Abfrage herausfinden
3. Anlegen der Indexe auf den
abgefragten Feldern
4. Optimierung der Abfragen anhand
der verwendeten Indexe
97. 1. Langsame Abfragen
identifizieren
> db.setProfilingLevel( n , slowms=100ms )
n=0: Profiler abgeschaltet
n=1: Protokollieren aller Abfragen langsamer als slowms
n=2: Protokollieren aller Operationen
> db.system.profile.find()
* Die Collection profile ist eine Capped Collection und hat daher eine feste
Anzahl von Einträgen
98. 2. Benutzung von explain()
> db.recipes.find( { calories:
{ $lt : 40 } }
).explain( )
{
"cursor" : "BasicCursor" ,
"n" : 42,
"nscannedObjects” : 53641
"nscanned" : 53641,
...
"millis" : 252,
...
}
* Keine Verwendung von Plänen aus dem Cache und erneuten Ausführungen
99. 2. Metriken des Executionplans I
• Cursor
– Der Typ des Cursors. BasicCursor bedeutet, dass
kein Index benutzt wurde
• n
– Die Anzahl der passenden Dokumente
• nscannedObjects
– Die Anzahl der gescannten Dokumente
• nscanned
– Die Anzahl der untersuchten Einträge
(Indexeinträge oder Dokumente)
100. 2. Metriken des Executionplans II
• millis
– Ausführungszeit der Abfrage
• Komplette Referenz unter
– http://docs.mongodb.org/manual/reference/explain
Das Verhältnis der gescannten
zu den gefundenen
Dokumenten sollte möglichst
nahe an 1 sein!
107. Mehrere Index verwenden
// MongoDB kann nur einen Index pro Abfrage verwenden
> db.collection.ensureIndex({ a: 1 })
> db.collection.ensureIndex({ b: 1 })
// Nur einer der beiden obigen Indexe wird verwendet
> db.collection.find({ a: 3, b: 4 })
108. Zusammengesetzte Indexe
// Zusammengesetzte Indexe sind im Allgemeinen sehr effektiv
> db.collection.ensureIndex({ a: 1, b: 1, c: 1 })
// Aber nur wenn die Abfrage ein Präfix des Indexes ist…
// Diese Abfrage kann den Index nicht effektiv verwenden
db.collection.find({ c: 2 })
// …diese Abfrage hingegen schon
db.collection.find({ a: 3, b: 5 })
109. Indexe mit geringer
Selektivität
// Folgendes Feld hat nur sehr wenige eindeutige Werte
> db.collection.distinct('status’)
[ 'new', 'processed' ]
// Ein Index auf diesem Feld bringt nur sehr wenig
> db.collection.ensureIndex({ status: 1 })
> db.collection.find({ status: 'new' })
// Besser ist ein zusammengesetzter Index zusammen mit einem
// anderen Feld
> db.collection.ensureIndex({ status: 1, created_at: -1 })
> db.collection.find(
{ status: 'new' }
).sort({ created_at: -1 })
110. Reguläre Ausdrücke & Indexe
> db.users.ensureIndex({ username: 1 })
// Abfragen mit regulären Ausdrücken, die linksgebunden sind
// können den Index verwenden
> db.users.find({ username: /^joe smith/ })
// Generische Abfragen mit regulären Ausdrücken hingegen nicht…
> db.users.find({username: /smith/ })
// Ebenso nicht schreibungsunabhängige Abfragen…
> db.users.find({ username: /Joe/i })
111. Negation
// Bei Negationen können Indexe nicht verwendet werden
> db.things.ensureIndex({ x: 1 })
// z.B. bei Abfragen mit not equal
> db.things.find({ x: { $ne: 3 } })
// …oder Abfragen mit not in
> db.things.find({ x: { $nin: [2, 3, 4 ] } })
// …oder Abfragen mit dem $not Operator
> db.people.find({ name: { $not: 'John Doe' } })
115. Write Concern - Schreibmodi
•
Bestätigung durch das Netzwerk
•
Bestätigung durch MongoDB
•
Bestätigung durch das Journal
•
Bestätigung durch Secondaries
•
Bestätigung durch Tagging
120. Tagging beim Schreiben
•
Verfügbar seit Version 2.0
•
Ermöglicht stärkere Kontrolle woher Daten
gelesen und wohin geschrieben werden
•
Jeder Knoten kann mehrere Tags haben
– tags: {dc: "ny"}
– tags: {dc: "ny", subnet: „192.168", rack: „row3rk7"}
•
Erlaubt das Anlegen für Regeln für das Write
Concern pro Replikaset
•
Anpassung der Regeln ohne Codeänderung
124. Modi zum Lesen von Daten
(Seit Version 2.2)
•
Nur Primary
(primary)
•
Primary bevorzugt
(primaryPreferred)
•
Nur Secondaries
(secondary)
•
Secondaries bevorzugt
(secondaryPreferred)
•
Nähester Knoten
(Nearest)
Falls mehr als ein Knoten möglich ist, wird immer der
näheste Knoten zum Lesen der Daten verwendet. (Alle
Modi außer Primary)
130. Tagging beim Lesen
•
Ermöglicht eine individuelle Kontrolle
woher Daten gelesen werden
– z.B. { "disk": "ssd", "use": "reporting" }
•
Lässt sich mit den Standard-Lese-Modi
kombinieren
– Außer dem Modus „Nur Primary“
131. Setzen der Read Preference
// Nur Primary
> cursor.setReadPref( “primary" )
// Primary bevorzugt
> cursor.setReadPref( “primaryPreferred" )
….
// Nur Secondaries mit Tagging
> cursor.setReadPref( “secondary“, [ rack : 2 ] )
Aufruf der Methode auf dem Cursor muss
vor dem Lesen der Dokumente erfolgen
135. MongoDB Treiber
•
Ein Wire Protokoll für alle
Programmiersprachen
•
Eine Implementierung des Treibers pro
Sprache
•
Hauptaufgaben:
– Konvertierung der sprachspezifischen
Datenstrukturen nach BSON
– Generierung der ObjectId für das Feld _id
136. MongoDB Java Treiber
•
Ein JAR ohne weitere Abhängigkeiten:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.11.3</version>
</dependency>
Verfügbar auf Github:
https://github.com/mongodb/mongo-java-driver
137. Verbindungsaufbau
import com.mongodb.MongoClient;
// Default: localhost:27017
mongo = new MongoClient();
// Replica set
mongo = new MongoClient(Arrays.asList(
new ServerAddress("replicant01", 10001),
new ServerAddress("replicant02", 10002),
new ServerAddress("replicant03", 10003)
));
// Sharding: mongos server
mongo = new MongoClient("mongos01", 4711);
138. Zugriff auf Datenbank und
Collection
import com.mongodb.DB;
import com.mongodb.DBCollection;
DB db = mongo.getDB("test");
DBCollection collection =
db.getCollection("foo");
147. Jongo: Object Mapping
public class Order {
private ObjectId id;
private Date date;
@JsonProperty("custInfo") private String customerInfo;
List<Item> items;
…
}
public class Item {
private int quantity;
private double price;
@JsonProperty("desc") private String description;
…
}
148. Jongo: Abfragen
// Java driver API
MongoClient mc = new MongoClient();
DB db = mc.getDB("odm_jongo");
// Jongo API entry point
Jongo jongo = new Jongo(db);
MongoCollection orders = jongo.getCollection("order");
// no DAO needed
Iterable<Order> result =
orders.find("{"items.quantity": #}", 2).as(Order.class);
// supports projection
Iterable<X> result =
orders.find().fields("{_id:0, date:1,
custInfo:1}").as(X.class);
155. Kurzüberblick über
Spring Data MongoDB
Hersteller
Lizenz
Dokumentation
VMware / SpringSource
Apache License, Version 2.0
http://www.springsource.org/spring
-data/mongodb
Hauptmerkmale • Repository Support
• Object/Document Mapping
• Templating
156. Spring Data
CrudRepository
Spring Data
JPA
PagingAndSortingRepository
Spring Data
Neo4j
MongoReposito
ry
GraphRepos itory
MongoT em plate
JpaRepository
Spring Data
MongoDB
Neo4 jTemplate
Em bedded
JPA
JDBC
RDBMS
Spring Data
…
RES T
Mongo Java
Driver
Mongo
DB
Neo4j
…
Einheitliche Architektur für relationale
Datenbanken sowie für unterstützte NoSQL-Stores
157. Spring Data MongoDB
•
Repository Support
– Abfragen werden aus den Methodensignaturen erstellt
– Annotationen für Abfragen
•
Object-Document-Mapping
– Annotationen: @Document, @Field, @Index, …
– Klassen werden auf Collections gemappt, Javaobjekte auf
Dokumente
•
Templating
–
–
–
–
Abstraktion der Ressourcen
Konfigurierbarkeit der Verbindungen zu MongoDB
Abdeckung des Lebenszyklus einer Collection
Unterstützung für Map/Reduce & Aggregation Framework
158. Spring Data MongoDB:
Konfiguration
<!-- Connection to MongoDB server -->
<mongo:db-factory host="localhost" port="27017" dbname="test" />
<!-- MongoDB Template -->
<bean id="mongoTemplate,
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
<!-- Package w/ automagic repositories -->
<mongo:repositories base-package="mongodb" />
160. Spring Data MongoDB:
Object Mapping
public class Order {
@Id private String id;
private Date date;
@Field("custInfo") private String customerInfo;
List<Item> items; ...
}
public class Item {
private int quantity;
private double price;
@Field("desc") private String description;
...
}
161. Spring Data MongoDB:
Repository Support
public interface OrderRepository extends
MongoRepository<Order, String> {
List<Order> findByItemsQuantity(int
quantity);
List<Order>
findByItemsPriceGreaterThan(double
price);
}
162. Spring Data MongoDB:
Zusätzliche Unterstützung für…
•
Map/Reduce & das Aggregation Framework
•
Das Management von Indexen
•
Grosse Dateien mittels GridFS
•
Geoinformatische Indexe und Abfragen
•
Optimistisches Locking
164. Kurzüberblick über
Hibernate OGM MongoDB
Hersteller
Lizenz
Dokumentation
JBoss / Redhat
GNU LGPL, Version 2.1
http://www.hibernate.org/subproject
s/ogm.html
Hauptmerkmale • JPA API (Teilweise)
• JPQL Query Language
165. Hibernate OGM
• Implementiert ein Subset der JPA API
• JP-QL Anfragen werden in native
Datenbankabfragen übersetzt
• Unterstützt Infinispan, EhCache,
MongoDB
168. Hibernate OGM MongoDB:
Object Mapping
@Entity
@NamedQuery(
name="byItemsQuantity",
query = "SELECT o FROM Order o JOIN o.items i WHERE i.quantity = :quantity"
)
public class Order {
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Id private String id;
private Date date;
@Column(name = "custInfo") private String customerInfo;
@ElementCollection
private List<Item> items;
@Embeddable
public class Item {
private int quantity;
private double price;
@Column(name="desc") private String description;
...
169. Hibernate OGM MongoDB:
Aktueller Status
•
Frühes Beta-Stadium
• Aktuell nur Speichern / Mergen /
Löschen von Daten möglich
• Noch keine Unterstützung für
Abfragen
• Benutzt eine relationale API
172. Was sollte ich einsetzen?
- „Ready for production“
JPA
Hibernate
OGM
Spring Data
MongoDB
- Aktive Community
- Ältestes Framework
- Nicht so mächtig wie Spring
- Frühes Betastadium
- Diskrepanz in API
Morphia
Der „bessere“ Treiber
Jongo
JDBC
MongoDB Java Driver
- „Ready for production“
- Von MongoDB Inc. unterstützt
MongoDB