Zum Hauptinhalt springen

Tag 13

1 Dokumentationsaufträge

1.1 Best Practices beim Datenbankdesign

1.1.1 Relational vs. Schemaless

Bei relationalen Datenbanken werden Informationen mittels SQL Joins geholt. Da MongoDB allerdings mit Documents und nicht mit Tabellen arbeitet, muss dieses Schema mittels Arrays und Nested Documents abgebildet werden.

{
"first_name": "Paul",
"surname": "Miller",
"cell": "447557505611",
"city": "London",
"location": [45.123, 47.232],
"profession": ["banking", "finance", "trader"],
"cars": [
{
"model": "Bentley",
"year": 1973
},
{
"model": "Rolls Royce",
"year": 1965
}
]
}
MERKE

Anstatt Daten in verschiedene Tabellen aufzusplitten, nutzen wir Arrays und Nested Documents um Informationen strukturiert abbilden zu können.

Wichtig: in diesem Fall keine separaten Collections erstellen

1.1.2 Embedding vs. Referencing

Um bei MongoDB FKs und PKs zu erzwingen, muss das Referencing mittels des Operators $lookup genutzt werden. $lookup kann mit einem LEFT OUTER JOIN verglichen werden.

Beispiel

Collection order erstellen:

db.orders.insertMany( [
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
{ "_id" : 3 }
] )

Collection inventory erstellen:

db.inventory.insertMany( [
{ "_id" : 1, "sku" : "almonds", "description": "product 1", "instock" : 120 },
{ "_id" : 2, "sku" : "bread", "description": "product 2", "instock" : 80 },
{ "_id" : 3, "sku" : "cashews", "description": "product 3", "instock" : 60 },
{ "_id" : 4, "sku" : "pecans", "description": "product 4", "instock" : 70 },
{ "_id" : 5, "sku": null, "description": "Incomplete" },
{ "_id" : 6 }
] )

Die Collections verknüpfen (referenzieren):

db.orders.aggregate( [
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
] )
OUTPUT
{
"_id" : 1,
"item" : "almonds",
"price" : 12,
"quantity" : 2,
"inventory_docs" : [
{ "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "pecans",
"price" : 20,
"quantity" : 1,
"inventory_docs" : [
{ "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"inventory_docs" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}

Weitere Informationen

Vor- und Nachteile Referencing

Vorteile:

  • Kleinere Documents
  • Documents können kleiner als 16MB gehalten werden (Limit von BSON)
  • Nützlich wenn nicht alle Daten oft aufgerufen werden müssen
  • Weniger Redundanzen in den Informationen

Nachteile:

  • Komplizierte Abfragen
Vor- und Nachteile Embedding

Vorteile:

  • Eine Query = alle Daten laden
  • Keine Joins
  • CRUD-Operationen sind ACID-fähig

Nachteile:

  • Query auf grosse Documents kann (bei doppelten Daten) die Performance beeinflussen

1.2 Übung Indexierung

1.2.1 Vorbereitung

Neue Datenbank phones erstellen:

use phones

Funktion importieren um Testdaten zu generieren:

    populatePhones = function(area, start, stop) {
for(var i = start; i < stop; i++) {
var country = 1 + ((Math.random() * 8) << 0);
var num = (country * 1e10) + (area * 1e7) + i;
var fullNumber = "+" + country + " " + area + "-" + i;
db.phones.insert({
_id: num,
components: {
country: country,
area: area,
prefix: (i * 1e-4) << 0,
number: i
},
display: fullNumber
});
print("Inserted number " + fullNumber);
}
print("Done!");
}

1.2.2 Daten generieren

Daten generieren:

populatePhones(800, 5550000, 5650000)

Daten kontrollieren:

db.phones.find().limit(2)
OUTPUT
[
{
_id: 88005550000,
components: { country: 8, area: 800, prefix: 555, number: 5550000 },
display: '+8 800-5550000'
},
{
_id: 28005550001,
components: { country: 2, area: 800, prefix: 555, number: 5550001 },
display: '+2 800-5550001'
}
]

1.2.3 Indices auslesen

Indices der bestehenden Collections anzeigen:

db.getCollectionNames().forEach(function(collection) {
print("Indexes for the " + collection + " collection:");
printjson(db[collection].getIndexes());
});
OUTPUT
Indexes for the phones collection:
[
{
v: 2,
key: {
_id: 1
},
name: '_id_'
}
]

1.2.4 Testen

Perfomance ohne Indices testen:

db.phones.explain("executionStats").find({display: "+1 800-5650001"})
OUTPUT
...
executionTimeMillis: 61, // Ausführzeit in Millisekunden
...
totalDocsExamined: 100000, // Anzahl Documents
...
stage: 'COLLSCAN', // Typ des Scans
...

MongoDB Stages
https://www.mongodb.com/docs/manual/reference/explain-results/

1.2.5 Index erfassen

Index erstellen

db.phones.createIndex(
{ display : 1 },
{ unique : true, dropDups : true }
)

Die Indexe sollen unique sein und dropDups entfernt Duplikate.

Perfomance nochmals mit Indices testen.

OUTPUT
...
executionTimeMillis: 1,
...
totalDocsExamined: 0,
...
stage: 'FETCH',
...

1.3 Übung Replication

1.3.1 Vorbereitung

Data-Directories für 3 MongoDB-Instanzen erstellen:

mkdir ./mongo1 ./mongo2 ./mongo3

1.3.2 Instanzen starten

Pro Instanz ein Terminal (muss offen bleiben):

mongod --replSet book --dbpath ./mongo1 --port 27011
mongod --replSet book --dbpath ./mongo2 --port 27012
mongod --replSet book --dbpath ./mongo3 --port 27013

1.3.3 Starten

Auf eine Instanz per Shell einloggen und Initialisierung für Replikation starten:

rs.initiate({
_id: 'book',
members: [
{_id: 1, host: 'localhost:27011'},
{_id: 2, host: 'localhost:27012'},
{_id: 3, host: 'localhost:27013'}
]
})

Status kontrollieren:

rs.status().ok

1.3.4 Insert testen

ACHTUNG

Verbindung auf gesamten Cluster muss genutzt werden.

mongo mongodb://localhost:27011,localhost:27012,localhost:27013/replicaSet=book

Ersten Test-Insert durchführen:

db.echo.insert({ say : 'HELLO!' })

Auf den Terminals der Instanzen sollte viel Output angezeigt werden.

Resultat des Tests überprüfen:

db.echo.find()

1.3.5 Replikation testen

Eine Instanz killen. Die anderen Instanzen sollten weiter funktionieren.

1.4 Übung Sharding

Folgende Grafik ist das Ziel:

Ziel Sharding MongoDB

1.4.1 Vorbereitung I, Config Server

Config-Ordner erstellen und Instanz starten:

mkdir ./mongoconfig
mongod --configsvr --replSet "config" --dbpath ./mongoconfig --port 27016

Auf Server einloggen:

mongosh localhost:27016

Cluster initialisieren:

rs.initiate()

1.4.2 Vorbereitung 2, Shards einrichten

Data-Verzeichnisse einrichten:

mkdir ./mongo4 ./mongo5

Zwei Instanzen als Shard-Server starten:

mongod --shardsvr --replSet "shard1" --dbpath ./mongo4 --port 27020
mongod --shardsvr --replSet "shard2" --dbpath ./mongo5 --port 27021

1.4.3 Shards initialisieren

Auf beide Shards verbinden und initialisieren:

Shard 1

mongosh localhost:27020
rs.initiate()

Shard 1

mongosh localhost:27021
rs.initiate()
info

Aktuelle Konfiguration überprüfen:

rs.status()

1.4.4 mongos (Query Router)

Mongos-Instanz starten (Verbindung mit Config-Server wird aufgebaut):

mongos --configdb config/localhost:27016 --port 27025

1.4.5 Shards hinzufügen

Auf mongos-Instanz verbinden:

mongosh localhost:27025

Shards hinzufügen:

sh.addShard("shard1/localhost:27020")
sh.addShard("shard2/localhost:27021")

Status der Shards kontrollieren:

sh.status()

1.4.6 Sharding einführen und testen

Sharding ist zwar aktiviert, aber noch nicht konfiguriert.

Auf der Datenbank populations Sharding aktivieren (auf mongos-Instanz):

sh.enableSharding("population")

Collection cities auf die Shards aufteilen. Dies mit einem Hashing auf dem Attribut country als Shardkey:

sh.shardCollection("populations.cities", { "country": "hashed" })

Neue Datensätze einfügen:

use populations

db.cities.insertMany([
{"name": "Seoul", "country": "South Korea", "continent": "Asia", "population": 25.674 },
{"name": "Mumbai", "country": "India", "continent": "Asia", "population": 19.980 },
{"name": "Lagos", "country": "Nigeria", "continent": "Africa", "population": 13.463 },
{"name": "Beijing", "country": "China", "continent": "Asia", "population": 19.618 },
{"name": "Shanghai", "country": "China", "continent": "Asia", "population": 25.582 },
{"name": "Osaka", "country": "Japan", "continent": "Asia", "population": 19.281 },
{"name": "Cairo", "country": "Egypt", "continent": "Africa", "population": 20.076 },
{"name": "Tokyo", "country": "Japan", "continent": "Asia", "population": 37.400 },
{"name": "Karachi", "country": "Pakistan", "continent": "Asia", "population": 15.400 },
{"name": "Dhaka", "country": "Bangladesh", "continent": "Asia", "population": 19.578 },
{"name": "Rio de Janeiro", "country": "Brazil", "continent": "South America", "population": 13.293 },
{"name": "São Paulo", "country": "Brazil", "continent": "South America", "population": 21.650 },
{"name": "Mexico City", "country": "Mexico", "continent": "North America", "population": 21.581 },
{"name": "Delhi", "country": "India", "continent": "Asia", "population": 28.514 },
{"name": "Buenos Aires", "country": "Argentina", "continent": "South America", "population": 14.967 },
{"name": "Kolkata", "country": "India", "continent": "Asia", "population": 14.681 },
{"name": "New York", "country": "United States", "continent": "North America", "population": 18.819 },
{"name": "Manila", "country": "Philippines", "continent": "Asia", "population": 13.482 },
{"name": "Chongqing", "country": "China", "continent": "Asia", "population": 14.838 },
{"name": "Istanbul", "country": "Turkey", "continent": "Europe", "population": 14.751 }
])

1.4.7 Kontrolle

Statistiken für Shards anzeigen:

db.cities.getShardDistribution()

1.4.8 Anwendung

Wenn jetzt ein find() mit dem explain() untersucht wird, kann man das Sharding sehen:

db.cities.find().explain()