FernUniversität in Hagen Seminar 01912 Big Data Management

Werbung
FernUniversität in Hagen
Fakultät für Mathematik und Informatik
Sommersemester 2013
Seminar 01912
Big Data Management
Prof. Dr. Ralf Hartmut Güting
Fabio Valdés
Zeitplan und Inhaltsverzeichnis
Donnerstag, 18. Juli 2013
11:00 Uhr
Parallele Datenbanken
Klaus Nieswand
12:15 Uhr
Spaltenorientierte Datenbanken
Nico Geisler
13:15 Uhr
Mittagspause
14:15 Uhr
MapReduce
Johannes Unterstein
15:30 Uhr
Google File System
Simon Eiersbrock
16:45 Uhr
Kontroverse: Parallele Datenbanken vs. MapReduce
Alica Moser
Freitag, 19. Juli 2013
09:00 Uhr
HadoopDB & SQL/MapReduce
Michael Küpper
10:15 Uhr
Hive
Markus Höhnerbach
11:30 Uhr
Pig
Roland Hellwig
12:30 Uhr
Mittagspause
13:30 Uhr
Überblick: NoSQL-Datenbanken & Key-Value Stores
Steffi Uhl
14:45 Uhr
Dynamo
Jana Stehmann
16:00 Uhr
Bigtable
Felix Siegrist
Samstag, 20. Juli 2013
09:00 Uhr
CouchDB
Kristina Steiger
10:15 Uhr
MongoDB
Wiebke Wilken
11:30 Uhr
Cassandra
Jan Kristof Nidzwetzki
12:30 Uhr
Mittagspause
13:30 Uhr
H-Store & VoltDB
Anette Naffin-Rehorst
14:30 Uhr
Verabschiedung
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 1
Parallele Datenbanken
Referent: Klaus Nieswand
Big Data Management
Parallele Datenbanken
2
Inhaltsverzeichnis
1
Einleitung ................................................................................................................................ 3
2
Parallele Datenverarbeitung.................................................................................................... 3
3
4
5
6
2.1
Anforderungen an die elektronische Datenverarbeitung ................................................ 3
2.2
Warum Parallelisieren ? .................................................................................................. 3
2.3
Wichtige Begriffe............................................................................................................ 4
2.4
Parallelisierungstechniken .............................................................................................. 6
Hardwarearchitektur................................................................................................................ 7
3.1
Shared Nothing ............................................................................................................... 7
3.2
Shared Memory............................................................................................................... 7
3.3
Shared Disk ..................................................................................................................... 8
3.4
Bewertung der Architekturen.......................................................................................... 8
3.5
Parallele Komponenten ................................................................................................... 8
3.6
Virtualisierung ................................................................................................................ 9
Softwareanforderungen ......................................................................................................... 10
4.1
Allgemeine Vorrausetzungen........................................................................................ 10
4.2
Die Eignung von Datenbanksystemen .......................................................................... 10
4.3
SQL ............................................................................................................................... 10
4.4
Verteilte Datenbanken................................................................................................... 11
4.5
Partitionierung............................................................................................................... 12
4.6
Kostenmodelle .............................................................................................................. 14
Realisierungen....................................................................................................................... 16
5.1
Teradata......................................................................................................................... 16
5.2
Tandem NonStop SQL.................................................................................................. 16
5.3
Gamma .......................................................................................................................... 17
5.4
The Super Database Computer (SDC) .......................................................................... 17
5.5
Bubba ............................................................................................................................ 17
Fazit....................................................................................................................................... 18
Big Data Management
1
Parallele Datenbanken
3
Einleitung
Im Rahmen dieser Ausarbeitung sollen verschiedene Aspekte von parallelen Datenbanken betrachtet werden. In Kapitel 2 wird das Problemfeld der parallelen Datenverarbeitung allgemein
dargestellt, beginnend mit einer kurzen Beschreibung der zugrunde liegenden Ziele der elektronischen Datenverarbeitung. In Kapitel 3 werden ausgewählte Hardwarearchitekturen für die parallele Datenverarbeitung vorgestellt und bewertet. Im Anschluss werden dann in Kapitel 4 die
Softwareaspekte der parallelen Datenverarbeitung untersucht. Hierbei wird der Schwerpunkt auf
die Besonderheiten von Datenbanksystemen gelegt. Dabei werden insbesondere in Kapitel 4.5
Partitionierung viele der in den übrigen behandelten oder angesprochenen Themen nochmals
zusammenhängend und ausführlich behandelt. Im letzten Kapitel werden einige frühe Realisierungen von parallelen Datenbanksystemen beschrieben. Große Teile dieses Arbeit gehen auf
einen Artikel von David DeWitt und Jim Gray zurück [1]. Die Beschreibung der Kostenmodelle
basiert auf einem Artikel von Donald Kossmann [2].
2
2.1
Parallele Datenverarbeitung
Anforderungen an die elektronische Datenverarbeitung
Ziel der elektronischen Datenverarbeitung ist es, Aufgabenstellungen fehlerfrei zu lösen. Ein
wichtiges Qualitätskriterium ist dabei die Zeit, die benötigt wird, um die Ergebnisse für die gestellten Aufgaben zu erhalten. Daneben spielen die Kosten, die für die Erstellung und den Betrieb der benötigten Systeme anfallen, eine wesentliche Rolle.
Werden für die Spezifikation der Leistungsanforderungen Antwortzeiten definiert, können in der
Datenverarbeitung zwei unterschiedliche Verarbeitungsarten unterschieden werden. Bei der
Batchverarbeitung ist es in der Regel ausreichend, wenn die Zeitvorgaben genau eingehalten
werden. Für Nutzeranfragen hingegen stellen die Zeitvorgaben nur die Obergrenzen dar, die idealer Weise möglichst weit unterschritten werden sollten, weil so die Benutzerfreundlichkeit verbessert werden kann. Aus diesem Grund sollen meist leistungsstarke Systeme zum Einsatz
kommen. Obwohl für die Realisierung leistungsstarker Systeme das technisch Machbare ein begrenzender Faktor ist, wird in der Regel eher der Kostenfaktor entscheidend für die Auswahl der
zu realisierenden Lösungen sein. Grundsätzlich gilt, je weiter man sich den technischen Leistungsgrenzen annähert, je teurer wird der Leistungsgewinn, der sich so erzielen lässt. Dementsprechend wird es das Ziel sein, innerhalb einer vorgegebenen Kostenobergrenze das leistungsstärkste System zu finden. Im Folgenden wird erläutert, wie die parallele Datenbankverarbeitung
zur Erreichung dieses Zieles beitragen kann.
2.2
Warum Parallelisieren ?
In diesem Abschnitt werden Konzepte der zeitlichen Abarbeitung von Aufgaben in einem EDVSystem beschrieben. Dabei geht es um die Fragestellung, wie die vorhandene Hardware den einzelnen auszuführenden Prozessen zugeordnet wird. Um die Komplexität der folgenden Betrachtungen zu verringern, wird ein Rechnersystem auf drei wesentliche Komponenten reduziert. Dabei handelt es sich um den Prozessor, den Arbeitsspeicher und den Festspeicher. Eine ausführlichere Beschreibung der Hardwarearchitektur erfolgt in Kapitel 3.
Die einfachste zeitliche Abfolge bei der Abarbeitung der Aufgaben wäre eine sequentielle Anordnung. Das heißt zum Beispiel für die Datenbankverarbeitung, nachdem eine Datenbankabfrage eingegangen ist, würde diese vollständig abgearbeitet, bevor eine neue Datenbankabfrage
angenommen wird. In ähnlicher Weise lief die Datenverarbeitung in den ersten Rechnersystemen
ab. Als die Rechnersysteme leitungsfähiger wurden, und sich das Anforderungsprofil durch ver-
Big Data Management
Parallele Datenbanken
4
stärkte Nutzerinteraktion bzw. durch eine dynamischere Interaktion von Anwendungen mit den
Datenbanksystemen veränderte, hat sich daran angepasst auch die Ablaufsteuerung von Datenbankabfragen verändert. Auch wenn die Komponenten nur einfach vorhanden waren, wurde die
Ablaufsteuerung so verändert, dass das Rechnersystem in die Lage versetzt wurde, eine neue
Datenbankabfrage anzunehmen, bevor die letzte abgearbeitet war. Da eine nicht parallele Hardwarestruktur eine echte Parallelverarbeitung nicht zulässt, müssen den einzelnen Aufgaben die
vorhandenen Hardwarekomponenten abwechselnd zugeordnet werden. Gäbe es einen idealen
Rechner mit einem unendlich schnellen Prozessor, unendlich viel Speicher mit einer unendlich
großen Bandbreite und einem Festspeichersystem ohne Verzögerungen beim Datentransfer, der
darüber hinaus nicht viel kostet, würde eine Parallelisierung nicht benötigt ([1] S.88). Alle Aufgaben könnten auch auf einem solchem sequentiellen System ausreichend schnell bearbeitet
werden. Da es ein solches System nicht gibt, müssen Lösungen gefunden werden, die die oben
genannte Zielsetzung möglichst gut erfüllen.
In dem zuvor beschriebenen Fall muss die laufende Abfrage unterbrochen werden, um neue Anfragen entgegennehmen zu können. Das heißt, dass die einzelnen Aufgaben stückweise ineinander verzahn abgearbeitet werden. Damit das System aber von außen erreichbar bleibt, müssen die
einzelnen Prozesse priorisiert werden, um der Annahmebearbeitung bevorzugt die Hardware zur
Verfügung zu stellen. Durch die überlappende Verarbeitung ist es möglich die Systemkomponenten besser auszulasten. Da im Rahmen einer einzelnen Verarbeitung nicht alle Komponenten
gleichmäßig benötigt werden (zum Beispiel wird während eines Datentransfers aus dem Festspeicher in den Arbeitspeicher der Prozessor nur wenig ausgelastet), können freie Ressourcen
neu zugeordnet werden. Dadurch lässt sich für die Gesamtheit der Aufgaben ein Geschwindigkeitsgewinn erzielen. Gleichzeitig entsteht für die Verwaltung der eingegangenen Aufgaben ein
zusätzlicher Verwaltungsaufwand, der mit zunehmender Anzahl der Aufgaben ansteigt. Dadurch
verlangsamt sich die Datenverarbeitung wieder etwas.
Eine weitere Möglichkeit die Leistung zu steigern ist die Parallelisierung. Dabei werden zusätzliche Hardwarekomponenten in das System eingebunden, sodass die wesentlichen Komponenten
für die Abarbeitung der gestellten Aufgaben mehrfach zur Verfügung stehen. Dadurch entstehen
parallele Teilsysteme. Architekturen, die durch die Bereitstellung mehrfach vorhandener Komponenten eine echte Parallelverarbeitung ermöglichen, werden in Kapitel 3 beschrieben. Die eingehenden Datenbankabfragen können dann diesen Teilsystemen zugewiesen werden. Im optimalen Fall liegen unabhängige Datenbankabfragen vor. Diese können dann unabhängig von weiteren Abfragen einem der parallelen Teilsysteme zugeordnet werden. Ziel muss es also sein, einzelne Datenbankabfragen so in unabhängige Teilaufgaben zu zerlegen, dass der Grad der Parallelisierung weiter gesteigert werden kann (intraquery parallelism). Die Möglichkeiten der Parallelisierung von Datenbankabfragen werden in Abschnitt 2.4 und in Kapitel 4 weiter beschrieben.
Bei der Parallelisierung wird durch eine große Anzahl von Verarbeitungskomponenten die Leistungssteigerung erzielt. Dabei können auch leistungsschwächere Standardkomponenten verwendet werden, die ein besseres Preis-Leistungs-Verhältnis haben. Die schwächere Leistung wird
durch eine größere Anzahl von Komponenten kompensiert. Damit lassen sich kostengünstigere
Systeme zusammenstellen, als bei der Verwendung weniger teurer Spezialkomponenten.
2.3
Wichtige Begriffe
Zum Vergleich der Qualität von parallelen Systemen wurden einige Maßzahlen eingeführt ([1] S.
87). Diese Maßzahlen bewerten die Leistungsgewinne, die sich durch eine Parallelisierung von
Programmabläufen erzielen lassen. Dabei hängt der Leistungsgewinn sowohl von der Systemarchitektur und der eingesetzten Software, als auch von der speziellen Aufgabe ab. Zum Vergleich
der Effizienz von Programmabläufen auf unterschiedlichen Systemen, werden die beiden Begriffe Speedup und Scaleup definiert. Der Speedup ist das Verhältnis der Ausführungszeiten, die
Big Data Management
Parallele Datenbanken
5
zwei zu vergleichende Systeme benötigen, wenn auf ihnen die gleichen Aufgaben ausgeführt
werden.
Ausführungszeit (Problem A, System 1)
Speedup =
Ausführungszeit (Problem A, System 2)
Dieser Wert ist besonders zum Vergleich von transaktionsorientierten Systemen geeignet. Hierbei kann untersucht werden, wie sich die Ausführungszeit für eine größere Anzahl von Transaktionen verändert, wenn ein leistungsstärkeres System (großes System) eingesetzt wird. Der
Speedup wird als linear bezeichnet, wenn die Ausführungszeit um den Faktor abnimmt, um den
die Leistungsfähigkeit des Systems zunimmt. Im Falle der Parallelisierung wird der Leistungsfähigkeitsfaktor durch die Anzahl der beteiligten Systeme, bzw. die Anzahl der relevanten Komponenten bestimmt. Eine gute Parallelisierbarkeit liegt vor, wenn ein linearer Speedup erreicht
wird.
Beim Scaleup werden die Ausführungszeiten zweier Systeme verglichen, wenn die Problemgröße der Aufgabe, die auf dem großen System ausgeführt wird um den gleichen Faktor zunimmt,
um den die Leistungsfähigkeit des großen Systems zunimmt. Der Scaleup ist dann das Verhältnis
der beiden Ausführungszeiten, die die Systeme für die Bearbeitung der ihnen gestellten Aufgaben benötigen.
Ausführungszeit (Problem A, System 1)
Scaleup =
Ausführungszeit (Problem B, System 2)
Der Scaleup wird als linear bezeichnet, wenn der Quotient der beiden Ausführungszeiten eins ist.
In diesem Fall ist das große System in der Lage eine entsprechend größere Aufgabe in der gleichen Zeit zu lösen, in der das kleine System die kleine Aufgabe löst. Dieses Testverfahren ist
besonders geeignet, um das Verhalten der Systeme bei der Ausführung von einzelnen großen
Aufgaben zu untersuchen. Eine gute Parallelisierbarkeit liegt vor, wenn ein linearer Scaleup erreicht wird.
Es gibt drei Einflussgrößen, die den Zeitgewinn bei der Parallelisierung beeinflussen können.
Als Startup wird die Initialisierungsphase beim Starten einer parallelen Verarbeitung bezeichnet. In dieser Initialisierungsphase können möglicherweise noch keine Parallelisierungstechniken
zum Einsatz gebracht werden. Deshalb wird hierbei durch ein paralleles System noch keine entsprechende Beschleunigung erzielt. Nimmt der Startup einen relativ großen Anteil der Ausführungszeit ein, wird nur ein schlechter Wert für den Speedup und den Scaleup erreicht. Ein Beispiel für solch eine Initialisierungsphase könnte die Ermittlung des Ausführungsplans für einen
Datenbankverarbeitungsprozess sein.
Als Interference wird der Einfluss bezeichnet, den ein neuer Prozess auf das Laufzeitverhalten
der anderen gleichzeitig ausgeführten Prozesse hat. Da dieses jeden der anderen Prozesse betrifft, nehmen die Auswirkungen der Interference mit der Anzahl der ausgeführten Prozesse zu.
Dies führt dazu, dass, wenn die Anzahl der parallelen Prozesse zu groß wird, sich die Verarbeitungszeit im Vergleich zu einer sequentiellen Ausführung verlängert. Bei Datenbankprozessen
Big Data Management
Parallele Datenbanken
6
kann eine Hauptspeicherknappheit eine Interference hervorrufen. Mit steigender Anzahl der parallelen Datenbankprozesse steht jedem einzelnen Datenbankprozess immer weniger Hauptspeicher zur Verfügung. Wird der Hauptspeicher zu gering, müssen Daten während der Verarbeitung
häufiger ausgelagert werden. Durch diesen Zusatzaufwand verlangsamen sich die einzelnen Datenverarbeitungsprozesse.
Der Begriff Skew bezeichnet die ungleichmäßige Verteilung der Umfänge der einzelnen parallel
durchzuführenden Aufgabenteile. Haben die einzelnen Aufgabenteile sehr unterschiedliche Größen, nimmt zwar in einem parallelen System die Laufzeit mit steigender Anzahl der Aufgabenteile gegenüber der sequentiellen Verarbeitung ab, dies geschieht aber nur unterproportional. Die
besonders großen Aufgabenteile werden noch ausgeführt, wenn die kleineren Aufgabenteile
schon längst beendet sind, und bestimmen so die Gesamtlaufzeit. Da die Parallelität nicht mehr
optimal ausgenutzt wird, ist die Beschleunigung nicht mehr linear.
2.4
Parallelisierungstechniken
Merge
Sort
Sort
Sort
Sort
Sort
Scan
Scan
Scan
Scan
Scan
Data
Data
Data
Data
Data
Abb. 1 a) pipeline parallelism b) partitioned parallelism
In diesem Abschnitt werden Techniken zur Parallelisierung von Aufgaben beschrieben ([1]
S. 86). Pipeline parallelism ist die überlappende (parallele) Ausführung aufeinanderfolgender
Verarbeitungsschritte (Abb. 1a). Teilt sich eine Verarbeitung in mehrere Verarbeitungsschritte
auf, wobei das Ergebnis des vorhergehenden Arbeitsschrittes die Ausgangswerte für den folgenden Arbeitsschritt liefert, können die Verarbeitungsschritte überlappend erfolgen, wenn der Ausgangsdatenstrom des vorhergehenden Arbeitsschrittes kontinuierlich während der Verarbeitung
erzeugt wird, und dieser Datenstrom der Folgeverarbeitung auch kontinuierlich zugeführt werden
kann. Ein Beispiel, bei der pipeline parallelism sehr effektiv eingesetzt werden kann, wäre eine
Selectoperation auf die eine Sortieroperation folgt. Jeder Datensatz, der bei der Selectoperation
gefunden wird, kann sofort der Sortieroperation zugeführt werden, die dann die eingehenden
Datensätze auch sofort verarbeitet. Bei günstigen Konstellationen dauert die Ausführung der
beiden Operationen kaum länger, als die Ausführung der aufwendigeren der beiden Operationen
alleine. Ungeeignet für pipeline parallelism sind Verarbeitungen, bei denen der Datenstrom des
vorhergehenden Arbeitsschrittes erst am Ende der Operation bereitgestellt werden kann. Dies
kann zum Beispiel bei einer Gruppierung der Fall sein, wenn das Ergebnis erst dann vorliegt,
wenn alle Datensätze durchsucht worden sind. Hier ist die Überlappungsphase in der die beiden
Verarbeitungsschritte tatsächlich parallel ausgeführt werden können nur relativ kurz, und damit
auch die Beschleunigung durch die Parallelverarbeitung nur sehr gering.
Big Data Management
Parallele Datenbanken
7
Bei einer anderen Form der Parallelisierung, dem partitioned parallelism, erfolgt die parallele
Verarbeitung unabhängiger Datenmengen oder Teilmengen (Abb. 1b). Da es keine Abhängigkeiten zwischen den Datenmengen gibt, können Operationen auf diesen Datenmengen autonom und
damit parallel auf verschiedenen Systemteilen erfolgen. Gegebenfalls ist es erforderlich größere
Datenmengen zu zerlegen (partitionieren), um dadurch diese unabhängigen Mengen zu erhalten.
Dabei kann eine Zerlegung auch aufgabenspezifisch sein. Auf das Thema Partitionierung wird in
Abschnitt 4.5 Partitionierung gesondert eingegangen.
Beide Arten der Parallelisierung können auch ggf. kombiniert werden. Das heißt, bei den einzelnen Teilmengen des partitioned parallelism kommt pipeline parallelism zum Einsatz.
Die Beschreibung dieser Parallelisierungstechniken zeigt auf, inwieweit sich Aufgaben strukturell zerlegen und anordnen lassen, um eine Parallelverarbeitung zu ermöglichen. Ob dies bei der
Ausführung auch sinnvoll anzuwenden ist, hängt von den zur Verfügung stehenden Ressourcen
ab. Auf diesen Punkt wird weiter in Abschnitt 4.6 Kostenmodelle eingegangen.
3
Hardwarearchitektur
Prozessor
Arbeitsspeicher
Festspeicher
Netzwerk
Abb. 2 a) Shared Nothing b) Shared Memory c) Shared Disk
3.1
Shared Nothing
In diesem Kapitel werden verschiedene Varianten zur Bereiststellung von parallelen Hardwareressourcen beschrieben ([1] S. 88-90). Dabei werden insbesondere die drei in Abschnitt 2.2 bereits vorgestellten Komponenten betrachtet. Ein einzelnes System besteht aus den Komponenten
Prozessor, Hauptspeicher und Festspeicher. Der Begriff Festspeicher fasst hier alle Komponenten zusammen auf denen Daten nichtflüchtig gespeichert werden können. In den meisten Fällen
werden Festplatten als Festspeicher eingesetzt. Es können aber auch andere Speicherkomponenten, wie zum Beispiel Flashspeicher, verwendet werden.
Bei der Shared Nothing Architektur werden mehrere einzelne Systeme zu einem größeren Gesamtsystem verbunden (Abb. 2a). Jedes System verfügt über einen eigenen Prozessor, eigenen
Hautspeicher und eigenen Festspeicher. Das jeweilige System kann nur auf seine eigenen Komponenten direkt zugreifen. Verbunden werden diese Systeme durch eine Netzwerkkomponente.
Über dieses Netzwerk wird auf einer höheren Ebene kommuniziert. Das heißt, es können Anfragen zwischen den einzelnen Systemen ausgetauscht werden, und Ergebnisse werden zurückgeliefert. In ähnlicher Weise erfolgt die Kommunikation in einer Client-Server-Struktur. Das System,
das eine Anfrage stellt, nimmt dabei die Funktion des Clients ein, während das System, das die
Anfrage bearbeitet, als Server fungiert. Jedes System kann dabei aufgabenabhängig jeweils eine
der beiden Funktionen annehmen. Bei dieser Struktur werden alle Komponenten vervielfacht.
3.2
Shared Memory
Bei einer Shared Memory Architektur werden nur die Prozessoren vervielfacht (Abb. 2b). Das
System wird dadurch vergrößert, dass mehrere Prozessoren eingebaut werden. Diese greifen gemeinsam auf den Hauptspeicher zu. Bei einer solchen Architektur müssen sich die Prozessoren
Big Data Management
Parallele Datenbanken
8
den Hauptspeicher teilen. Dies bedeutet aber nicht, dass einzelne Speicherbereiche einzelnen
Prozessoren fest zugeordnet werden. Vielmehr wird der Hauptspeicher den Prozessoren dynamisch zugeordnet. Dies hat zur Folge, dass die Speichergrößen dem Speicherbedarf angepasst
werden können. Es ist auch möglich über die Speicherinhalte direkt zu kommunizieren. Das
heißt, Prozessoren können gemeinsam auf Speicherbereiche zugreifen, wenn sie gezielt zusammenarbeiten, und so gemeinsam nutzbare Daten produzieren. Das Netzwerk wandert somit eine
Ebene tiefer. Der Hauptspeicher ist an den Festspeicher angebunden. Es werden die für alle Prozessoren benötigten Daten zwischen diesen beiden Komponenten ausgetauscht, wobei die einmal
in den Hauptspeicher übertragenen Daten auch von mehreren Prozessoren genutzt werden können.
3.3
Shared Disk
Bei der Shared Disk Architektur wird das System so aufgebaut, dass dem gesamten System nur
ein Festspeicher zur Verfügung steht (Abb. 2c). Das heißt, jeder Prozessor verfügt zwar über
seinen eigenen Hauptspeicher, die einzelnen Hauptspeicher sind aber an denselben Festspeicher
angebunden. In diesem Fall stehen den einzelnen Arbeitseinheiten gemeinsam die Daten des
Festspeichers zur Verfügung. Bei dieser Architektur werden Prozessoren und Hauptspeicher in
gleicher Weise vervielfacht.
3.4
Bewertung der Architekturen
Insgesamt zeichnet sich die Shared Nothing Architektur durch ihre gute Skalierbarkeit aus. Im
Grunde können beliebig viele Systeme zu einem System zusammengeschaltet werden. Da jedes
System eine vollfunktionsfähige Einheit darstellt, ist das Gesamtsystem leicht erweiterbar. Insbesondere können Strukturen realisiert werden, bei denen das Hinzufügen eines neuen Systems
nur wenige Anpassungen bei den vorhandenen Systemen erfordert. Die einzelnen verbundenen
Systeme können auch unterschiedlich konfiguriert sein, was eine Regeneration der einzelnen
Systeme wesentlich vereinfacht. Es ist möglich jedes einzelne System gesondert auszutauschen,
um es an neue technologische Standards anzupassen, wodurch auch die Regenerationszyklen
beschleunigt werden können. Zusätzlich führt die Verwendung von Standardkomponenten bei
dieser Architektur zu den größten Kostenvorteilen. Bei dieser Architektur werden durch die
Kommunikation auf einer höheren Ebene auch die geringsten Anforderungen an das Netzwerk
gestellt. Moderne Netzwerktechnologien ermöglichen es die Netzwerke an die einzelnen Systemanforderungen anzupassen und eine insgesamt leistungsfähige Verbindung sicherzustellen.
Da die Leistungsfähigkeit eines Systems stark durch seine Engpasskomponenten bestimmt wird,
sind die Verbesserungen, die durch eine Architektur mit gemeinsam genutzten Komponenten zu
erzielen sind, begrenzt, da nur Teile des Systems vervielfacht werden und somit die gemeinsam
genutzten Komponenten schnell zum Flaschenhals werden. Bei einer Architektur mit gemeinsam
genutzten Komponenten entsteht auch ein zusätzlicher Verwaltungsaufwand, der den Leistungsgewinn durch die zusätzlichen Komponenten einschränkt. Dabei nimmt die Vergrößerung des
Verwaltungsaufwandes mit jeder zusätzlichen Komponente zu, sodass ab einer bestimmten Anzahl die Leistungsfähigkeit des Systems abnimmt.
Aus den genannten Gründen ist die Shared Nothing Architektur am besten geeignet, um sehr
große parallele Systeme aufzubauen.
3.5
Parallele Komponenten
Im vorherigen Abschnitt wurde die Shared Nothing Architektur als besonders gut geeignet bewertet, um sehr große parallele Systeme aufzubauen. Es finden aber in heutigen Systemen auch
Technologien Anwendung, die eine Shared Memory oder eine Shared Disk Architektur beinhalten. Dies rührt daher, dass die Parallelverarbeitung noch da ein Potential für Leistungssteigerun-
Big Data Management
Parallele Datenbanken
9
gen bietet, wo andere Technologien bereits ausgereizt sind. Für alle der hier betrachteten Komponenten lassen sich Beispiele dafür finden.
Bei der Prozessorentwicklung konnte eine Zeit lang durch die Erhöhung der Taktraten eine Leistungssteigerung erreicht werden. Als hier die Grenzen des Machbaren erreicht wurden, wurden
Parallelisierungstechniken angewandt. Dies beinhaltete die Entwicklung von MMX-Befehlen,
die Mehrfachauslegung von Recheneinheiten, bis hin zu den Mehrkernprozessoren, die heute
schon Standard in den gängigen Rechnersystemen sind. Einige dieser Techniken werden in [3]
beschrieben.
Bei den Speicherbausteinen wurden Techniken entwickelt, bei denen mehrere Speicherbausteine
in Bänken koordiniert zusammenarbeiten (Interleaving).
Auch in modernen Festplattenplattensystemen werden Parallelisierungstechniken eingesetzt. Wo
anfangs noch durch die Verdichtung der Speicherung nicht nur eine Speichervergrößerung, sondern auch eine Geschwindigkeitsverbesserung erzielt werden konnte, werden heute einzelne
Festplatten zu Festplattensystemen zusammengeschlossen, die dann als RAID oder SAN gemeinsam agieren. Obwohl diese Systeme teilweise dazu dienen, Datenverlust zu verhindern oder
die gemeinsame Nutzung der Daten zu verbessern, können in solchen Systemen auch Technologien eingesetzt werden, die durch ganz gezielte Verteilung der Daten auf mehrere Festplatten
höhere Datentransferraten ermöglichen.
An dieser Stelle soll nicht unerwähnt bleiben, dass die höhere Komplexität der Parallelverarbeitung auch zu Tendenzen führen kann, die Parallelität zu verringern. Als Beispiel sei hier der serielle Festplattenstandard SATA genannt, der seinen parallelen Vorgänger ATA abgelöst hat,
und trotzt geringerer Parallelität größere Datentransferraten erzielt.
3.6
Virtualisierung
Eine weitere Möglichkeit einem System parallele Hardwareressourcen zuzuordnen stellt die Virtualisierung dar. Bei der Virtualisierung wird zwischen Betriebssystem und Hardware eine weitere Softwareschicht eingefügt. Das Betriebssystem wird nicht mehr direkt auf der Hardware
installiert, sondern in einer virtuellen Maschine. In dieser wird dem Betriebssystem nur virtuelle
Hardware bereitgestellt. Dort werden Hardwarekomponenten durch Software simuliert. Das Virtualisierungssystem weist dann, in der Virtualisierungsschicht, der virtuellen Maschine Teile der
vorhandenen Hardware zu. Dabei ist es möglich die Hardwarezuweisung sehr individuell zu skalieren. Wird zum Beispiel eine virtuelle Maschine auf einem Rechnercluster in einem Rechenzentrum betrieben, können einem Einkernprozessor der virtuellen Maschine sowohl eine schwächere Prozessorleistung zugewiesen werden, als auch alle in dem Cluster vorhanden Prozessoren.
Dadurch lassen sich einem herkömmlichen System auch parallele Prozessorressourcen zuordnen.
Die Zuweisung kann dabei dynamisch oder statisch erfolgen. Ähnliches ist bei den Festplattenkapazitäten möglich. So können mehrere Festplatten der virtuellen Maschine nur einer realen
Festplatte zugeordnet sein, oder eine Festplatte der virtuellen Maschine besteht in der Hardwarezuweisung aus einem schnellen, aus mehreren Festplatten bestehenden, System.
Die Virtualisierungstechnik wird in erster Linie zur Vereinfachung der Systemadministration
und zur Verbesserung der Systemauslastung eingesetzt, um eine Kostenersparnis zu erzielen und
die Ausfallsicherheit zu verbessern. Auch wenn sich normale Datenbanksysteme in solch einer
Systemumgebung betreiben lassen, ist sie für Datenbanksysteme mit hohen Leistungsanforderungen ungeeignet. Leistungsfähige Datenbankmanagementsysteme versuchen zur Leistungsoptimierung auch die Kenntnisse der Hardwarearchitektur zu verwenden. In einer virtuellen Maschine werden sie aber über die vorhandene Hardware getäuscht. Die Optimierungsbemühungen
des Datenbankmanagementsystems würden auf falschen Annahmen basieren, und damit zu
schlechten Ergebnissen führen. Insbesondere bei dynamischen Hardwarezuweisungen würden
die historischen Informationen aus den gesammelten Statistiken nicht immer zu der aktuellen
Big Data Management
Parallele Datenbanken
10
Hardwarezuweisung passen. Und selbst wenn in dem virtuellen System eine 1:1 Abbildung der
realen Hardware erfolgte, wäre die Leistung des Systems durch den zusätzlichen Verwaltungsaufwand in der virtuellen Schicht schlechter, als bei einem direkten Betrieb auf identischer
Hardware.
4
4.1
Softwareanforderungen
Allgemeine Vorrausetzungen
Damit parallele Hardware optimal genutzt werden kann, ist es erforderlich, dass die eingesetzte
Software Parallelverarbeitung unterstützt. Zwar ist es möglich, dass durch ein geeignetes Betriebssystem bereits eine herkömmliche Anwendungssoftware von den parallelen Ressourcen
profitieren kann, die dort implementierten Ablaufstrukturen könnten dieses aber stark einschränken. Wenn die Anwendungssoftware so entwickelt wurde, dass durch unnötige Abhängigkeiten
bei deren Ausführung sequentielle Reihenfolgen festgelegt sind, ist dies der Fall. Damit diese
Software die parallelen Hardwarekomponenten nutzen kann, müsste sie neu entwickelt werden.
Da dies neben den hohen Kosten mit vielen Risiken verbunden ist, ist dies ein Hauptgrund, der
gegen eine Umstellung auf parallele Systeme sprechen kann.
4.2
Die Eignung von Datenbanksystemen
Relationale Datenbanksysteme eignen sich in der Regel gut für eine Parallelisierung. Ein Grund
dafür ist, dass durch die Verwendung der nicht prozeduralen Sprache SQL der in Anwendungen
und Abfragen verwendete Programmcode unabhängig von dessen tatsächlichen Ausführung ist.
Es wird nur vorgegeben was getan werden soll, nicht aber wie es getan werden soll. Deshalb
muss der SQL-Code bei einer Systemumstellung nicht geändert werden. Nur das Datenbankmanagementsystem, dass den Code später umsetzt, muss für die Parallelverarbeitung geeignet sein.
Darüber hinaus beinhalten die bei Datenbanksystemen ablaufenden Prozesse zahlreiche Aktivitäten, die parallel ablaufen können. Auf der einen Seite stehen die lesenden Operationen, die separat betrachtet alle unabhängig von einander sind und deshalb parallel ausgeführt werden können.
Auf der anderen Seite gibt es auch bei datenändernden Operationen Prozesse, die zumindest
teilweise auf parallelen Komponenten ausgeführt werden können. Mit den Änderungen an den
eigentlichen Inhalten der Datenbank geht in der Regel auch ein Loggingprozess einher. Die Daten können auf verschiedene Festplatten geschrieben werden, wodurch ein höherer Datendurchsatz erzielt werden kann. Zur Beschleunigung der Leseprozesse werden häufig Indizes angelegt.
Auch diese können mit parallelen Komponenten gepflegt werden, wobei jeder Index separat
verwaltet werden könnte.
Eine weitere Möglichkeit zur Parallelisierung stellt die Verteilung von einzelnen Aufgaben der
Abfrageverarbeitung dar. So kann die Ermittlung geeigneter Ausführungspläne auf einem anderen Teilsystem ausgeführt werden, als die Abfrageausführung. Für jede dieser Aufgaben kann
dann die gesamte Kapazität des entsprechenden Teilsystems verwendet werden.
4.3
SQL
Wie bereits in Abschnitt 4.2 erwähnt, ist die Verwendung von SQL als standardisierte nicht prozedurale Abfragesprache ein Grund dafür, dass Datenbanksysteme gut geeignet für eine Parallelisierung sind. Im Folgenden soll dies weiter erläutert werden ([1] S. 90).
Die wichtigsten SQL-Befehle lassen sich grob in lesende und ändernde Befehle unterteilen. Zu
den lesenden Befehlen gehört der SELECT-Befehl, über den Daten aus einer oder mehreren Tabellen an Hand von vorgegebenen Auswahlkriterien ausgegeben werden können. Die Befehle
INSERT, UPDATE und DELETE stellen die wichtigsten ändernden Befehle dar. Der INSERTBefehl fügt neue Datensätze ein, der UPDATE-Befehl aktualisiert die Attribute vorhandener
Big Data Management
Parallele Datenbanken
11
Datensätze und der DELETE-Befehl löscht vorhandene Datensätze.
Durch die Verwendung von SQL Befehlen werden Relationen erzeugt, aktualisiert und abgefragt. Diese Befehle basieren auf einem einfachen Satz von Operatoren der relationalen Algebra.
Eine SELECT Operation, im Folgenden als Scan bezeichnet, ist der einfachste und meist genutzte Operator. Er erzeugt einen Zeilen- und Spaltenausschnitt aus der relationalen Tabelle. Ein
Scan der Relation R unter Verwendung des Prädikats P und der Attributliste L erzeugt einen relationalen Datenstrom als Output. Der Scan liest jedes Tupel t von R und überprüft das Prädikat
P für ihn. Die Tupel t, für die P(t) wahr ist, werden ausgewählt. Der Scan verwirft alle Attribute
von t, die nicht in L enthalten sind, und fügt den resultierenden Tupel in den Outputdatenstrom
des Scans ein. Der Outputdatenstrom eines Scans kann an einen anderen relationalen Operator
gesendet, an eine Anwendung zurückgegeben, auf einem Terminal angezeigt oder als Report
gedruckt werden. Die Uniformität der Daten und Operatoren erlaubt die Darstellung in Datenflussgraphen. Der Output eines Scans könnte an einen Sort Operator gesendet werden, der die
Daten unter Berücksichtigung eines für bestimmte Attribute vorgegebenen Sortierkriteriums anordnet.
Die Erhaltung der Datenkonsistenz ist eine wichtige Aufgabe des Datenbankmanagementsystems. Durch ein geeignetes Transaktionsmanagement soll dies erreicht werden. Auch wenn das
Transaktionsmanagement kein Schwerpunkt in dieser Ausarbeitung sein soll, sollen kurz einige
Besonderheiten erwähnt werden, da diese auch für parallele Datenbanksysteme von Relevanz
sind. Für eine weitere Betrachtung des Themas wird auf [4] verwiesen.
Parallele lesende Befehle lassen sich relativ einfach gemeinsam abarbeiten. Da lesende Befehle
sich nicht gegenseitig beeinflussen, konkurrieren sie nicht miteinander. Bei den ändernden Befehlen ist dies anders. Greifen mehrere ändernde Befehle auf die gleichen Datensätze zu, muss
ermittelt werden, wie die einzelnen Datensätze nach Ausführung aller Befehle aussehen sollen.
Dabei kann die Ausführungsreihenfolge das Ergebnis stark beeinflussen. Da Änderungsbefehle
meist auch aus den Ergebnissen vorangegangener Abfragen resultieren, können zwischenzeitlich
vorgenommene Änderungen auch hier zu Fehlern in der Datenkonsistenz führen. Auch Folgen
von Abfragen können wegen zwischenzeitlich erfolgten Änderungen zu inkonsistenten Abfrageergebnissen führen. Es ist Aufgabe des Transaktionsmanagements sämtliche Dateninkonsistenzen zu verhindern.
4.4
Verteilte Datenbanken
Verteilte Datenbanken sind Datenbanken, bei denen die Daten auf mehrere Rechnersysteme aufgeteilt sind. Eine besondere Betrachtung der verteilten Datenbanken erfolgt in [5]. Das Transaktionsmanagement stellt besondere Anforderungen an verteilte Datenbanksysteme. Auch wenn
die Daten auf verschiedene Systeme verteilt abgelegt werden, wobei Teile der Datenbanken auch
mehrfach vorhanden sein können, muss die Datenkonsistenz genauso, wie bei einem nicht verteilten Datenbanksystem, gewährleistet werden. Parallele Datenbanken sind eine Sonderform der
verteilten Datenbanken. Das heißt, dass alle Besonderheiten, die auf verteilte Datenbanken zutreffen, auch auf parallele Datenbanken zutreffen. Die entsprechenden Mechanismen für verteilte
Datenbanksysteme müssen dann auch für parallele Datenbanksysteme Anwendung finden.
Big Data Management
4.5
Parallele Datenbanken
12
Partitionierung
Prozessor
Festspeicher
Netzwerk
Hashfunktion
Daten
a-k
l-r
s-z
Daten
Abb. 3 a) round robin b) range partitioning c) hash partitioning
In Kapitel 3 wurden verschiedene Hardwarearchitekturen für parallele Datenbanksysteme untersucht. In Abschnitt 3.4 wurde die Shared Nothing Architektur als besonders geeignet zur Realisierung paralleler Datenbanken bewertet. In einer Shared Nothing Architektur werden mehrere
Systeme parallel betrieben, bei der jedes System primär nur auf seine eigenen Komponenten
zugreifen kann. Dies bedeutet, dass die Daten der Datenbanken auf die Festspeicher der einzelnen Systeme verteilt werden müssen (im Gegensatz zur einer gemeinsamen Nutzung bei einer
Shared Disk Architektur). Auf allen Festplatten zusammen müssen dann die Daten der Datenbank mindestens einmal abgebildet sein. Eine Möglichkeit wäre, dass jedes System die Daten der
gesamten Datenbank erhält. Dies wäre sogar eine akzeptable Lösung, wenn auf die Datenbank
ausschließlich lesende Zugriffe erfolgten. Zwar wäre der Festspeicher insgesamt sehr groß, jedes
System könnte aber unabhängig von den anderen Systemen die ihm zugewiesenen Anfragen
verarbeiten. Die Anfragen würden auf alle vorhandenen Systeme aufgeteilt. Die Arbeitslast würde verteilt und die Anfragen könnten schneller bearbeitete werden. Große Abfragen könnten in
kleinere unterteilt und dann auf die Systeme aufgeteilt werden. Eine Kommunikation zwischen
den Systemen müsste zur gleichmäßigen Verteilung der Anfragen auf die Systeme und zum Austausch von Teilergebnissen erfolgen.
In dem Moment, wo ändernde Abfragen hinzukommen, müssen alle Datenbestände, unter Beachtung des Transaktionsmanagements, mit großem Aufwand synchronisiert werden. Vor diesem Hintergrund ist der relativ große Festspeicherbedarf meist nicht mehr zu rechtfertigen. Auch
wenn die Datenbestände so groß werden, dass sie ein einzelnes System nicht mehr verwalten
kann, muss eine Aufteilung der Daten erfolgen. Die Datenbank wird partitioniert.
Bei den folgenden Betrachtungen werden Elemente zur Verarbeitungsoptimierung, wie zum Beispiel Indizes, aus Gründen der Vereinfachung vernachlässigt. Diese müssten aber im Rahmen
von optimalen Verarbeitungsprozessen zusätzlich berücksichtigt werden.
Eine Datenbankpartitionierung kann statisch oder dynamisch erfolgen. Bei der statischen Partitionierung werden die Daten in Teile aufgeteilt, die auch so auf dem Festspeicher abgelegt werden. Bei der dynamischen Partitionierung werden in Rahmen von Datenbankoperationen die
Daten gezielt aufgeteilt, um unabhängige Datenmengen zu erhalten, die separat weiterverarbeitet
werden können. Diese werden als Zwischenergebnisse nicht in der Datenbank abgespeichert.
Bei der statischen Partitionierung kann zwischen der horizontalen und der vertikalen Partitionierung unterschieden werden. Bei der vertikalen Partitionierung werden die Datensätze der Datentabellen einzelnen Partitionen zugeordnet. Bei der horizontalen Partitionierung erfolgt eine Aufteilung der Attribute auf die Partitionen, wobei jede Partition die Schlüsselattribute enthalten
muss, um die Datensätze wieder rekonstruieren zu können.
Bei einer räumlich verteilten Datenbank würde die Aufteilung der Daten in der Regel so erfol-
Big Data Management
Parallele Datenbanken
13
gen, dass sie an dem Ort abgespeichert werden können, an dem sie am meisten benötigt werden.
Bei einer parallelen Datenbank erfolgt die Aufteilung so, dass möglichst unabhängig verarbeitbare Datenmengen entstehen.
Die horizontale Aufteilung kann nach unterschiedlichen Regeln erfolgen. Beim round robin
Verfahren werden die Daten sequentiell auf die einzelnen Partitionen aufgeteilt (Abb. 3a). Das
heißt, dass der nächste Datensatz in die nächste Partition abgespeichert wird. Ist die letzte Partition erreicht, wird wieder mit der ersten begonnen. Durch dieses Verfahren wird eine gleichmäßige Aufteilung der Daten auf die Partitionen erreicht. Es können aber keine inhaltlichen Vorteile bei der Verarbeitung erzielt werden. Das heißt, dass aus dem Verteilungsmechanismus nicht
hervorgeht, in welcher Partition ein konkreter Datensatz gespeichert ist. In einer Datenbankabfrage müssen dann in der Regel alle Partitionen verarbeitet werden. Dieses kann aber parallel auf
verschiedenen Systemteilen erfolgen. Deshalb sollten diese Partitionen auf unterschiedlichen
Systemen gespeichert werden. Aber auch innerhalb eines Systems lassen sich Performanceverbesserungen erzielen, wenn zum Beispiel die einzelnen Partitionen auf unterschiedlichen Festplatten abgespeichert werden. Dadurch kann der Datendurchsatz des Festplattensystems bei einem schnellen Datenbus erhöht werden.
Beim range partitioning werden die Daten nach inhaltlichen Kriterien aufgeteilt (Abb. 3b).
Durch Filterkriterien auf ausgewählte Attribute der Datensätze wird die Zuordnung zu den Partitionen festgelegt. Entsprechend der BETWEEN-Klausel werden Wertebereiche definiert, die auf
die Partitionen abgebildet werden. Bei diesem Verfahren kann die Information, wie die Daten
aufgeteilt wurden, in der Abfragebearbeitung verwendet werden. Das heißt, fallen einzelne Abfragekriterien nur in den Bereich einer Partition, braucht auch nur diese Partition zur Verarbeitung herangezogen werden. Dadurch verringert sich die zu verarbeitende Datenmenge. Dieses
wirkt sich besonders stark aus, wenn Tabellen, die mittels Join verbunden werden, nach den gleichen Kriterien partitioniert werden. Werden diese Partitionen auch dementsprechend auf die Systeme verteilt, stehen die zusammen zu verarbeitenden Daten auch zusammen, und können so
effizient parallel verarbeitet werden. Nachteil dieses Verfahrens ist, dass sich die Partitionen
stark in ihrer Größe unterscheiden können. Dies kann zu einem Skew führen.
Auch beim hash partitioning erfolgt die Aufteilung der Daten nach inhaltlichen Kriterien
(Abb. 3c). Auf die Datensätze wird auf geeignete Attribute eine Hashfunktion angewandt. Die
Anzahl der Ergebnisse der Hashfunktion entspricht der Anzahl der Partitionen. Der Wert der
Hashfunktion bestimmt in welcher Partition der Datensatz abgelegt werden soll. Durch die Verwendung der Hashfunktion sollen einerseits die Datensätze gleichmäßig auf die Partitionen verteilt werden. Andererseits kann die Hashfunktion auch inhaltlich zur Optimierung der Verarbeitung genutzt werden.
Entsprechen die Aufteilungskriterien auch den Kriterien, nach denen die Daten in der Abfrageverarbeitung parallelisiert werden sollen, ist eine effiziente Verarbeitung möglich. Aber nicht
immer werden sich die Daten, die zusammen bearbeitet werden sollen, auf dem gleichen System
befinden. In diesem Fall ist es erforderlich einen Teil der Daten für die Verarbeitung auf das entsprechende System zu übertragen. Dort könnten die übertragenen Daten gegebenenfalls auch
zwischengespeichert werden, um sie weiteren Verarbeitungen zuzuführen.
In einigen Fällen können die Kriterien, nach denen die Datensätze verteilt worden sind, nicht
ausreichend für eine parallele Verarbeitung sein. Dies ist zum Beispiel der Fall, wenn nur die
Daten einer Partition in eine Verarbeitung einfließen, diese aber parallel verarbeitet werden sollen. Dann kann es sinnvoll sein die Daten dynamisch, speziell für die Verarbeitung, aufzuteilen.
Dadurch entsteht ein zusätzlicher Verarbeitungsaufwand. Dies lohnt sich, wenn der durch die
mögliche Parallelverarbeitung erzielbare Leistungsgewinn diesen Aufwand kompensiert.
Big Data Management
Parallele Datenbanken
14
Insgesamt hat die Partitionierung großen Einfluss auf die Verarbeitungsgeschwindigkeit. Die
Frage der Datenbankpartitionierung kann dabei sowohl als ein Problem der Datenbankkonzeption als auch des Datenbankbetriebes behandelt werden. Bei der Datenbankkonzeption würden die
Regeln für die Datenverteilung während der Datenbankentwicklung festgelegt. Da dafür nicht
nur Kenntnisse über die Datenstruktur erforderlich sind, sondern auch über die Datenmengen
und Dateninhalte, sowie über die erwarteten Abfragen, wird es sehr schwierig sein, eine gute
Aufteilung festzulegen. Zusätzlich besteht die Gefahr, dass sich die Gegebenheiten während des
Betriebes ändern. Dementsprechend könnte es sinnvoll sein, wenn die Partitionierung während
des Betriebes durch das Datenbankmanagementsystem vorgenommen wird. Mit während des
Betriebes gewonnenen Statistiken könnte die Aufteilung an sich ändernde Gegebenheiten angepasst werden.
4.6
Kostenmodelle
Im vorangehenden Abschnitt wurde gezeigt, wie die Daten in einer für eine Parallelverarbeitung
geeigneten Weise abgespeichert, beziehungsweise während der Abfrageverarbeitung aufgeteilt
werden können. Wie eine spezielle Abfrage dann tatsächlich ausgeführt werden soll, muss durch
den query analyser ermittelt werden. Der query analyser erstellt dafür verschiedene Ausführungspläne und bewertet den Aufwand (die Kosten), der bei der Durchführung der Ausführungspläne entstehen kann. Der Aufwand für die Ermittlung eines geeigneten Ausführungsplans sollte
dabei möglichst gering sein, da dieser die Ausführung verzögert. Deshalb wird ggf. nur ein Teil
der möglichen Ausführungspläne berücksichtigt. Da in einem parallelen System die Anzahl der
möglichen Ausführungen sehr groß sein kann, ist hier die Komplexität der Ermittlung eines geeigneten Ausführungsplans entsprechend hoch. Im Folgenden sollen die Schwierigkeiten beim
Ermitteln eines Ausführungsplans beschrieben und die Besonderheiten für die Parallelverarbeitung herausgearbeitet werden ([2] S. 429-431, 459-460).
Eine Möglichkeit zur Ermittlung eines geeigneten Ausführungsplans ist die Verwendung eines
klassischen Kostenschätzmodells. Dabei werden die Kosten jeder einzelnen Operation eines
Ausführungsplans geschätzt und aufsummiert. Die so ermittelten Kosten stellen den gesamten
Ressourcenverbrauch bei Realisierung eines Ausführungsplans dar. Der Ressourcenverbrauch
setzt sich zusammen aus den CPU-Kosten und den Kosten für den Festplatten-I/O. Dabei setzen
sich die Kosten für den Festplatten-I/O zusammen aus den Kosten für Seek, Latentz und Transfer. In einem verteilten System kommen noch die Kommunikationskosten für die Netzwerkkommunikation hinzu. Diese setzen sich wiederum zusammen aus den Fixkosten für jede Nachricht, den datenmengenabhängigen Kosten für den Datentransfer und den CPU-Kosten für die
Steuerung der Datenübertragung. Um den Zusatzaufwand für die Datenverteilung zu berücksichtigen, wird der Aufwand der vorhandenen Ressourcen durch Gewichtsfaktoren bewertet. Da der
Aufwand nicht nur von den Datenstrukturen sondern auch von den aktuellen Datenmengen abhängt, werden für die Kostenschätzung Informationen über den aktuellen Systemzustand benötigt. Diese können zum Beispiel in Form von Statistiken gewonnen werden. Das klassische Kostenschätzmodell hat in Bezug auf die Parallelisierung aber einige Schwachpunkte. In Abschnitt
4.5 wurde zum Beispiel gezeigt, dass eine Abfrageverarbeitung durch eine dynamische Partitionierung parallelisiert werden kann. Durch die Partitionierung entsteht aber ein Zusatzaufwand.
Dieser erhöht die Kosten für einen solchen Ausführungsplan, womit die Wahrscheinlichkeit,
dass dieser ausgewählt wird, abnimmt. Es wird auch nicht die Leistungsfähigkeit der einzelnen
Ressourcen und deren Auslastung berücksichtigt.
Antwort-Zeit-Modelle können diese Punkte besser berücksichtigen. Das klassische Kostenmodell, das den gesamten Ressourcenverbrauch einer Abfrage schätzt, ist geeignet den Gesamtdurchsatz eines Systems zu optimieren. Bei stark ausgelasteten Systemen können am meisten
Big Data Management
Parallele Datenbanken
15
Abfragen ausgeführt werden, wenn alle Abfragen so wenige Ressourcen wie möglich verbrauchen. Das klassische Kostenmodell betrachtet aber nicht die Parallelität innerhalb einer Abfrage,
sodass bei nur schwach ausgelasteten Systemen mit schnellem Netzwerk ein Abfrageoptimierer,
der dieses Kostenmodell verwendet, nicht unbedingt den Ausführungsplan mit der geringsten
Antwortzeit für eine Abfrage findet. Um den Ausführungsplan mit der kürzesten Antwortzeit zu
finden, muss der Abfrageoptimierer ein Kostenmodell verwenden, welches die Antwortzeit an
Stelle des Ressourcenverbrauchs schätzt. In einem solchen Modell wird zwischen pipeline parallelism und partitioned parallelism unterschieden. Dabei könnte das Modell wie folgt vorgehen.
Zuerst wird der gesamte Ressourcenverbrauch für jeden einzelnen Operator berechnet. Danach
wird für jede geteilte Ressource die gesamte Nutzung für eine Gruppe von Operatoren, die parallel ausgeführt werden, berechnet. Zum Beispiel wird die Nutzung des Netzwerkes, unter Berücksichtigung seiner Bandbreite und der Menge der zu übertragenden Daten, die bei der parallelen
Ausführung aller Operatoren entsteht, berechnet. Die Antwortzeit von einer Gruppe von Operatoren, die parallel ausgeführt werden, ergibt sich dann als das Maximum des gesamten Ressourcenverbrauchs von einem einzelnen Operator und dem Gesamtverbrauch aller geteilten Ressourcen. Dieses Kostenmodell berücksichtigt die Effekte von Operatorparallelität nur in einer groben
Weise. Zum Beispiel die Zuteilungsproblematiken, die entstehen, wenn viele Operatoren konkurrierend die gleiche Ressource nutzen wollen, werden nicht berücksichtigt. In speziellen Situationen werden suboptimale Ausführungspläne ausgewählt, obwohl der Ressourcenverbrauch für die
einzelnen Operatoren gut abgeschätzt worden ist. Ein Vorteil dieses Modells ist aber, dass sich
Ausführungspläne, wie beim klassischen Kostenmodell, sehr schnell bewerten lassen. Dies ist
besonders wichtig, wenn die Anzahl der möglichen Pläne sehr groß wird.
Einen anderen Ansatz verwenden die ökonomischen Modelle für die Verarbeitung verteilter Abfragen. Der Motivation für die Nutzung ökonomischer Kostenmodelle liegt die Idee zu Grunde,
dass verteilte Systeme zu komplex sind, um von einer zentralen Komponente mit einem einzelnen universellen Kostenmodell gesteuert werden zu können. Systeme, die auf einem ökonomischen Modell basieren, unterliegen den Gesetzen des Kapitalismus. Jeder Server, der einen
Dienst anbietet, versucht seinen eigenen Profit zu maximieren, in dem er seinen Dienst den Clients verkauft. Es wird erwartet, dass die spezifizierten Ziele aller individuellen Clients am besten
erfüllt werden, wenn alle Server so vorgehen.
Mariposa ist ein verteiltes Datenbanksystem, welches ein ökonomisches Modell verwendet. Mariposa verarbeitet Abfragen, in dem es Auktionen durchführt. In diesen Auktionen kann jeder
Server bieten, um Teile der Abfrage ausführen zu dürfen. Die Clients müssen für die Ausführung
ihrer Abfragen den gebotenen Preis zahlen. Der Ablauf sieht in etwa wie folgt aus. Zuerst legen
die Clients für die von ihnen initiierten Abfragen jeweils ein Budget fest. Das Budget für jede
Abfrage hängt von der Bedeutung der Abfrage und der Zeit ab, die der Client bereit ist auf das
Ergebnis der Abfrage zu warten. Dabei nimmt der Preis, den der Client bereit ist für die Bearbeitung der Abfrage zu bezahlen, mit steigender Wartezeit ab. Jede Abfrage wird von einem Broker
bearbeitet. Der Broker analysiert die Abfrage und generiert einen Plan, der die Join-Reihenfolge
und die Join-Methoden spezifiziert. Für diesen Schritt kann der Broker einen gewöhnlichen
Abfrageoptimierer für zentrale Datenbanksysteme verwenden. Dann startet der Broker die Auktion. Als Teil dieser Auktion gibt jeder Server, der Kopien von Teilen der abgefragten Daten
besitzt, oder bereit ist, eine oder mehrere der in dem vom Broker spezifizierten Plan enthaltenen
Operationen auszuführen, Gebote ab. Die Gebote enthalten den Operator, den Preis, die Laufzeit
und den Zeitpunkt, bis zu dem das Angebot gültig ist. Der Broker sammelt alle Angebote ein und
macht Verträge mit den Servern, die die Abfragen ausführen sollen. Dabei versucht der Broker
seinen eigenen Gewinn zu maximieren. Er berechnet die Summe der Differenzen zwischen den
Preisen, die für die Ausführung der Operationen auf den einzelnen Servern bei einer bestimmten
Laufzeit bezahlt werden müssen, und den Budgets, die von den Clients für die entsprechende
Laufzeit angesetzt worden sind. Da für eine kürzere Laufzeit das Budget höher ist, ist der Broker
Big Data Management
Parallele Datenbanken
16
auch bereit hierfür einen höheren Preis zu zahlen. Die Angebote, die dann den höchsten Gewinn
ergeben, werden ausgeführt. Reicht das Budget nicht aus, um eine Abfrage ohne Verlust auszuführen, wird diese an den Client zurückgegeben. Dieser muss dann entscheiden, ob er das Budget
erhöht, oder auf die Ausführung der Abfrage verzichtet.
Ein Vorteil von Mariposa ist, dass Server unterschiedlicher Leistungsklassen miteinander verbunden werden können. Ein Teil der Server bietet dann leistungsfähige Dienste an, während andere Server leistungsschwächere Dienste anbieten. Ein weiterer Vorteil ist, dass auch die Datenverteilung gesteuert werden kann. Wenn es für einen Server profitabel ist, kann er Kopien der
Daten bei anderen Servern kaufen, um dann Leistungen mit Gewinn anbieten zu können.
5
5.1
Realisierungen
Teradata
Im Folgenden werden einige ältere Realisierungen paralleler Datenbanksysteme beschrieben ([1]
S. 94-95). Teradata entwickelte seit 1978 zahlreiche parallele SQL Datenbankserversysteme, die
auf einer Shared Nothing Architektur basierten. Dabei kamen kommerzielle Standardhardwarekomponenten (Prozessoren, Speicher, Festplatten) zum Einsatz. Die Systeme können mehr als
tausend Prozessoren und mehrere tausend Festplatten enthalten. Die Prozessoren lassen sich in
zwei Gruppen einteilen. Die Access Module Prozessoren (AMPs) führen die Datenbankabfragen
aus. Dazu sind einem AMP mehrere Festplatten und ein großer Arbeitsspeicher zugeordnet. Die
Interface Prozessoren (IFPs) führen die Abfrageanalyse und -optimierung durch, koordinieren
die Arbeit der AMPs und steuern die Kommunikation. Die Prozessoren verbindet ein zweifach
redundantes, baumstrukturiertes Netzwerk, welches Y-Net genannt wird. Für die Speicherung
der Daten kommt hash partitioning zum Einsatz. Dabei werden die Daten in zwei Phasen den
Festplatten zugeordnet. Zuerst wird eine Hashfunktion auf den Primärschlüssel der Datensätze
angewandt, um den AMP zu bestimmen, der für die Speicherung der Daten verantwortlich ist.
Diese Hashfunktion verteilt die Daten über mehrere AMPs. Eine zweite Hashfunktion legt den
Ort innerhalb der Festplatten eines AMPs fest, an der die Daten abgespeichert werden. Die Daten
werden in Reihenfolge des Hashkeys abgelegt.
Die Teradata Systeme erreichen bei der Bearbeitung relationaler Abfragen einen fast linearen
Speedup und einen fast linearen Scaleup.
5.2
Tandem NonStop SQL
Das Tandem NonStop SQL System besteht aus durch 4-plexed Glasfaserringe verbundene Prozessorcluster. Bei diesem System werden die Anwendungen auf den gleichen Prozessoren und
dem gleichen Betriebssystem ausgeführt wie die Datenbankserver. Es wird nicht zwischen Frontend und Backend bei den Programmen und der Hardware unterschieden. Das System ist so konfiguriert, dass sich die Zahl der Festplatten an der Leistungsfähigkeit des Prozessors orientiert.
Das Verhältnis beträgt dabei eine Festplatte pro MIPS. Die Festplatten werden gedoppelt. Jede
Festplatte wird von einer Menge von Prozessen verwendet, die einen großen shared RAM cache
besitzen und einen Satz von Sperren und Logsätzen für die Daten auf dem Plattenpaar verwalten.
Ein Schwerpunkt von Tandem NonStop SQL liegt auf der Optimierung von sequentiellen Scans,
dem Prefetching großer Datenblöcke und dem Filtern und Manipulieren der Datensätze mit
SQL-Prädikaten auf den Festplattenservern. Ziel ist es den Datenverkehr auf dem Netzwerk zu
minimieren. Relationen können mit range partitioning auf mehrere Festplatten verteilt werden.
Es werden Entry-Sequenced-, Relative- und B-Tree-Organisationen unterstützt, für sekundäre
Indizes aber nur B-Tree. Nested-Join-, Sort-Merge-Join- und Hash-Join-Algorithmen kommen
zur Anwendung. Die Parallelisierung von Operationen in einem Ausführungsplan wird durch das
Einführen von Split- und Merge-Operationen zwischen den Knoten des Ausführungsbaums er-
Big Data Management
Parallele Datenbanken
17
reicht. Scans, Aggregationen, Joins, Updates und Deletes werden parallel ausgeführt. Darüber
hinaus nutzen viele Utilities, wie zum Beispiel Ladeoperationen und Reorganisationen, Parallelisierung.
Tandem Systeme sind in erster Line für die Online Transaktionsverarbeitung (OLTP – online
transaction processing) konzipiert, bei der viele einfache Transaktionen auf einer großen verteilten Datenbank ausgeführt werden. Neben der Parallelität bei der Ausführung vieler unabhängiger
Transaktionen ist eine Hauptfunktionalität bei OLTP-Systemen der parallele Indexupdate. SQL
Relationen haben typischerweise fünf Indizes, wobei auch zehn Indizes nicht ungewöhnlich sind.
Die Indizes beschleunigen das Lesen, verlangsamen aber Inserts, Updates und Deletes. Bei der
Pflege der Indizes kann die Bearbeitungszeit bei mehreren Indizes nahezu konstant gehalten
werden, wenn die Parallelverarbeitung auf mehrere Prozessoren und Festplatten aufgeteilt werden kann.
Die Tandem Systeme zeigen bei der Transaktionsverarbeitung einen fast linearen Scaleup, und
bei der Ausführung von großen Abfragen einen fast linearen Speedup und einen fast linearen
Scaleup.
5.3
Gamma
Eine Version von Gamma läuft auf einem 32-Knoten Intel iPSC/2 Hypercube, bei dem jedem
Knoten eine Festplatte zugeordnet ist. Neben round robin, range und hash partitioning kommt bei
Gamma ein Verfahren zum Einsatz, das als hybrid-range partitioning bezeichnet wird, welches
die Vorteile von hash und range partitioning vereinigt. Sobald eine Relation partitioniert ist, bietet Gamma geclusterte und nicht geclusterte Indizes auf den partitionierten und den nicht partitionierten Attributen an. Die Indizes sind als Binärbäume oder hash tables implementiert.
Gamma verwendet Split- und Merge-Operationen, um bei relationalen Algebraoperationen Parallelverarbeitung und Pipelining anzuwenden. Es werden Sort-Merge-Join und drei unterschiedlichen Hash-Join-Varianten unterstützt.
Gamma erreicht für relationale Abfragen einen fast linearen Speedup und einen fast linearen
Scaleup.
5.4
The Super Database Computer (SDC)
Beim SDC handelt es sich um ein Datenbankprojekt der Universität von Tokio. Der SDC verwendet, um eine gute Performance zu erzielen, ein kombiniertes Hardware- und SoftwareKonzept. Die Basismodule (PM – processing module) bestehen aus einem oder mehreren Prozessoren mit einem Shared Memory. Diese Prozessoren werden ergänzt durch eine spezielle
Hochgeschwindigkeitssortiereinheit und ein Festplattensubsystem. Um ungleiche Datenverteilungen bei Hash-Joins zu minimieren, werden Cluster von PMs durch ein Omega Netzwerk verbunden, das sowohl nicht blockierende NxN Kommunikation als auch dynamisches Routing
verwendet. Der SDC wurde konzipiert, um mehrere tausend PMs zu enthalten. Ein Schwerpunkt
wurde auf die Vermeidung ungleicher Datenverteilungen gelegt. Die Daten werden mit hash
partitioning auf die PMs verteilt. Die SDC Software enthält ein Betriebssystem und einen relational database query executor. Der SDC hat ein Shared Nothing Hardwaredesign mit einer Datenfluss-Softwarearchitektur. Durch die spezielle Netzwerkarchitektur und die spezielle Sortiereinheit handelt es sich um kein System, das aus Standardhardware besteht.
5.5
Bubba
Der Bubba Prototyp enthielt einen 40 Knoten FLEX/32 Multiprozessor mit 40 Festplatten. Obwohl es sich hierbei um einen Shared Memory Multiprozessor handelt, wurde Bubba als Shared
Nothing System konzipiert. Der Shared Memory diente ausschließlich zum Nachrichtenaustausch. Die Knoten werden in drei Gruppen unterschieden. Dabei handelt es sich um Interface
Big Data Management
Parallele Datenbanken
18
Prozessoren, die zur Kommunikation mit externen Host-Prozessoren und zur Koordination der
Abfrageausführung verwendet werden, Intelligent Repositories, die zur Datenspeicherung und
Abfrageausführung dienen, und Checkpoint/Logging Repositories. Bubba nutz range und hash
partitioning sowohl als Speichermechanismus als auch als Datenverarbeitungsmechanismus.
Darüber hinaus weist Bubba einige Besonderheiten auf. Bubba nutz FAD, anstatt SQL, als Interfacesprache. FAD ist eine erweiterte relationale persistente Programmiersprache. FAD bietet
Unterstützung für komplexe Objekte mit vielen Typkonstruktoren und verteilten Unterobjekten,
Datensatz orientierte Manipulationsoperatoren und traditionelle Sprachkonstrukte. Der FAD
Compiler ist verantwortlich dafür Operationen zu finden, die, entsprechend ihrer Partitionierung,
parallel verarbeitet werden können. Die Programmausführung gehorcht einem Datenflussausführungsparadigma. Die Aufgabe ein FAD Programm zu kompilieren und zu parallelisieren ist
schwieriger, als eine relationale Abfrage. Eine andere Besonderheit von Bubba ist die Verwendung eines single-level Speichermechanismuses, in welchem die persistente Datenbank von jeden Knoten für jeden Prozess, der auf den Knoten ausgeführt wird, in einen virtuellen Speicheradressbereich abgebildet wird. Dies ist eine Abweichung von dem üblichen Ansatz mit Dateien
und Seiten.
6
Fazit
In dieser Ausarbeitung wurden mehrere Verfahren und Techniken beschrieben, um durch Parallelisierung eine Leistungssteigerung in der Datenbankverarbeitung zu erreichen. Dabei konnte
nur ein kleiner Ausschnitt der für die Arbeit mit parallelen Datenbanken relevanten Themen behandelt werden. Auch wenn die einzelnen Maßnahmen bereits geeignet sind, einen Leistungsgewinn herbeizuführen, kann ein optimales Ergebnis nur erreicht werden, wenn die Maßnahmen
sorgfältig aufeinander abgestimmt werden.
Quellenverzeichnis
[1]
[2]
[3]
[4]
[5]
David J. Dewitt and Jim Gray, Parallel database systems: the future of high performance
database systems, Communications of the ACM, 35:85-98, 1992
Donald Kossmann, The state of the art in distributed query processing, ACM Comput.
Surv., 32(4):422 - 469, 2000.
Theo Ungerer, Kurs 01709 Technische Informatik 3, FernUniversität in Hagen, Fachbereich Informatik, 2003
Gunter Schlageter, Wolfgang Wilkes, Michael Balzer, Dominic Becking, Peter Rosenthal, Thomas Berkel, Kurs 01665 Datenbanksysteme, FernUniversität in Hagen, Fakultät
für Mathematik und Informatik, 2010
Peter Dadam, Kurs 01666 Datenbanken in Rechnernetzen, FernUniversität in Hagen,
Fakultät für Mathematik und Informatik, 2010
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 2
Spaltenorientierte Datenbanken
Referent: Nico Geisler
Spaltenorientierte Datenbanken
Nico Geisler
Inhaltsverzeichnis
1 Einführung...........................................................................................................3
1.1 Rückblick.......................................................................................................... 3
1.2 Forschung und Entwicklung........................................................................... 3
2 DBMS als Basis....................................................................................................4
2.1 Grundlagen DBMS.......................................................................................... 4
2.2 Zeilenorientierung............................................................................................4
2.3 Spaltenorientierung......................................................................................... 5
2.4 Datenkomprimierung...................................................................................... 5
3 Spaltenorientierte DBMS................................................................................... 6
3.1 Eigenschaften....................................................................................................7
3.2 Beispiele für spaltenorientierte DBMS.......................................................... 7
3.3 Beispiel C-Store................................................................................................ 8
4 Vergleich MonetDB und MySQL.....................................................................12
4.1 Daten- und Systembasis.................................................................................13
4.2 Test-Abfragen.................................................................................................14
4.3 Ergebnisse.......................................................................................................15
5 Zusammenfassung und Ausblick......................................................................16
Literaturverzeichnis..............................................................................................17
Seite 2
Spaltenorientierte Datenbanken
Nico Geisler
1 Einführung
In der heutigen Zeit nehmen wir die aktuelle Fülle an Informationen und daraus resultierenden
Daten als selbstverständlich hin. Fast jedes Handeln oder jede Aktion benötigt, verarbeitet oder
erzeugt Informationen. In vielen Bereichen müssen diese Informationen, auf Basis
verschiedenster Gründe, als Daten, dauerhaft oder für einen bestimmten Zeitraum, digital
gespeichert und gelesen werden. Diese Daten werden, in den häufigsten Fällen, in Datenbanken
abgelegt und durch Datenbankverwaltungssysteme bereitgestellt.
1.1 Rückblick
Schaut man in die 70er Jahre zurück, waren solche Datenbanksysteme schon mit Großrechnern
verbunden, konnten Datengrößen von bis zu 200 MB speichern und arbeiteten mit dem
relationalen Datenmodell von CODD. Aber schon in den 80er Jahren beschäftige man sich mit
dem Thema Spaltenorientierung. Sybase, ein Softwareanbieter aus Kalifornien, war lange Zeit
das einzige Unternehmen, das sich mit diesem Bereich der Entwicklung überhaupt beschäftigt
hat. Nachdem ab dem Jahr 2000 klar war, dass Web 2.0 eine immer größere Rolle spielen sollte,
mussten sich einige Unternehmen mit neuen Techniken, zur Bewältigung und Verwaltung großer
Datenmengen, auseinandersetzen.
1.2 Forschung und Entwicklung
Einige Universitäten nahmen die Herausforderung an, die Verwaltung großer Datenmengen zu
bewerkstelligen. Sie entwickelten auf Open-Source Lizenz-Basis neue Datenbanksysteme, die
auf Spaltenorientierung ausgerichtet waren. Manche dieser Projekte, wie z.B. C-Store, wurden
später für kommerzielle Zwecke weiterentwickelt und verbessert. Auch größere Konzerne wie
Google Inc. oder Amazon.com, Inc. entwickelten Ihre eigenen IT-Strukturen weiter und nutzten
bzw. nutzen dabei die Vorzüge spaltenorientierter Datenbanksysteme.
Wie sich aber, laut einer recht aktuellen Studie der IDC 1 aus 2012, gezeigt hat, sind deutsche
Unternehmen noch am Anfang dieser Entwicklung. Aufgrund fehlender zeitlicher Ressourcen,
die hauptsächlich für das tägliche Geschäft verbraucht werden, können keine Innovationen
genutzt oder weiterentwickelt werden. Dabei sind die Potenziale im gesamten Bereich des BigData vielversprechend [IDC2012].
1
International Data Corporation
Seite 3
Spaltenorientierte Datenbanken
Nico Geisler
2 DBMS als Basis
Um
die
Potenziale
Datenbanksysteme
aufzuzeigen,
aufzufrischen.
sind
ein
Zusätzlich
paar
grundlegende
wird
erläutert
wie
Informationen
die
über
zeilen-
und
spaltenorientierten Datenbanksysteme unterschieden werden und was Kompressionsalgorithmen
für eine Rolle in der Verwaltung der Daten spielen.
2.1 Grundlagen DBMS
Datenbankmanagementsysteme sind meist große und komplexe Softwaresysteme. Durch sie ist
es möglich Datenbanken zu definieren, auf diesen Daten zu speichern, zu bearbeiten oder zu
löschen. Die Komplexität von Datenbanken wird so vor dem Anwender verborgen.
Ein Standard, der sich im Laufe der Zeit heraus entwickelt hat, sind relationale
Datenbankmanagementsysteme. Diese nutzen Relationen, welche aus Attributen und Tupeln
bestehen. Für die Operationen auf relationalen Daten wird die relationale Algebra verwendet, die
als Ausgangsbasis für SQL (Structured Query Language) dient. Für die Darstellung und
Konzeption haben sich Relationenmodelle durchgesetzt. Systemintern werden Relationen als
Tabellen, mit Spalten als Attribute und Zeilen als Tupel, dargestellt.
2.2 Zeilenorientierung
Zeilenorientierte Datenbanksysteme haben sich auf dem Weltmarkt durchgesetzt und sind ein
fester Bestandteil in Informationssystemen vieler Unternehmen. Der Grund dafür liegt zum
Großteil daran, dass diese Systeme für OLTP2 optimiert sind. Das bedeutet, Daten werden durch
Geschäftsprozesse erstellt, verarbeitet und für andere Geschäftsvorfälle bereitgestellt.
Die Daten selbst werden vom DBMS in Tabellen verwaltet. Sie werden aus diesen Tabellen
zeilenweise gelesen und geschrieben. Physisch werden die Daten auch zeilenweise abgelegt, wie
die Abbildung zeigt.
Abbildung 1: Zeilenorientierte physische Datenspeicherung einer Tabelle
2
Online Transaction Processing dt.: Echtzeit-Transaktionssteuerung
Seite 4
Spaltenorientierte Datenbanken
Nico Geisler
Schlüssel ermöglichen es, Daten zeilenweise zu lesen. Dadurch werden Datensätze (Zeilen)
eindeutig identifizierbar und können auch mit anderen Tabellen in Beziehung gebracht werden.
Selbst durch Normalisierungsprozesse lässt sich nicht vermeiden, dass manche Tabellen
Unmengen von Attributen besitzen. Das führt dazu, dass jeder Zugriff auf diese Tabelle auch
einen Zugriff auf alle Attribute nach sich zieht, ob man diese für die aktuelle Verarbeitung nun
benötigt oder nicht. Zeitliche Verzögerungen lassen sich also bei großen oder komplexen
Tabellenstrukturen nicht vermeiden [GI2012].
2.3 Spaltenorientierung
Zugriffe der oben genannten Art werden meist für Auswertungen benötigt und werden als
OLAP3 bezeichnet. Spaltenorientierte Datenbanksysteme können bei analytischen Anfragen ihre
Vorteile ausspielen. Das System sieht auf der physischen Ebene vor, die Daten spaltenweise
abzuspeichern, siehe Abbildung 2.
Abbildung 2: Spaltenorientierte physische Datenspeicherung einer Tabelle
Damit ist es möglich einzelne Spalten zu aggregieren, ohne alle anderen Informationen der
kompletten Tabelle lesen zu müssen [GI2012].
2.4 Datenkomprimierung
Datenkomprimierung ist ein Verfahren zur Änderung der Struktur von Daten, mit dem Ziel der
Einsparung von Speicherplatz. Die Einsparung kann sich auch auf die Zeit für übertragene Daten
beziehen, da kleinere Daten weniger Übertragungszeit benötigen.
Es werden hierbei zwei Verfahren unterschieden, die verlustfreie Kompression und die
verlustbehaftete Kompression. Das letztere Verfahren ist für Datenbanken uninteressant, da bei
dieser Kompression die Daten so verändert werden, dass Sie nicht wieder in den Originalzustand
3
Online Analytical Processing dt.: Methoden von analytischen Informationssystemen
Seite 5
Spaltenorientierte Datenbanken
Nico Geisler
decodiert werden können.
Häufig genutzte Verfahren sind die Lauflängenkodierung oder Phrasenkodierung (z.B. LZW4).
Die Lauflängenkodierung nutzt die Wiederholungen und Sequenzen von Zeichen oder
Zeichenketten, um Daten zu verkleinern. Speziell für sortierte Daten ist es eine gute Methode, da
dort die Wahrscheinlichkeit höher ist, Wiederholungen anzutreffen. Die Kodierung ersetzt, im
Fall von gefundenen Wiederholungen, diese mit dem Einzelwert vorangestellt und einem
Multiplikator, der aussagt, wie häufig der gerade gefundene Wert hintereinander auftaucht.
Vereinfacht dargestellt wäre die folgende Zeichenkette aus
bbbdddfff
kodiert zu
3b3d3f
nun um 3 Zeichen kürzer als vorher.
Phrasenkodierungen wie der LZW, arbeiten mit Wörterbüchern um Daten durch günstigere
Zeichen oder Zeichenketten zu ersetzen. Eine Sortierung ist in diesem Fall nicht notwendig bzw.
bringt keinen erheblichen Komprimierungsvorteil. Ein Nachteil der Phrasenkodierung ist das
zusätzliche Abspeichern des Wörterbuchs, da dieses zur Wiederherstellung benötigt wird.
Kompressionsalgorithmen spielen in zeilenorientierten Datenbanksystemen keine große Rolle,
da die gespeicherten Tupel i.d.R. viele unterschiedliche Attribut-Typen darstellen
([AMF06] S.673/674).
3 Spaltenorientierte DBMS
Zeilenorientierte Systeme werden oft mit Karteikarten verglichen. Sie enthalten, wie Zeilen in
einer Datenbank, alle notwendigen Informationen über einen Sachverhalt sehr übersichtlich
dargestellt und auf einen Blick. In Unternehmen werden viele solcher Karteikarten abgelegt und
für spätere Abfragen wieder benötigt. Der Nachteil zeigt sich bei Auswertungen. Möchte man
alle Umsätze von Kunden summieren, muss man zwangsläufig jede Karteikarte in die Hand
nehmen.
Spaltenorientierte Systeme würden in dieser Form alle Umsätze auf eine Karteikarte setzen, um
den Abfrageaufwand zu minimieren. Sie werden oft als lese-optimierte Systeme bezeichnet, da
sie große Datenmengen in kurzer Zeit lesen können, indem Sie unnötige Attribute ignorieren.
Wie schon im Punkt 2.3 beschrieben, speichern spaltenorientierte DBMS die enthaltenen Daten
spaltenweise, hintereinander in den physikalischen Speicher. Zusätzlich machen sie sich den
Vorteil der Spaltensicht zunutze und wenden noch Komprimierungsalgorithmen auf die Daten
an.
4
Entwickelt von Abraham Lempel und Jacob Ziv, verbessert von Terry A. Welch
Seite 6
Spaltenorientierte Datenbanken
Nico Geisler
3.1 Eigenschaften
Die
Eigenschaften
spaltenorientierter
Datenbankmanagementsysteme
sind
denen
der
zeilenorientierten Pendants sehr ähnlich und werden daher nur kurz angesprochen. Einige davon
werden am Beispiel von C-Store näher erläutert.
Physische und logische Datenunabhängigkeit wird vom DBMS garantiert. Das bedeutet für alle
Anwendungsprogramme und sonstige laufende Abfragen, dass sie Änderungen auf der
Speicherebene oder der logischen Sichten nicht sehen.
Viele DBMS bieten Views an, um Anwendungsprogrammen nur die Daten zeigen zu müssen,
die sie auch für die Verarbeitung benötigen. In diesem Kontext wird auch die Mehrfachnutzung
angeboten. Denn ein Datenbanksystem, an dem nur ein Benutzer arbeiten kann, ist im täglichen
Geschäft nicht sinnvoll. Daher gibt es unterschiedliche Rollen im DBMS um Administratoren
und Nutzern den gleichzeitigen Zugang zu ermöglichen.
Mit Transaktionen und der damit verbundenen Concurrency Control5 wird der Multizugriff
erreicht. Transaktionen sind Datenbank-Aktionen, die vom DBMS gesteuert werden und einen
konsistenten Zustand auf der Datenbank sichern. Die Concurrency Control regelt dabei den
Ablauf der vielen Transaktionen, die evtl. auf die gleichen Daten zugreifen möchten.
Datenschutz wird vom DBMS über Rollen und Rechte erreicht. Damit wird gesteuert, dass auch
nur solche Benutzer an die Daten dürfen, die mit entsprechenden Rechten versehen sind.
Datensicherheit wird über Logging-Mechanismen erreicht, welche bei Abstürzen eine
Wiederherstellung der Daten ermöglichen sollen.
3.2 Beispiele für spaltenorientierte DBMS
Hier sind nun einige spaltenorientierte Datenbanksysteme kurz vorgestellt.
Cassandra ist ein von der Apache Software Foundation weiterentwickeltes spaltenorientiertes
DBMS. Es wurde erstmals als Teilprojekt unter Facebook erarbeitet, um interne Suchanfragen
zu optimieren. Es wird durch ein sehr offenes Datenmodell beschrieben, welches auf SuperColumns und Column-Families beruht. Zudem wurde, seit Version 0.8, eine eigene
Anfragesprache eingeführt, die Ähnlichkeit mit SQL aufweist.
C-Store startete als Entwicklungs-Projekt in Zusammenarbeit mehrerer Universitätsmitarbeiter
aus unterschiedlichen Universitäten. Es ist für Linux-Systeme entwickelt worden und bietet ein
System, was auf spaltenorientierte Verarbeitung ausgerichtet ist. Mitarbeiter des MIT haben CStore weiterentwickelt und es um Funktionalitäten im Bereich Komprimierung erweitert. Das
Projekt ist seit 2009 eingestellt und steht nur als Beispielprojekt in Version 0.2 zu Verfügung.
5
Verfahren in der Datenbanktechnik um konkurrierende Zugriffe auf die DB zu steuern
Seite 7
Spaltenorientierte Datenbanken
Nico Geisler
Der C-Store Ansatz wurde als Weiterentwicklung in einer kommerziellen Version namens
Vertica fortgeführt.
MonetDB ist ein DBMS welches von Mitarbeitern des niederländischen Forschungsinstituts
CWI entwickelt wurde. Es wird als Open-Source-Projekt frei verfügbar angeboten. Es bietet eine
große Funktionsvielfalt, gerade im Bereich DataMining und OLAP wird eine hohe Performance
von komplexen Queries und großen Datenbanken erreicht. Die Festplattenzugriffe werden
reduziert, indem viele Daten im Arbeitsspeicher abgelegt werden, sowie die CPU-Leistung
besser genutzt wird [MonDB1_13].
3.3 Beispiel C-Store
Wie oben schon erklärt ist C-Store ein Softwareprojekt mehrerer Mitarbeiter unterschiedlicher
Universitäten. Es ist ein spaltenorientiertes DBMS und wurde entwickelt, um einen Großteil
unnötiger Festplattenzugriffe zu minimieren. Der Grund dafür ist, dass Festplatten eine
beschränkte Datenübertragungsrate haben. CPU-Kerne werden heute kaum noch ausgelastet und
sind für solche Arbeit bestens ausgestattet. Um Zugriffe zu sparen, können die Daten
komprimiert werden. Es muss weniger transportiert werden und die CPU kümmert sich um die
Komprimierung und Dekomprimierung der Daten. Probleme, die beim Speichern und
Wiederherstellen von komprimierten Daten auftreten können, wurden durch eine spezielle
Architektur aufgelöst.
C-Store arbeitet mit zwei Softwarekomponenten dem Writable Store(WS) und dem Readoptimized Store(RS). Beide sind verbunden durch einen Tuple Mover. Schreiboptimierte
Zugriffe wie Updates und Inserts werden über den WS gesteuert. Alle Leseanfragen gehen über
den RS. Inserts werden direkt vom WS verarbeitet, Deletes werden im RS markiert und später
vom Tuple Mover weiterverarbeitet. Updates sind eine Kombination aus Insert und Delete
([SAB+05] S.554/555).
Das logische Datenmodell ist nach relationalen Grundsätzen aufgebaut und wird, wie in vielen
anderen Systemen, als Tabellen dargestellt. Es können Verweise durch Primär- und
Fremdschlüssel hergestellt werden. Standard-SQL-Semantic ist Teil der C-Store-QueryLanguage und kann somit leicht für Abfragen genutzt werden.
Im Gegensatz zu zeilenorientierten Systemen, wo die Tabellen als Ganzes physisch gespeichert
sind, werden in C-Store nur Projections abgelegt. Diese sind mit einer oder mehreren logischen
Tabellen verbunden und beinhalten ein oder mehrere Attribute dieser Tabellen. Da es mehrere
Projections auf eine Tabelle geben kann, werden diese durchnummeriert ([SAB+05] S.555).
Seite 8
Spaltenorientierte Datenbanken
Bsp:
Nico Geisler
Table Kunde
Nachname, Vorname, Straße, Hausnummer, PLZ, Ort, Telefon, JahresUmsatz
Projections
Kunde1 (Nachname, JahresUmsatz)
Kunde2 (Nachname, Vorname, Straße, Hausnummer, PLZ, Ort)
Sortierungen werden über Sort Keys festgelegt, die in der physischen Struktur von links nach
rechts abgearbeitet werden.
Bsp:
Projection
Kunde1(Nachname, JahresUmsatz | Nachname)
Projections werden intern noch mal in Segmente unterteilt. Diese erhalten eine SegmentID (SID)
auf Basis der Sort Keys. Zusätzlich weist jedes Segment seinen zugehörigen Werten Storage
Keys zu um die Position eines Wertes genau lokalisieren zu können. Das ist notwendig um später
ganze Zeilen wieder zusammenbauen zu können bzw. die logische Struktur einer Tabelle für
SQLs herstellen zu können.
Um verschieden Projections wieder logisch vereinigen zu können werden noch Join Indices
eingesetzt. Diese stellen aus SIDs und Storage-Keys unterschiedlicher Projections eine
Verbindung für Abfragen her ([SAB+05] S.556).
Der RS in C-Store nutzt zur Geschwindigkeitsoptimierung komprimierte Daten, die
Komprimierung wird pro Spalte durchgeführt. Das Komprimierungsverfahren hängt hierbei von
der aktuellen Datenlage ab und wird in 4 Fälle unterschieden. Die Unterscheidung hängt hier von
der Sortierung und der Häufigkeit ungleicher Werte ab.
Fall1: Zu komprimierende Spalte ist sortiert und hat wenige ungleiche Werte
Die komprimierte Spalte wird als Sequenz von 3er-Wertkombinationen der Form (v, f, n)
dargestellt.
Das v stellt den gespeicherten Spaltenwert dar, das f für steht für das erste Auftreten von v in der
Spalte und n steht für die Anzahl von v in der Spalte.
Bsp:
Die Spalte mit den Werten - 0,0,1,1,1,1,2,2,2,2,2,2,2
komprimiert als (0, 1, 2) , (1, 3, 4), (2, 7, 7)
Fall2: Zu komprimierende Spalte ist nicht sortiert und hat wenige ungleiche Werte
Die komprimierte Spalte wird hier als 2er-Wertkombinationen der Form (v, b) dargestellt.
Das v stellt wieder den gespeicherten Spaltenwert dar und das b ist eine Bit-Folge, die angibt, an
Seite 9
Spaltenorientierte Datenbanken
Nico Geisler
welchen Positionen der Wert v auftaucht.
Bsp:
Die Spalte mit den Werten - 0,0,1,1,2,1,0,2,1
komprimiert als (0, 110000100), (1, 001101001), (2, 000010010)
Fall3: Zu komprimierende Spalte ist sortiert und hat viele ungleiche Werte
Die komprimierte Spalte wird hier dargestellt, indem die Differenzen zum vorherigen Wert
abgelegt werden. Der erste Wert der Spalte ist der erste Wert der komprimierten Spalte, danach
werden nur noch die Differenzen zum Vorgänger abgelegt.
Bsp:
Die Spalte mit den Werten – 2,4,5,16,22,24,24,33,50
komprimiert als 2,2,1,11,6,2,0,9,17
Fall4: Zu komprimierende Spalte ist nicht sortiert und hat viele ungleiche Werte
In diesem Fall wird keine Komprimierung durchgeführt([SAB+05] S.557).
In allen Fällen werden zur schnelleren Suche und eindeutiger Bestimmung komprimierter Werte
B-Bäume als Indizes verwendet. Das ist notwendig um die Festplattenzugriffe auf ein Minimum
zu reduzieren.
Durch eine Erweiterung von C-Store wurde es möglich die Kompression von Werten nicht nur
in Abhängigkeit der Datenlage zu steuern, sondern auch unter Verwendung der QL-Abfragen.
Die hinzukommenden Funktionen führen dazu, dass nicht alles was von der Festplatte oder dem
Arbeitsspeicher gelesen wird, zwangsläufig dekomprimiert werden muss.
Das wird erreicht, indem eine Buffer-Klasse verwendet wird, die auf dem Bestand
komprimierter Daten arbeiten kann. Diese Klasse enthält Zugriffsmethoden, welche einzelne
oder als Array verpackte, dekomprimierte Daten des Buffers liefern und Methoden die
Informationen direkt aus den komprimierten Daten holen.
Zwischen den abgesetzten Abfragen und dem Datenzugriff sitzt noch eine Schnittstelle die
Informationen darüber hat, wie Daten für bestimmte Komprimierungsalgorithmen auf der
Festplatte oder im Arbeitsspeicher abgelegt werden. Diese Schnittstelle hat Zugriff auf Indizes
der Spalten und liefert komprimierte Daten der Festplatte an den Buffer. Sollten die Daten in
einer komplexen Komprimierungsform vorliegen, liefert die Schnittstelle sofort die decodierten
Daten ([AMF06] S.674-676 ) .
Die WS-Komponente ist auf Verwaltungsebene identisch zum RS. Es werden dort genauso
Projections und Join Indices verwendet, nur die Speicherabbildung ist komplett unterschiedlich.
Um die hier verwendeten Datenstrukturen weiterverarbeiten zu können, wird als Bibliothek eine
Berkeley-Datenbank verwendet und davon die B-Bäume zur Umsetzung.
Seite 10
Spaltenorientierte Datenbanken
Nico Geisler
Zur Speicherung der Daten bzw. Spalten verwendet C-Store einen Storage Allocator. Dieser
dient dazu anhand von SID und Storage Keys zusammenhängende Daten nah beieinander auf
die Festplatte zu schreiben. Also werden Spalten, sowie ganze Projections nebeneinander
geschrieben um spätere Zugriffe zu beschleunigen ([SAB+05] S.557/558).
Da C-Store nicht hauptsächlich für OLTP entwickelt wurde sind hier spezielle LockingMechanismen im Einsatz. Es wird erwartet, dass wenn schreibende Zugriffe gemacht werden,
diese als Blöcke verarbeitet werden und nicht in vielen Einzeltransaktionen enden. Für lesende
Zugriffe wird zusätzlich Snapshot Isolation verwendet, da Multizugriffe ermöglicht werden
sollen. C-Store verwendet dafür zeitpunktbezogene High- und Low Water Marks (HWM, LWM),
die durch Epochen (Zeitabschnitt im Sekundenbereich) markiert werden. WS verwaltet dafür
zusätzlich zwei Vektoren jeweils für jede Projection, einen für Inserts (IV) und einen für Deletes
(DRV). Durch diese kann das System feststellen, wann welche Daten für Abfragen sichtbar sind.
Der Insertion Vector enthält alle Insert-Segmente und die Information, in welcher Epoche diese
bearbeitet wurden. Der Deletion Record Vector enthält für nicht gelöschte Sätze eine 0 und für
bereits gelöschte Sätze die Epoche, in welcher sie entfernt wurden.
HWM ist im Systemablauf ein Zeitpunkt, in jüngster Vergangenheit, der bestimmt, wann
Lesezugriffe isoliert ablaufen können. Die Bestimmung der HWM findet über einen
Überwachungsprozess statt. Dieser verwaltet die Epochen und hält Kontakt zu den laufenden
Zugriffssystemen (z.B. Webserver, Applicationserver), in denen die Transaktionen ablaufen. Um
eine neue HWM zu setzen, wird eine Broadcast-Nachricht an alle aktiven Zugriffssysteme
gesendet.
Diese
müssen
warten,
bis
alle
Transaktionen,
die
zum
Zeitpunkt
des
Nachrichteneingangs arbeiteten, abgeschlossen sind. Jeder der Zugriffssysteme schickt dann eine
Antwort, um zu bestätigen, dass die Epoche abgelaufen ist. Der Überwachungsprozess setzt dann
eine neue HWM und gibt diese an die Zugriffssysteme weiter. Für diese und vorherige Epochen
ist sichergestellt, dass alle Daten geschrieben wurden und den lesenden Prozessen zur Verfügung
stehen ([SAB+05] S.556-558).
Das
Sperren
von
einzelnen
Transaktionen
oder
Zugriffssystemen,
sowie
das
Wiederherstellungssystem im Fehlerfall wird über ein spezielles Logging realisiert. Es werden
UNDO Sätze gespeichert, die im Fehlerfall herangezogen werden können. Deadlocks aufgrund
von gelockten Transaktionen werden mit Timeouts wieder aufgelöst ([SAB+05] S.559/560).
Ein weiterer wichtiger Teil des Systems ist der Tuple Mover. Er steuert den Übergang von
gelöschten Sätzen vom WS zum RS. Da im laufenden Betrieb immer Transaktionen laufen,
müssen diese mit den zu ändernden Daten synchronisiert werden. Es sollen schließlich keine
Seite 11
Spaltenorientierte Datenbanken
Nico Geisler
Sätze mehr gelesen werden die nicht mehr existieren. Die LWM sowie die DRV helfen bei der
Synchronisierung, wobei die Low Water Mark regelmäßig neu festgelegt und als BroadcastNachricht an alle laufenden Zugriffssysteme verteilt wird. Die Steuerung des Tuple Mover
schaut sich die gelöschten Segmente an, sind sie vor oder genau zum Zeitpunkt der LWM
werden sie gelöscht. Sind die Segmente laut Deletion Record Vector nicht gelöscht oder nach der
LWM werden sie in den RS zurückgelesen ([SAB+05] S.560/561).
Als Grundlage für Abfragen auf dem C-Store-System wurden eigens dafür Operatoren zur
Erstellung optimierter Abfragepläne entwickelt. Ein Zugriff direkt auf das optimierte System
erfolgt nicht, sondern wird über Standard-SQL eingeleitet. Das System selbst entscheidet welche
Operatoren und welche Rückgabetypen erwartet bzw. benötigt werden und baut daraus einen
Abfrageplan. Die Rückgabetypen ergeben sich aus den oben genannten Erläuterungen des
Systems. Entweder sind es Spalten, Projections oder BitStrings die aus einem Query
zurückgegeben werden. Dabei werden 10 unterschiedliche Operatoren verwendet wie z.B.
Decompress: Operator um komprimierte Spalten komplett dekomprimiert zurückzuliefern.
Aggregation Operators: Standard Operatoren wie SUM, MAX, COUNT
Concat: Zusammenführen mehrerer Projections mit gleicher Sortierung
Join: Verbinden unterschiedlicher Projections durch Join-Indice Verbindungen
Ein zusätzliches Teilsystem, welches den optimalen Abfrageplan erstellt, muss beim Abarbeiten
beachten ob es mit komprimierten oder dekomprimierten Daten umgehen soll. Dabei sind die
ankommenden Abfragen und die abzufragenden Daten entscheidend. Wie oben bereits erklärt,
sind bei der Datenart die 4 genannten Typen zu unterscheiden und vom Optimierer auszuwerten
([SAB+05] S.561).
4 Vergleich MonetDB und MySQL
Um eine Bewertung eines spaltenorientierten Datenbanksystems vorzunehmen, wird ein
Vergleich mit einem zeilenorientierten DBS durchgeführt. MySQL ist ein heute gängiges, häufig
verbreitetes, zeilenorientiertes DBS und wird aufgrund der Performance und der großen
Entwicklergemeinschaft
geschätzt.
MonetDB
ist
wie
MySQL
ein
Open-Source
Datenbanksystem, aber auf Spaltenorientierung ausgelegt. Auf Basis des Beispiels von C-Store
wurde ein ähnliches System verwendet, welches auch von den Entwicklern von C-Store
vorgeschlagen wird. Beide Systeme basieren auf Modellebene auf Tabellen und besitzen ein
Shell-Interface zur Steuerung des Ablaufs. Beide Systeme bieten auch Schnittstellen für Zugriffe
von Applikationen an z.B. JDBC, ODBC oder Perl [MonDB2_13] u. [MySQL13].
Seite 12
Spaltenorientierte Datenbanken
Nico Geisler
4.1 Daten- und Systembasis
Das laufende System ist ein normaler Personal-Computer mit folgender Ausstattung:
Betriebssystem:
Windows 7 Ultimate – 64bit
Prozessor:
Quad Core 2,4Ghz
Arbeitsspeicher:
4 GB DDR2 (75% verfügbar)
Festplattenspeicher: 500 GB
MySQL wird in der Community-Version 5.6.12 installiert.
MonetDB wird in der Februar2013-Release-Version verwendet.
Die Daten sind in beiden DBS identisch und werden über CSV-Dateien importiert. Für den Test
wurde nur eine Tabelle verwendet, die in beiden Datenbanksystemen über ein Create-Script
eingefügt wurde.
CREATE TABLE AFL042_EBENEGKENNER_DA_TB
(
AFL042_00_ID
BIGINT NOT NULL ,
AFL042_99_AFL034_FLST_SATZNR BIGINT NOT NULL ,
AFL042_98_AFL034_SYSBIS
TIMESTAMP (6) ,
AFL042_02_JAHR
INT NOT NULL ,
AFL042_29_BTNR15
VARCHAR (15) ,
AFL042_03_POS
TINYINT NOT NULL ,
AFL042_04_WERT
TINYINT NOT NULL ,
AFL042_05_MACHTWORT_WERT
TINYINT,
AFL042_13_MACHTWORT_DATUM DATE,
AFL042_14_MACHTWORT_BEARB VARCHAR(16),
AFL042_07_SYS_BIS_SATZART
VARCHAR(1),
AFL042_08_BEARBEITER
VARCHAR(16),
AFL042_09_H_BEARBEITER
VARCHAR(16),
AFL042_10_SYS_VON
TIMESTAMP (6) NOT NULL ,
AFL042_11_SYS_BIS
TIMESTAMP (6) NOT NULL ,
AFL042_12_VERSION
SMALLINT NOT NULL
);
Die Tabelle stammt aus einem Softwareprojekt und enthält Datensätze, deren Inhalt
Plausibilitätsprüfungen darstellen. Diese werden im Projekt regelmäßig geschrieben bzw.
überschrieben. Zusätzliche werden auf den Prüfungsdaten größere Auswertungen gemacht,
spezielles Interesse gilt hier den Feldern AFL042_03_POS und AFL042_04_WERT. Für den
Vergleich wurde ein Testdatenbestand von etwa 1,5 Mio. Datensätzen importiert.
Seite 13
Spaltenorientierte Datenbanken
Nico Geisler
4.2 Test-Abfragen
Die Abfragen ergeben sich aus den im Projekt notwendigen Informationen und Arbeitsabläufen.
1. Abfrage:
Select count(distinct(AFL042_29_BTNR15)) from AFL042_EBENEGKENNER_DA_TB
where AFL042_02_JAHR
= 2012
and
> 'aktuelles Datum';
AFL042_11_SYS_BIS
2. Abfrage:
Select AFL042_29_BTNR15, Count(*) from AFL042_EBENEGKENNER_DA_TB
where AFL042_04_Wert
>1
and
AFL042_02_JAHR
= 2012
and
AFL042_11_SYS_BIS
> 'aktuelles Datum'
group by AFL042_29_BTNR15;
3. Abfrage:
Select AFL042_29_BTNR15, AFL042_03_POS, AFL042_05_MACHTWORT_WERT from
AFL042_EBENEGKENNER_DA_TB
where AFL042_05_MACHTWORT_WERT
is not null
and
AFL042_02_JAHR
= 2012
and
AFL042_11_SYS_BIS
> 'aktuelles Datum';
4. Abfrage:
Select distinct (AFL042_29_BTNR15) from AFL042_EBENEGKENNER_DA_TB
where AFL042_14_MACHTWORT_BEARB
like 'BATCH%'
or AFL042_08_BEARBEITER
like 'BATCH%'
or AFL042_09_H_BEARBEITER
like 'BATCH%;
5. Abfrage:
update AFL042_EBENEGKENNER_DA_TB set AFL042_11_SYS_BIS = 'aktuelles Datum'
where AFL042_03_POS
= 13
and
AFL042_04_WERT
=3
and
AFL042_08_BEARBEITER like 'BATCH%'
and
AFL042_11_SYS_BIS
> 'aktuelles Datum' ;
Seite 14
Spaltenorientierte Datenbanken
Nico Geisler
6. Abfrage:
update AFL042_EBENEGKENNER_DA_TB set AFL042_04_WERT = 0
where AFL042_03_POS
in (2,3,7,9)
and
AFL042_04_WERT > 1
and
exists (select * from AFL042_EBENEGKENNER_DA_TB kenner2
where kenner2.AFL042_29_BTNR15 = AFL042_29_BTNR15
and kenner2.AFL042_99_AFL034_FLST_SATZNR =
AFL042_99_AFL034_FLST_SATZNR
and kenner2.AFL042_03_POS
=1
and kenner2.AFL042_04_WERT
= 2 );
4.3 Ergebnisse
Die Ergebnisse ergeben sich aus den abgesetzten Abfragen und der benötigten Zeit des
jeweiligen Systems. Zusätzlich zeigte die Überwachung der laufenden Prozesse, dass der
MonetDB-Server im gesamten Ablauf maximal 100 MB Arbeitsspeicher benötigte. MySQLServer hatte im gesamten Ablauf zwischen 450 und 500 MB Arbeitsspeicher reserviert. Die
Speicherverteilung auf Festplattenebene sah ähnlich aus. MonetDB mit etwa 120 MB und
MySQL mit ungefähr 500 MB genutztem Festplattenspeicher.
MonetDB
MySQL
1. Abfrage
1s
6,72 s
2. Abfrage
0,45 s
3,58 s
3. Abfrage
0,03 s
3,37 s
4. Abfrage
0,12 s
6,47 s
5. Abfrage
0,24 s
6,78 s
6. Abfrage
1,3 s
20,46 s *
* Die 6. Abfrage musste für MySQL etwas abgeändert werden, da es dort nicht erlaubt war eine
zu ändernde Tabelle in einem Subselect im FROM Abschnitt zu benutzen. Um das Problem zu
umgehen, wurde ein weiteres Subquery eingebaut. Damit sieht MySQL es als abgeschlossene
temporäre Tabelle an und meldet keine Fehler.
Das Ergebnis der Abfragen zeigt, dass die MonetDB insgesamt um ein vielfaches schneller
reagiert hat als die zu vergleichende MySQL-DB. Man muss natürlich beachten, dass in keinem
der beiden Systeme Optimierungen vorgenommen wurden.
Seite 15
Spaltenorientierte Datenbanken
Nico Geisler
5 Zusammenfassung und Ausblick
Wie man in den vorhergehenden Erläuterungen und Beispielen sehen konnte, tragen
spaltenorientierte Datenbanken einen großen Teil zur Big-Data Entwicklung bei. Sie bringen
aufgrund ihrer Funktionalitäten und Verbesserungen im Datenbankumfeld, neue Möglichkeiten
mit Daten umzugehen. Gerade im Bereich OLAP und Data-Mining spielen sie eine große Rolle
und können ihr Potenzial aufzeigen. Auf Basis der Erfahrungen mit MonetDB scheint ein
Wechsel von zeilenorientierten Datenbanken als lohnenswert und nicht sehr schwierig. Da die
Systeme meistens auf Tabellenmodelle aufbauen und SQL unterstützen, ist auch das bisher
gesammelte Know-how voll einsetzbar.
Wie man an großen Unternehmen wie Google oder Amazon sieht, die auch auf solche Systeme
setzen oder Teilsysteme nutzen, ist eine Investition im Big-Data Bereich sinnvoll. Was dabei
beachtet werden sollte, ist das sich viele bereits Gedanken zu dem Themenkomplex gemacht
haben und man somit nicht auf der grünen Wiese beginnen muss. Zusätzlich ist ein Blick rechts
und links, zu anderen Bereichen wie Data-Cubes, dokumentenorientierten Datenbanken oder
Graphen-Datenbanken, lohnenswert.
Seite 16
Spaltenorientierte Datenbanken
Nico Geisler
Literaturverzeichnis
[SAB+05]
M. Stonebraker, D. J. Abadi, A. Batkin, X. Chen, M. Cherniack,
M. Ferreira, E. Lau, A. Lin, S. Madden, E. J. O’Neil, P. E. O’Neil, A.
Rasin, N. Tran, und S. B. Zdonik.
C-Store: A column-oriented DBMS. In VLDB, Seiten 553–564, 2005.
[AMF06]
D. J. Abadi, S. Madden und M. Ferreira
Integrating Compression and Execution in
Column-Oriented Database Systems. In SIGMOD, Juni 2006
[MonDB1_13]
http:// http://www.monetdb.org/
[Stand - 12.06.2013]
[MonDB2_13]
http:// http://www.monetdb.org/Documentation/SQLreference
[Stand - 12.06.2013]
[MySQL13]
http://dev.mysql.com/doc/refman/5.1/de/connectors.html
[Stand - 12.06.2013]
[GI2012]
Daniel Böswetter
http://www.gi.de/service/informatiklexikon/detailansicht/
article/spaltenorientierte-datenbanken.html
[Stand - 12.06.2013]
[IDC2012]
http://www.idc.de/press/presse_idc-studie_big_data2012.jsp
[Stand - 12.06.2013]
Seite 17
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 2.1
MapReduce
Referent: Johannes Unterstein
Thema 2.1: MapReduce
Johannes Unterstein
Inhaltsverzeichnis
Inhaltsverzeichnis .................................................................................................. 3
1 Einleitung und Motivation.............................................................................. 1
2 Google MapReduce-Implementierung .......................................................... 2
2.1 Das MapReduce-Verfahren ......................................................................... 2
2.1.1 Eingabe aufteilen ................................................................................... 4
2.1.2 Map-Phase ............................................................................................. 4
2.1.3 Reduce-Phase ........................................................................................ 5
2.1.4 Ausgabedateien ..................................................................................... 5
2.2 Der Master und die Worker ......................................................................... 5
2.3 Fehlertoleranz und fehlerhafte Einträge ...................................................... 5
2.4 Lokalität und Granularität ........................................................................... 6
2.5 Backup-Tasks .............................................................................................. 7
2.6 Nützliche Erweiterungen ............................................................................. 7
2.6.1 Sortierungsgarantie ................................................................................ 7
2.6.2 Benutzerdefinierte Kombinierungsfunktionen ...................................... 8
2.6.3 Benutzerdefinierte Partitionierungsfunktion ......................................... 8
2.6.4 Fehlerhafte Sektoren überspringen ........................................................ 8
2.6.5 Lokale Ausführung ................................................................................ 8
3 Performance am Beispiel von TeraSort ........................................................ 9
3.1 Testkonfiguration ........................................................................................ 9
3.2 Sortierungsalgorithmus ............................................................................... 9
3.3 Effekt von Backup-Tasks und Fehlern ...................................................... 10
4 Auswirkungen und Erfahrungen bei Google .............................................. 11
5 Apache Hadoop.............................................................................................. 11
5.1 Architektur und Unterschiede zu Google MapReduce ............................. 12
5.2 Prominente Verwender und Erweiterungen .............................................. 12
6 Schlussbemerkung ......................................................................................... 13
7 Literatur ......................................................................................................... 14
Thema 2.1: MapReduce
Johannes Unterstein
1 Einleitung und Motivation
Dieser Seminararbeit liegen die Publikationen [DG04] und [DG08] von Jeffrey Dean und
Sanjay Ghemawat zu Grunde. Alle Informationen bezüglich des MapReduce-Verfahrens
wurden diesen offiziellen Publikationen entnommen und werden daher in dieser Arbeit nicht
gesondert gekennzeichnet.
Google musste über die Jahre viele Algorithmen entwickeln, welche große Datenmengen verarbeiten. Bei diesen Algorithmen geht es zum Beispiel um das Berechnen der Strukturen von
Webseiten oder deren Zusammenfassungen, aber auch um die Berechnung der meistgesuchten Anfragen eines Tages. Um diese Art von Anfragen in annehmbarer Zeit beantworten zu
können, müssen diese Berechnungen parallelisiert und somit auf tausende Knoten verteilt
werden.
Aus einer Verteilung bzw. Parallelisierung resultiert immer eine stark erhöhte Komplexität
des Systems, da die Verteilung der Aufgaben und Daten, Fehlertoleranz und weitere Aspekte
behandeln werden muss. Da ein Anwendungsentwickler im Normalfall nicht auf Parallelisierung spezialisiert ist bzw. keine Erfahrung mit der Verarbeitung von Datenmengen dieser
Größe hat, entstand der Bedarf nach einem einfachen Programmiermodell, welches diese
Komplexität mindert. Aus diesem Grund hat Google mit dem MapReduceProgrammiermodell eine Abstraktionsschicht entworfen, welche sich um diese Infrastrukturthemen kümmert und die damit einhergehende Komplexität kapselt. Dabei wurden die Aspekte der Parallelisierung, Fehlertoleranz, Lastverteilung und weitere Aspekte in ein Framework gefasst, welches in C++ implementiert ist. Unter Framework werden in diesem Kontext
die infrastrukturelle Umgebung und ihre Bibliotheken verstanden, in welchem sich der Anwendungsentwickler bewegt und welche er verwenden kann.
Das Programmierparadigma Map und Reduce war nicht neu und aus funktionalen Programmiersprachen wie Lisp bereits bekannt. Bei diesem Vorgehen wird zunächst auf den Eingabedaten eine Map-Funktion angewendet, welches eine Menge von Zwischenergebnissen berechnet. Diese Zwischenergebnisse bestehen aus Schlüssel/Wert-Paaren und werden anhand
ihrer Schlüssel gemischt. Auf ihnen wird dann die Reduce-Funktion angewendet um alle Zwischenergebnisse, welche den gleichen Schlüssel haben, wieder zu kombinieren.
Bei Google wurde festgestellt, dass sich viele Probleme bzw. Algorithmen in diesem Paradigma abbilden lassen und sich daher kompakt und nicht unnötig komplex ausdrücken lassen,
sich trotzdem aber ausreichend performant gestalten. Aus diesem Grund ist es nicht verwunderlich, dass sich die Anzahl an MapReduce-Implementierungen von 2004 bis 2007 etwa verzehnfacht hat1.
Nicht nur bei Google werden Algorithmen auf Basis des MapReduce-Paradigma immer beliebter, es haben sich auch OpenSource-Implementierungen des Paradigmas entwickelt. Eines
der bekannteren Beispiele dieser Implementierungen ist das Projekt Hadoop, welches unter
dem Dach der Apache Software Foundation entwickelt wird. Hadoop ist in Java entwickelt
worden und wird unter anderem von Facebook verwendet um mit den dort auftretenden Datenmengen umzugehen [ABPA+09, Seite 1].
1
Die Anzahl bezieht sich auf die Implementierung von 4.000 Map-Funktionen und 2.500 Reduce-Funktionen
im September 2007.
1
Thema 2.1: MapReduce
Johannes Unterstein
2 Google MapReduce-Implementierung
Das MapReduce-Verfahren erlaubt es dem Entwickler besonders einfach einen Algorithmus
parallel auszuführen. Da die Abläufe innerhalb der Map- und der Reduce-Funktion meistens
seiteneffektfrei sind, ergibt sich ein natürlicher und einfacher Ansatz zur Parallelisierung.
Es wird ein verteiltes Dateisystem benötigt, da Daten zwischen den Knoten ausgetauscht werden müssen. Das Google File System (GFS) [Seminarthema 2.2, Google File System] stellt
die Grundlage für den verteilten Dateiaustausch im MapReduce-Verfahren dar. Das GFS ist
ein verteiltes Dateisystem, welches Verfügbarkeit und Ausfallsicherheit auf unsicherer Hardware durch Replikationen sicherstellt. Die Besonderheit dieses Dateisystems ist, dass der
Ausfall eines Knotens als Regelfall und nicht als Ausnahmefall betrachtet wird.
Das MapReduce-Verfahren stellt nicht nur dem Anwendungsentwickler ein Interface zur Verfügung, welches er nutzen kann, sondern auch Frameworkentwicklern ein Interface, welches
sie auf bestimmte Hardware und Ausführungsumgebungen zuschneiden und optimieren können. Die Implementierung, welche Google verwendet, ist auf eine große Anzahl von kostengünstigen Personal Computern zugeschnitten, welche über ein Netzwerk miteinander verbunden sind.
2.1 Das MapReduce-Verfahren
Um die Anwendung des MapReduce-Verfahrens zu zeigen, wird das Verfahren zunächst anhand eines vereinfachten Beispiels grafisch dargestellt und darauf aufbauend das gesamte
Verfahren vorgestellt. In diesem einleitenden Beispiel soll das Auftreten von Worten in einem
Text gezählt werden.
2
Thema 2.1: MapReduce
Johannes Unterstein
Abbildung 1 - Vereinfachte Darstellung des MapReduce-Verfahrens2
In Abbildung 1 wird dargestellt, dass die gesamte Eingabe zunächst in separate Eingabesegmente aufgeteilt wird und diese danach auf die vorhandenen Knoten verteilt werden. Anschließend wird auf jedem Eingabesegment die Map-Funktion angewendet um eine Menge
von Zwischenergebnissen zu produzieren. Ein Zwischenergebnis besteht in diesem Fall zum
Beispiel aus dem Schlüssel/Wert-Paar „Bob -> 1“. Ist ein Knoten mit einem Eingabesegment
fertig, so kann er ein weiteres Segment verarbeiten, sofern noch Segmente vorhanden sind.
Sind alle Eingabesegmente verarbeitet, werden die generierten Zwischenergebnisse anhand
ihrer Schlüssel auf die Knoten verteilt. Diese Phase wird als „Mischen“ bezeichnet. In diesem
Beispiel wird unter anderem die Regel (Partitionsfunktion) verfolgt, dass die Schlüssel „Bob“,
„läuft“ und „John“ von Knoten 1 verarbeitet werden. Bei der Aufteilung ist zu beachten, dass
die Zwischenergebnisse unter Umständen von einem Knoten auf den anderen transferiert
werden müssen. So muss hier das Zwischenergebnis „Jim -> 1“ von Knoten 1 und von Knoten 2 nach Knoten 3 transferiert werden, da diese dort für die Reduce-Funktion benötigt werden.3
Die drei Reduce-Funktionen addieren nun Werte anhand ihrer Schlüssel und schreiben das
Ergebnis in eine Ausgabedatei pro Knoten. Hier ist zu beachten, dass pro Knoten, auf dem eine Reduce-Funktion ausgeführt wird, genau eine Ausgabedatei produziert wird. Daher werden
drei unabhängige und nicht kombinierte Ausgabedateien produziert.
2
3
Abbildung nach eigener Darstellung.
Bei der hier genannten Phase „Mischen“ handelt es sich in Wirklichkeit nicht um eine eigenständige Phase.
Es wird während der Reduce-Funktion ein Lesen der Daten von dem entfernten Knoten durchgeführt.
3
Thema 2.1: MapReduce
Johannes Unterstein
Abbildung 2 – Vollständige Darstellung des MapReduce-Verfahrens [DG04, Seite 139]
Anhand von Abbildung 2 werden die bereits eingeführten Phasen näher und auch korrekt in
Bezug auf den Master- und die Worker-Knoten erläutert. In einer MapReduce-Berechnung
gibt es M Map-Aufgaben und R Reduce-Aufgaben. Eine Aufgabe ist definiert als die Anwendung einer Map- oder Reduce-Funktion auf einer definierten Datenmenge. Sowohl die Zahl
M, wie auch die Zahl R, wird vom Benutzer definiert.
2.1.1 Eingabe aufteilen
Die Eingabe wird automatisch in M Teile, mit jeweils etwa 16 Megabytes bis 64 Megabytes
(MB) Größe, aufgeteilt. Die Größe kann vom Benutzer über einen Parameter definiert werden.
Danach werden Programm- und Eingabedaten auf einen Verbund (Cluster) von Computern
verteilt. Der Master ist ein besonderer Knoten in diesem Cluster. Er kümmert sich um die
Verteilung und Zuweisung der Aufgaben, während ein Worker nur eine Map- bzw. ReduceAufgabe ausführt.
2.1.2 Map-Phase
In dieser Phase weist der Master einem Worker-Knoten, welcher den Zustand „Leerlauf“ (Idle) hat, eine Map-Funktion samt Daten zu. Der Worker liest den Inhalt der Daten und verarbeitet diesen, indem er die benutzerdefinierte Map-Funktion auf dem Inhalt anwendet. Als
Ergebnis entstehen die Schlüssel/Wert-Paare als Zwischenergebnisse, welche im Speicher
zwischengepuffert werden. In periodischen Abständen werden diese Zwischenergebnisse auf
4
Thema 2.1: MapReduce
Johannes Unterstein
R Bereiche der lokalen Festplatte geschrieben und die Positionen der Bereiche zurück an den
Master gemeldet.
2.1.3 Reduce-Phase
Wenn einem Worker eine Reduce-Aufgabe zugewiesen wird, liest er mittels „remote procedure call“4 zunächst alle erforderlichen Zwischenergebnisse der anderen Worker ein. Da einer Reduce-Aufgabe normalerweise mehrere Schlüssel zugewiesen werden, müssen somit
auch Daten für mehrere Schlüssel gelesen werden. Wenn alle Zwischenergebnisse gelesen
sind, werden diese anhand ihrer Schlüssel sortiert. Sollte die Anzahl an Zwischenergebnissen
zu groß sein, so dass sie nicht in den Arbeitsspeicher passen, wird eine externe Sortierung
verwendet.
Der Worker iteriert über jeden Schlüssel der Zwischenergebnisse und übermittelt den Schlüssel und die dazugehörige Menge an Werten an die benutzerdefinierte Map-Funktion.
2.1.4 Ausgabedateien
Das Ergebnis der Anwendungen der Reduce-Funktion wird an die finale Ausgabedatei des
jeweiligen Workers angehängt. Hierbei ist zu beachten, dass pro Worker genau eine Ausgabedatei entsteht. Wir sprechen also nicht nur von verteilter Ausführung, sondern auch von
verteilten Ergebnissen. In den meisten Fällen muss der Benutzer allerdings die Ausgabedateien nicht wieder zusammen führen, da diese oft als Eingabe für einen weitere Map/Reduce Berechnungen dienen.
2.2 Der Master und die Worker
Wie bereits beschrieben gibt es zwei Arten von Knoten in dem verwendeten Cluster. Ein
Knoten ist besonders und wird Master genannt. Bei dem Master laufen alle Informationen zusammen. Er weiß welche Knoten ihm zur Verfügung stehen, welche Teile der Eingabe bereits
verarbeitet wurden, wo die Zwischenergebnisse gespeichert sind und welcher Knoten gerade
mit welcher Aufgabe beschäftigt ist. Im Gegenzug weist er den Worker-Knoten die Aufgaben
zu und ihm wird gemeldet, wenn ein Knoten fertig ist und an welcher Position Zwischenergebnisse geschrieben wurden.
2.3 Fehlertoleranz und fehlerhafte Einträge
Das MapReduce-Verfahren muss generell sehr fehlertolerant sein, da die Knoten im Cluster
aus handelsüblicher Hardware bestehen und somit fehleranfällig sind. Es gibt zwei Arten von
4
Mit „remote procedure call“ wird der Aufruf einer Methode auf einem entfernten Objekt, einem Objekt welches sich auf einem anderen Server befindet, bezeichnet.
5
Thema 2.1: MapReduce
Johannes Unterstein
Ausfällen in dem Szenario. Zum einen kann ein Worker-Knoten ausfallen, zum anderen kann
der Master-Knoten ausfallen.
Der Master-Knoten fragt die Worker-Knoten in periodischen Abständen, ob diese noch lebendig sind. Erhält der Master-Knoten keine Antwort, so markiert er den Worker-Knoten und
seine ausgeführten Aufgaben als fehlgeschlagen. Schlägt eine Aufgabe fehl, so wird sie einfach einem anderen Worker-Knoten zugewiesen und erneut ausgeführt. Schlägt eine MapAufgabe fehl, werden alle bereits abgeschlossenen Map-Aufgaben von diesem Knoten ebenfalls als fehlgeschlagen markiert, da die Zwischenergebnisse von diesem Knoten auf der lokalen Festplatte des Knotens gespeichert wurden und nun nicht mehr lesbar sind.
Die Informationen des Master-Knotens werden während der MapReduce-Berechnung nicht
gesichert, da eine Rekonstruktion des Clusterzustandes bei einem Ausfall des Master-Knotens
zu komplex wäre. In dem Fall eines Ausfalls des Master-Knotens wird die Berechnung erneut
angestoßen. Da es allerdings nur einen einzigen Master-Knoten (im Vergleich zu tausenden
Worker-Knoten) gibt und dieser auch nur administrative Aufgaben wahrnimmt (und keine berechnenden), ist es relativ unwahrscheinlich, dass dieser Knoten ausfällt.
In besonderen Fällen kann die benutzerdefinierte Map- oder Reduce-Funktion selbst auch fehlerhaft in Bezug auf eine bestimmte Art von Daten sein. Der übliche Weg an dieser Stelle wäre, den Fehler der Funktion zu beheben und die MapReduce-Berechnung erneut zu starten.
Manchmal ist dies aber nicht möglich, da der Entwickler keinen Einfluss auf den Code der
Fehlerstelle hat, oder es für den Benutzer auch akzeptabel ist auf eine bestimmte Anzahl von
Daten zu verzichten. Daher wurde die Regel implementiert, die Verarbeitung eines Datensatzes im Fehlerfall erneut anzustoßen. Stellt der Master allerdings ein weiteres Mal das Auftreten eines Programmfehlers in einem Datensatz fest, wird dieser Datensatz nicht erneut ausgeführt und in der Zukunft ignoriert. Dieses Konzept wird Überspringen von fehlerhaften Sektoren oder im englischen „skip bad records“ genannt.
2.4 Lokalität und Granularität
Die Netzwerkbandbreite ist eine rare und teure Ressource in diesem Parallelisierungsframework. Daher wird die Eigenschaft vom GFS genutzt, so dass die Eingabedaten auf den Clusterknoten lokal gespeichert werden und bei Bedarf nicht über das Netzwerk transferiert werden müssen. Typischerweise werden Eingabedaten in etwa 64 MB große Blöcke zerteilt und
von GFS auf drei Clusterknoten verteilt um Ausfallsicherheit zu garantieren. Der MasterKnoten wird über alle Informationen der Lokalität der Eingabedaten informiert. Er weist eine
Map-Aufgabe einem Knoten zu, auf welchem eine Kopie der Eingabedaten vorhanden ist.
Sollte ein Knoten mit einer Map-Aufgabe fehlschlagen, so kann der Master-Knoten einfach
einem anderen Knoten, auf welchem die gleichen Daten vorhanden sind, die Aufgabe erneut
zuweisen. Daher verursachen die meisten Lese-Operationen von Eingabedaten während der
Berechnung keine Netzwerklast.
Um die Aufgaben möglichst feingranular zu verteilen, sind die Anzahl von Map- und ReduceAufgaben (M und R) wesentlich höher als die Anzahl an verfügbaren Knoten. Wenn die Aufgaben klein gehalten werden und wenn mehrere Aufgaben pro Knoten vorhanden sind, kann
die Last wesentlich granularer verteilt werden und das Wiederherstellen nach einem Knotenausfall ist ebenfalls schneller.
6
Thema 2.1: MapReduce
Johannes Unterstein
Es ist also von Vorteil, wenn M und R groß gewählt werden. Es gibt allerdings auch Beschränkungen, welche auf die Wahl von M und R einwirken. So muss der Master M + R Entscheidungen treffen, welcher Knoten diese Aufgabe ausführt. Weiterhin muss er M * R Zustände der Knoten im Speicher halten.
Da jede Reduce-Aufgabe eine Ausgabedatei produziert, ist die Wahl von R unter Umständen
auch dadurch beschränkt, wie viele Ausgabedateien der Benutzer benötigt.
Bei der Wahl von M wird bei Google darauf geachtet, dass die resultierenden Teile der Eingabedaten zwischen 16 und 64 MB groß sind.
Eine typische MapReduce-Berechnung besteht bei Google ungefähr aus 2.000 Knoten,
200.000 Map-Aufgaben und 5.000 Reduce-Aufgaben.
2.5 Backup-Tasks
Einer der üblichen Gründe, warum Berechnungen in die Länge gezogen werden, sind sogenannte Nachzügler. Dabei handelt es sich um einzelne Aufgaben, welche besonders lange
dauern und gehäuft am Ende einer Phase auftreten. Dieser Effekt kann aus unterschiedlichen
Gründen auftreten, zum Beispiel weil die Lesegeschwindigkeit der Festplatte eines Knoten
aufgrund eines Hardwaredefekts rapide einbricht oder weil eine andere Berechnung die CPU
dieses Knotens stark in Anspruch nimmt.
Wenn eine MapReduce-Berechnung gegen Ende ihrer Durchlaufzeit ist, wird der MasterKnoten daher alle aktuell ausgeführten Aufgaben an Worker im Zustand Idle erneut zuweisen.
Das erste Ergebnis, welches als fertig zurückgemeldet wird, wird akzeptiert und das zweite
Ergebnis wird ignoriert.
Durch diesen simplen aber effektiven Trick konnte eine signifikante Steigerung der Performanz des Verfahrens erreicht werden. Die zusätzlichen Aufgaben werden Backup-Tasks genannt. Die konkreten Auswirkungen auf die Performanz wird in Kapitel 3.3 erläutert.
2.6 Nützliche Erweiterungen
In diesem Abschnitt werden nützliche Erweiterungen näher vorgestellt, welche die Basisfunktionalität sinnvoll ergänzen.
2.6.1 Sortierungsgarantie
Es wurde die Eigenschaft implementiert, welche die Zwischenergebnisse für den ReduceWorker anhand des Schlüssels gruppiert und somit sortiert. Vorher war lediglich die Zuordnung von Schlüssel auf Reduce-Worker gegeben, allerdings konnte eine unsortierte Reihenfolge der Schlüssel auftreten. Da die Ergebnisse nun sortiert sind, ist das Produzieren der
Ausgabedatei vereinfacht worden. Es müssen nur noch anhängende Schreiboperationen ausgeführt werden.
7
Thema 2.1: MapReduce
Johannes Unterstein
2.6.2 Benutzerdefinierte Kombinierungsfunktionen
In dem Beispiel des Wörterzählens produziert ein Map-Worker hunderte oder tausende von
Zwischenergebnissen der Art „Wort -> 1“. Da dies, beim Übertragen der Daten, nicht die performanteste Lösung ist, wurden sogenannte Kombinierungsfunktionen eingeführt. Eine Kombinierungsfunktion ist eine Funktion, welche Zwischenergebnisse kombiniert, bevor sie auf
den Reduce-Worker übertragen werden. In den meisten Fällen wird eine Kombinierungsfunktion den gleichen Code ausführen, welchen auch eine Reduce-Funktion ausführen würde. Der
einzige Unterschied zwischen Reduce- und Kombinierungsfunktion besteht darin, welche Art
von Ausgabe produziert wird.
2.6.3 Benutzerdefinierte Partitionierungsfunktion
Die Zwischenergebnisse werden anhand ihrer Schlüssel auf die Reduce-Knoten verteilt. Dabei wird standardgemäß eine Hash-Funktion auf den Schlüssel angewendet und modulo R5
gerechnet. So ist garantiert, dass ein Schlüssel auf einen Knoten verteilt wird, welcher auch
existiert. Diese Partitionierungsfunktion sorgt für eine gute Balancierung und Partitionsverteilung. In bestimmten Szenarios kann es allerdings sinnvoll sein, die Zwischenergebnisse nicht
anhand des Schlüsselhashs zu partitionieren. Daher wurde die Möglichkeit geschaffen, dass
der Benutzer eine selbstimplementierte Partitionierungsfunktion dem System übergeben kann.
2.6.4 Fehlerhafte Sektoren überspringen
Wie schon in Kapitel 2.3 beschrieben, wurde die Verbesserung implementiert, dass Eingabeteile, welche von der Map-Funktion nicht ausgewertet werden können, nach zwei erfolglosen
Versuchen übersprungen werden. Da in der Regel große Datenmengen verarbeitet werden,
spielen einzelne nicht verarbeitbare Eingabeteile nur eine kleine Rolle und können daher vernachlässigt werden. Diese Verbesserung macht das System fehlertoleranter gegenüber fehlerbehafteten Implementierungen der Map-Funktion.
2.6.5 Lokale Ausführung
Durch ein speziellen Parameter beim Starten der MapReduce-Berechnung ist es möglich, dass
die Berechnung nur auf einer Maschine und in sequenzieller Reihenfolge ausgeführt wird.
Diese Möglichkeit wurde eingebaut, damit der Anwendungsentwickler eine bessere Chance
hat sein Programm zu testen bzw. zu untersuchen, bevor die Berechnung auf tausenden Knoten durchgeführt wird.
5
Mit modulo wird eine Rechenoperation bezeichnet, bei welcher der ganzzahlige Rest bei einer Division als
modulo bezeichnet wird. R beschreibt die Anzahl an verfügbaren Knoten.
8
Thema 2.1: MapReduce
Johannes Unterstein
3 Performance am Beispiel von TeraSort
Anhand des Beispiels TeraSort soll die enorme Geschwindigkeit dieses Verfahrens gezeigt
werden. Bei diesem Beispiel wird etwa ein Terabyte6 an Daten sortiert. Besonders an der Implementierung ist, dass der Algorithmus nur knapp 50 Zeilen umfasst.
3.1 Testkonfiguration
Das Cluster, auf welchem die MapReduce-Berechnung durchgeführt wurde, besteht aus etwa
1800 handelsüblichen Computern. Jeder Computer besitzt zwei 2GHz Intel Xeon Prozessoren
mit HyperThreading, 4GB Arbeitsspeicher und zwei herkömmliche IDE Festplatten. Alle
Computer sind über ein Gigabit Netzwerk miteinander verbunden. Die hier beschriebene
Testberechnung wurde an einem Nachmittag an einem Wochenende durchgeführt, an welchem das Cluster keiner weiteren größeren Belastung ausgesetzt war. Es waren allerdings 11,5GB Arbeitsspeicher pro Knoten für andere Berechnungen reserviert.
In diesem Testszenario wird das Terabyte an Eingabedaten in M=15.000 Datenteile zu je ungefähr 64 MB und somit auch ebenso vielen Map-Aufgaben aufgeteilt. Weiterhin werden
R=4.000 Reduce-Aufgabe verwendet und eine Partitionierungsfunktion, welche die Zwischenergebnisse anhand ihrer Schlüssel sortiert.
3.2 Sortierungsalgorithmus
Wie bereits angesprochen ist die Implementierung des Algorithmus äußerst kompakt gehalten
und es werden viele eingebaute Mechanismen verwendet bzw. zum eigenen Vorteil genutzt.
So wird die Tatsache, dass Zwischenergebnisse sortiert werden, genutzt um die eigentliche
Sortierung durchzuführen. Dabei wird in der Reduce-Funktion die eingebaute Identitätsfunktion dazu verwendet die Schlüssel (aus den Schlüssel/Wert-Paaren der Zwischenergebnisse)
unverändert in die Ausgabedatei zu schreiben.
Abbildung 3 stellt die Transferrate in Bezug zur vergangenen Zeit dar. Es wird die Ausführung des Algorithmus unter drei Umständen beschrieben. Pro Umstand wird jeweils ein
Graph für das Verteilen der Eingabedaten (Input), das Zuweisen der Zwischenergebnisse zu
Reduce-Aufgaben (Shuffle) und dem Schreiben der Ausgabedaten (Output) dargestellt.
6
Ein Terabyte entspricht 10^12 Byte.
9
Thema 2.1: MapReduce
Johannes Unterstein
Abbildung 3 - Datentransferraten der Sortierungsberechnung [DG04, Seite 145]
Die Graphen in der ersten Spalte stellen den Ablauf einer normalen Ausführung dar. Zunächst
entsteht Datentransfer dadurch, dass die Daten für die Map-Aufgaben verteilt werden müssen.
Kurz darauf entsteht Datentransfer um die Zwischenergebnisse von den Map-Workern zu den
Reduce-Workern zu transferieren. Auf der Zeitachse wieder ein Stück versetzt fängt der Datentransfer an, welcher beim Schreiben der finalen Ausgabedateien verursacht wird. Hier ist
zu sagen, dass der Datentransfer zu Stande kommt, weil finale bzw. dauerhafte Ergebnisse
immer redundant, also auf mehreren Clusterknoten, abgelegt werden. Die Ausführung unter
normalen Umständen hat ungefähr 891 Sekunden gedauert. Zum Vergleich wird das bis dahin
schnellste und veröffentlichte Ergebnis von TeraSort mit 1057 Sekunden angegeben. In einem
anderen Testaufbau aus 2012 wurde ein TeraSort virtualisiert in einer sogenannten Cloud innerhalb von 54 Sekunden berechnet. Bei dieser Ausführung waren 1003 virtuelle Knoten mit
jeweils 4 CPU Kernen beteiligt [BAN12, MAP12].
3.3 Effekt von Backup-Tasks und Fehlern
In der mittleren Spalte von Abbildung 3 ist die gleiche Berechnung dargestellt, allerdings ohne die Erweiterung von Backup-Tasks. Die drei dargestellten Graphen haben einen ähnlichen
Charakter, wie die Graphen der normalen Ausführung, allerdings gibt es einen verhältnismäßig langen Teil am Ende der Berechnung, vergleiche dazu Kapitel 2.5. Die Berechnung hat also ohne die Erweiterung von Backup-Tasks etwa 1283 Sekunden gedauert, was einen Anstieg
von 44% an Rechenzeit darstellt.
In der letzten Spalte der Grafik wird das Szenario dargestellt, in dem mitten in der Berechnung 200 Clusterknoten wegbrechen und nicht mehr verfügbar sind. Dem Graphen kann entnehmen worden, dass ungefähr bei Sekunde 250 die Maschinenfehler eintreten, da dort die
Datentransferraten einbrechen. Nach diesem kurzen Einbruch verlaufen die Graphen allerdings wieder charakteristisch ähnlich zu den ersten beiden Durchführungen. Die komplette
10
Thema 2.1: MapReduce
Johannes Unterstein
Berechnung dauert etwa 933 Sekunden, was ein Anstieg von lediglich 5% an Rechenzeit darstellt. In Anbetracht der vielen Maschinenfehler ist der relativ geringe Anstieg der Berechnungszeit ein Zeichen dafür, wie gut das Verfahren mit Fehlern umgehen kann.
4 Auswirkungen und Erfahrungen bei Google
Abbildung 4 – MapReduce-Verwendungsstatistik für verschiedene Monate [DG08, Seite 112]
Nach dem im Jahr 2003 die erste Version des MapReduce-Frameworks vorgestellt wurde,
waren die Autoren positiv überrascht, auf wie viele Arten von Problemen dieses Verfahren
angewendet kann. Das Verfahren wird bei Google unter Anderem für groß angelegtes Maschinenlernen, Cluster-Probleme bei Google News und Froogle, Verarbeitung von Satellitenbildern und die Extrahierung von Schlüsseleigenschaften von Webseiten angewendet. In den
folgenden Jahren hat es eine signifikant steigende Nutzung des Frameworks gegeben.
Wie Abbildung 4 zu entnehmen ist, gab es von den Jahren 2004 bis 2007 einen rasanten Zuwachs an Verwendung. So gab es im August 2004 ungefähr 29.000 MapReduceBerechnungen, welche etwa 3.000 Terabyte an Eingabedaten verarbeitet haben. Im September
2007 sind diese Zahlen schon auf über 2 Millionen Berechnungen gestiegen, welche über
400.000 Terabyte verarbeiten.
Die wohl größte Verwendung des MapReduce-Frameworks bei Google ist das Produktionssystem, welches die Datenstruktur für die Google Websuche berechnet. Im Jahr 2003 wurde
der komplette Produktionscode neu geschrieben und das MapReduce-Framework verwendet.
Zu Beginn bestand der gesamte Algorithmus aus 8 Phasen, welche einzelne und verkette
MapReduce-Berechnungen sind. Im Laufe der Zeit haben sich weitere Phasen ergeben, welche aufgrund der Architektur von MapReduce einfach zu ergänzen waren. Die Hauptverbesserungen der Neuimplementierung sind, dass der Code wesentlich einfacher, besser verständlich und kürzer ist. Weiterhin ist der Code jetzt performant genug, so dass sich der Luxus geleistet werden kann, semantisch unabhängige Berechnungen auch in getrennten Durchläufen
zu berechnen.
5 Apache Hadoop
Das von Dean und Ghemawat beschriebene Konzept, um Verarbeitung von großen Datenmengen in annehmbarer Zeit möglich zu machen, hat sehr großen Anklang gefunden und es
11
Thema 2.1: MapReduce
Johannes Unterstein
gibt mittlerweile in vielen Sprachen eine OpenSource-Implementierung dieses Konzeptes. So
gibt es zum Beispiel das Disco Projekt7 für die Sprachen Python und Erlang oder das Skynet
Projekt8 für die Sprache Ruby. Für die Sprache Java gibt es Projekt Hadoop, welches mittlerweile ein Top Level Projekt der Apache Software Foundation geworden ist [HAD13.1].
5.1 Architektur und Unterschiede zu Google MapReduce
Zunächst müssen wir unterscheiden zwischen dem Konzept MapReduce und der Implementierung von Google in dem gleichnamigem Framework Google MapReduce. Sowohl Google
MapReduce, wie auch Hadoop sind Implementierungen des MapReduce-Konzeptes und sind
sich von den zugrundeliegenden Konzepten und von der Architektur daher sehr ähnlich.
Hadoop ist als OpenSource-Projekt verfügbar und kann somit von jedermann frei genutzt und
modifiziert werden. Das Google MapReduce-Framework ist hingegen mehr oder weniger nur
als Konzept in den Publikationen von Jeffrey Dean und Sanjay Ghemawat verfügbar (für die
Welt außerhalb von Google). Weiterhin ist das Google MapReduce-Framework in der Sprache C++ implementiert und verwendet das GFS als verteiltes Dateisystem. Hadoop hingegen
ist in der Sprache Java Implementiert und verwendet das Hadoop Distributed File System
(HDFS) als verteiltes Dateisystem. Die Architektur von dem GFS und dem HDFS sind relativ
ähnlich und haben die gleichen Ziele bzw. Aufgaben, lediglich die Bezeichnung von bestimmten Komponenten ist unterschiedlich.9
5.2 Prominente Verwender und Erweiterungen
Hadoop wird unter anderem bei Facebook eingesetzt, um Analyse der dortigen Datenquellen
durchzuführen [HAD13.2, Abschnitt #F]. Um den Produktsuchindex aufzubauen wird Hadoop unter anderem bei Amazon und eBay eingesetzt [HAD13.2, Abschnitt #A und #E]. Weiterhin wird Hadoop sehr intensiv bei Yahoo verwendet um zum Beispiel die Datenstruktur für
die Websuche zu berechnen [HAD13.2, Abschnitt #Y]. Durch diese prominente und intensive
Nutzung haben sich einige Erweiterungen an Hadoop ergeben, welche ebenfalls der OpenSource-Gemeinde zugänglich gemacht wurden. So wurde von der Firma Facebook die
Erweiterung Hive entwickelt, welche Abfragen in SQL-ähnlicher Form (HQL) an sehr große
Tabellen ermöglicht [TSJ+09, Seite 1]. Weiterhin wurde von der Firma Yahoo! die Sprache
Pig Latin entwickelt, welche es ermöglicht Programme in einer höheren Sprache zu formulieren und diese in Map- und Reduce-Funktionen übersetzen zu lassen [ORS+08, Seite 1099].
Unabhängig von einer konkreten Firma hat sich die Implementierung von HadoopDB ergeben, welches einen Mischansatz zwischen relationalen Datenbanken als Datenquelle und Hadoop zur Datenverarbeitung darstellt [ABPA+09, Seite 1]. Die Themen HadoopDB, Hive und
Pig sind Bestandteil der Seminarthemen 2.4, 2.5 und 2.6 und werden daher an dieser Stelle
nicht näher erläutert.
7
8
9
Webseite des Projekts http://discoproject.org/, Stand 30.05.2013.
Webseite des Projekts http://skynet.rubyforge.org/, Stand 30.05.2013.
Vergleiche Architekturbeschreibung in [BOR07] über HDFS auf Seite 4 – 8 mit der Architekturbeschreibung von GFS in [DG04] auf Seite 139.
12
Thema 2.1: MapReduce
Johannes Unterstein
6 Schlussbemerkung
Das von Google präsentierte MapReduce-Konzept und die beschriebene Implementierung auf
Basis von handelsüblichen Computern ist sehr leistungsfähig und bietet auch Anwendungsentwicklern ohne umfangreiche Erfahrung von Parallelisierung die Infrastruktur um performante und fehlertolerante parallele Berechnungen durchzuführen. Durch die Restriktion des
zur Verfügung stehenden Programmiermodells konnte eine sehr gute Parallelisierung und
Verteilung der Berechnungen erreicht werden. Trotzdem konnte das Programmiermodell so
simpel gehalten werden, dass es einfach zu verwenden ist. Immerhin müssen nur zwei Funktionen implementiert werden.
Aus den genannten Gründen hat das MapReduce-Framework bei Google, wo mittlerweile die
Datenstruktur für den Websuchindex mittels MapReduce berechnet wird, einen Siegeszug angetreten. Es hat weiterhin auch in der OpenSource-Gemeinde großen Anklang gefunden. Als
ein prominentes Beispiel ist hier das Projekt Hadoop zu nennen, welches mittlerweile bei
Größen wie Facebook, Yahoo!, eBay oder Amazon eingesetzt wird. Basierend auf dem initialen Konzept haben sich viele Erweiterungen an Hadoop ergeben, welche im weiteren Seminarverlauf vorgestellt werden.
13
Thema 2.1: MapReduce
Johannes Unterstein
7 Literatur
[ABPA+09]
Abouzeid, A. ; Bajda-Pawlikowski, K. ; Abadi, D. ; Silberschatz, A. ; Rasin,
A.: HadoopDB: An architectural hybrid of MapReduce and DBMS technologies for analytical workloads. In: Proceedings of the VLDB Endowment 2
(2009), Nr. 1, S. 922–933. – ISSN 2150–8097
[BAN12]
Bandugula, N.: Breaking the Minute Barrier for TeraSort, Web Publikation
(2012), http://www.wired.com/insights/2012/11/breaking-the-minute-barrierfor-terasort/. Stand 30.05.2013
[BOR07]
Borthakur, D.: The Hadoop Distributed File System: Architecture and Design,
Web
Publikation
(2007),
http://hadoop.apache.org/docs/r0.18.0/hdfs_design.pdf. Stand 30.05.2013
[DG04]
Dean, J. ; Ghemawat, S.: MapReduce: Simplified Data Processing on Large
Clusters. In: Proceedings of Operating Systems Design and Implementation
(OSDI). San Francisco, CA, 2004, S. 137 – 150
[DG08]
Dean, J. ; Ghemawat, S.: MapReduce: Simplified Data Processing on Large
Clusters. In: Communications of the ACM 51 (2008), January, Nr. 1, S. 107 –
113
[HAD13.1]
Apache Hadoop.
30.05.2013.
[HAD13.2]
Apache Hadoop. Web Publikation. http://wiki.apache.org/hadoop/PoweredBy.
Stand 30.05.2013.
[MAP12]
MapR: MapR and Google Compute Engine Set New World Record for Hadoop
TeraSort,
Web
Publikation
(2012),
http://www.mapr.com/company/press-releases/mapr-and-google-computeengine-set-new-world-record-for-hadoop-terasort. Stand 30.05.2013
[ORS+08]
Christopher Olston, Benjamin Reed, Utkarsh Srivastava, Ravi Kumar, and
Andrew. Tomkins. Pig latin: a not-so-foreign language for data processing. In
SIGMOD. Conference, S. 1099-1110, 2008.
[TSJ+09]
Ashish Thusoo, Joydeep Sen Sarma, Namit Jain, Zheng Shao, Prasad Chakka,
Suresh. Anthony, Hao Liu, Pete Wycko, and Raghotham Murthy. Hive- a warehousing solution over a map-reduce framework. In IN VLDB '09: PROCEEDINGS OF THE VLDB ENDOWMENT, S. 1626-1629, 2009.
Web
Publikation.
14
http://hadoop.apache.org.
Stand
Seminararbeit zum Thema
Google File System
Student:
Simon Eiersbrock
Betreuung: Fabio Valdés
FernUniversität in Hagen
Fakultät für Mathematik und Informatik
Studiengang Master of Science in Praktischer Informatik
Sommersemester 2013
Seminar 1912 Big Data Management
2
Inhaltsverzeichnis
1 Einleitung
1.1 Dateisysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Konventionelle Dateisysteme . . . . . . . . . . . . . . . . . . . . .
1.3 Verteilte Dateisysteme . . . . . . . . . . . . . . . . . . . . . . . .
1
1
2
2
2 Google File System
2.1 Anforderungen . . . . . . . . . . . . .
2.2 Architektur . . . . . . . . . . . . . . .
2.3 Korrektheit . . . . . . . . . . . . . . .
2.3.1 Priorisierte Master-Operationen
2.3.2 Atomic Record Appends . . . .
2.3.3 Locking . . . . . . . . . . . . .
2.3.4 Prüfsummen . . . . . . . . . . .
2.3.5 Stale Replica Detection . . . . .
2.3.6 Gleiche Replizierung . . . . . .
2.3.7 Applikationen . . . . . . . . . .
2.4 Fehlertoleranz . . . . . . . . . . . . . .
2.4.1 Replizierung . . . . . . . . . . .
2.4.2 Shadow Master . . . . . . . . .
2.4.3 Replica Placement . . . . . . .
2.4.4 Re-replication . . . . . . . . . .
2.4.5 Verzögertes Löschen . . . . . .
2.4.6 Regelmäßige Kommunikation .
3
3
4
6
6
6
7
7
7
8
8
8
8
8
9
9
9
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Praktischer Einsatz
10
4 Fazit und Ausblick
4.1 Das Google File System . . .
4.2 Verwandte Technologien . . .
4.2.1 Parallele Datenbanken
4.2.2 AFS . . . . . . . . . .
12
12
12
13
13
Literaturverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iii
i
ii
1 Einleitung
Das Google File System (kurz: GFS) ist ein verteiltes Dateisystem, welches von
Google auf seinen Servern und dort speziell für die Websuche verwendet wird
[DG04, S. 147] [GGL03, S. 29]. Das GFS wurde von Google entwickelt und an die
Anforderungen des Unternehmens angepasst. Als verteiltes Dateisystem bestehen
zusätzlich zu den Anforderungen an ein konventionelles System einige Besonderheiten: Es müssen Aspekte wie Geschwindigkeit, Skalierbarkeit, Ausfallsicherheit
und Verfügbarkeit gesondert beachtet werden. Google hat diese Punkte unter
Berücksichtigung der Systemlandschaft und der bei Google eingesetzten Applikationen in die GFS-Architektur einfließen lassen [GGL03, S. 29].
In diesem Dokument soll nach einem kurzen allgemeinen Überblick über konventionelle und verteilte Dateisysteme auf die Einzelheiten des GFS eingegangen
werden. Ein besonderer Schwerpunkt wird dabei auf die Teilaspekte Korrektheit
und Fehlertoleranz gelegt. Zum Abschluss wird noch ein kurzer Vergleich zu verwandten Technologien wie verteilten Datenbanken oder dem Andrew File System
(AFS) gezogen.
1.1 Dateisysteme
Es gehört zu den Grundfunktionen einer jeden Anwendung, Daten anzuzeigen
und gegebenenfalls zu speichern. Diese Daten können im eigenen Arbeitsspeicher
abgelegt werden, jedoch ergeben sich diverse Nachteile bezüglich Kapazität, Übertragbarkeit und Dauerhaftigkeit der Datenspeicherung. Dateisysteme bieten eine
Lösung für diese Probleme und dienen der Organisation der Datenspeicherung.
Dadurch ist es möglich
• eine große Menge von Informationen zu speichern
• die Informationen bei Ende der Anwendung zu erhalten
• mehrere Prozesse auf die Daten zugreifen zu lassen
Die Speicherung von Daten basiert heute auf einer Unterteilung des Speichers in
Blöcke. Das Dateisystem teilt eine Datei auf ein oder mehrere freie Speicherblöcke
auf und stellt sie anderen Anwendungen als eine Einheit zur Verfügung. Zur
Optimierung des belegten Speichers ist es üblich, Dateien nicht sequentiell zu
speichern, sondern alle freien Speicherstellen zu nutzen [Tan09, S. 314].
1
1 Einleitung
1.2 Konventionelle Dateisysteme
Konventionelle Dateisysteme findet man auf jedem modernen Windows- oder
Linux-basierten Rechner. Diese Systeme arbeiten im Normalfall auf nur einer
Festplatte.1 Weit verbreitet sind die Systeme NTFS und FAT (Windows) oder
ext3 und ext4 (Linux). Diese Systeme haben bei allen Unterschieden auch viele
Gemeinsamkeiten:
• Blockweise Speicherung von Dateien
• Einheitliche Schnittstelle für Anwendungen
• zusätzliche Funktionen (z.B. Verwaltung von Zugriffsrechten, unterschiedliche Dateitypen)
Genutzt werden diese Systeme zur Speicherung der Betriebssystem-, Anwendungsund Benutzerdaten. Optimiert sind sie eher auf kleine Dateien und einen lokalen
Betrieb [Tan09, S. 353ff].
1.3 Verteilte Dateisysteme
Lokale Dateisysteme beschränken sich - wie ihr Name schon sagt - auf einen lokalen Computer. Damit gibt es Einschränkungen bei Aspekten wie Geschwindigkeit,
Datensicherheit oder Speicherkapazität. Verteilte Dateisysteme bieten Lösungen
für diese Einschränkungen an, da sie sich nicht nur auf einen Datenspeicher oder
einen Computer beschränken. Über Netzwerkverbindungen können sie auch zusammenhängende Daten über Rechnergrenzen hinweg verwalten. Ein verteiltes
Dateisystem bringt diverse Vorteile [GGL03, S. 29]:
• Höhere Verfügbarkeit
Daten können redundant gespeichert werden, so dass ein Client nicht merkt,
wenn eine Kopie ausfällt
• Skalierbarkeit
Wird mehr oder weniger Speicher gebraucht, können jederzeit Systeme hinzugefügt oder entfernt werden
• Fehlertoleranz
Ein Client bekommt von Problemen auf der Serverseite nichts mit. Ausfälle
einzelner Systeme werden durch redundante Speicherung abgefangen.
1
2
Ausnahme sind nur die sogenannten RAID-Systeme, bei denen mittels eines speziellen Controllers mehrere Festplatten zu einer Einheit zusammengeschlossen werden. Aus Sicht des
Dateisystems ist eine solcher RAID-Verbund aber einfach eine große Festplatte.
2 Google File System
2.1 Anforderungen
An das GFS bestehen neben den allgemeinen Anforderungen an ein (verteiltes)
Dateisystem diverse weitere Anforderungen, die auf folgenden Annahmen und
Beobachtungen basieren [GGL03, S. 30] :
• Systemausfälle
An einem GFS-Cluster können mehrere tausend Einzelsysteme beteiligt
sein. Es ist sehr wahrscheinlich, dass immer mindestens eines dieser Systeme ausfällt. Das Gesamtsystem darf davon aber nicht beeinflusst werden
und Ausfälle müssen automatisch entdeckt und abgefangen werden.
• Große Dateien
Im Vergleich zu Dateien auf einem üblichen Computer sind die Dateien
bei Google sehr groß, mehrere GB sind keine Seltenheit. Das GFS muss für
diese großen Dateien optimiert sein. Kleine Dateien müssen auch unterstützt
werden, eine Optimierung für sie findet aber nicht statt.
• Viel Lesezugriff
Einmal geschriebene Daten werden bevorzugt gelesen und nicht mehr verändert.
• Dateien wachsen durch Anhängen
Die meisten Dateien wachsen durch Anhängen von Informationen, nicht
durch wahlfreies Schreiben in der Datei. Einmal geschriebene Daten werden
selten verändert. Kleine Schreibzugriffe an beliebige Stellen in der Datei
werden unterstützt, sind aber nicht effizient.
• Konkurrierende Zugriffe
Mehrere Clients können in die selbe Datei schreiben. Dabei muss sichergestellt sein, dass die Datei auch bei redundanter Speicherung immer korrekt
bleibt.
• Bekannte Anwendungsfälle
Auf dem GFS arbeiten Applikationen, welche auch von Google entwickelt
wurden. Die Anforderungen an das Dateisystem sind also einerseits sehr
genau bekannt und es kann z.B. ein einfacheres Konsistenzmodell genutzt
werden. Andererseits sind den Anwendungen auch Schwächen des GFS bekannt und können darauf optimiert werden. Möchte eine Anwendung z.B.
3
2 Google File System
Abbildung 2.1: GFS-Architektur[GGL03, S. 31]
viele kleine Lesezugriffe auf eine Datei machen, so wird sie die Zugriffe vorher sortieren und dem Server in einer Anfrage übermitteln.
• Anwendungsschnittstelle
Das GFS bietet eine Dateisystem-übliche Schnittstelle mit Ordnern und
Dateiname, unterstützt aber die für konventionelle Dateisysteme bekannten
Funktionen nicht vollständig. Typische Operationen wie CREATE, READ,
DELETE usw. werden aber angeboten, zusätzlich einige weitere Funktionen.
2.2 Architektur
Die Architektur eines GFS-Clusters ist in Abbildung 2.1 dargestellt. Ein solcher
Cluster besteht aus einem Master-Server und mehreren Chunk-Servern. Auf ihn
wird von mehreren Clients zugegriffen. Die gespeicherten Dateien sind in einzelne
Teile (so genannte Chunks) aufgeteilt, jeder dieser Chunks hat eine eigene feste
64 bit große ID. Ein Chunk wird von den Chunkservern als normale Linux-Datei
gespeichert und wird zur Steigerung der Datensicherheit auf mehreren ChunkServern repliziert. Die Verwaltung der Chunks übernimmt der Master. Dort ist
bekannt, wo welcher Chunk gespeichert ist, welcher Chunk zu welcher Datei gehört und wer auf die Chunks zugreifen darf.
Ein Client, der auf eine Datei zugreifen möchte, stellt seine Anfrage immer zuerst an den Master-Server. Der Master teilt dem Client dann mit, auf welchem
Chunkserver die gewünschten Daten gespeichert sind. Zwischen Master und Client werden nur Metadaten ausgetauscht, die eigentliche Datenübertragung findet
dann zwischen Chunkserver und Client statt. Der Master hat also Kenntnis von
jeder Client-Anfrage und kann daraus zusätzliche Maßnahmen ableiten: Um die
Last besser zu verteilen, können die Client-Requests auf diverse Chunkserver
verteilt werden und durch Auswertung von Zugriffsstatistiken kann eine bessere
örtliche Verteilung der einzelnen Chunks erreicht werden (Welcher Client greift
besonders häufig auf welche Datei zu? Kann man die Datei näher an den Client
4
2.2 Architektur
kopieren?)
Durch diese Architektur kann der Master aber auch zu einem Bottleneck oder
Single Point of Failure werden. Ist er nicht erreichbar, kann kein Client mehr auf
die Dateien zugreifen und noch schlimmer: Werden die Metadaten zerstört, so
sind auch die Dateien nicht mehr lesbar, obwohl es auf den Chunkservern keinen
Fehler gab.
Um einen Ausfall des Masters zu vermeiden und die Geschwindigkeit nicht zu
reduzieren, gibt es diverse Maßnahmen:
• Operation Log
Jede Änderung an den Metadaten wird in einer Logdatei festgehalten. Diese
Logdatei liegt auf der Festplatte des Masters und wird auch auf andere
Systeme gesichert.
• Intelligentere Chunkserver
Der Speicherort eines Chunks wird nicht auf dem Master gespeichert. Der
Master sammelt diese Informationen von jedem Chunkserver ein und hält
sie nur in seinem Arbeitsspeicher.
• Wenig Master-Kommunikation
Das Gesamtsystem ist durch diverse Maßnahmen darauf ausgelegt, das der
Master möglichst wenig in die Kommunikation mit einbezogen wird.
– Blockgröße
Ein Chunk ist 64 MB groß, dadurch kann ein Client mit nur einer
Anfrage an den Master vergleichsweise viele Daten lesen1
– Client-Caching
Um die Last auf dem Master zu reduzieren, cached jeder Client die
Anfragen an den Master für eine gewisse Zeit. Damit muss nicht bei
jedem neuen Zugriff auf einen schon bekannten Chunk wieder der Master nach dem Speicherort des Chunks gefragt werden.
– Veränderungen auf einem Chunk werden von den Chunkservern selbständig an andere Chunks repliziert. Dazu bestimmt der Master einen
primären Chunkserver für einen Chunk, der die weitere Organisation
übernimmt.
• Speicherung im Arbeitsspeicher
Die Metadaten sind nicht besonders groß, für jeden 64 MB großen Chunk
gibt es nur 64 Bytes an Metadaten. Diese Daten können vollständig im
schnellen Arbeitsspeicher gehalten werden. [GGL03, S. 31f]
1
Die Größe eines Chunks entspricht bei einem konventionellen Dateisystem der Blockgröße.
Dass ein Chunk 64 MB groß ist, ist ein guter Indikator für die erwartete Größe der Dateien.
Je größer eine Datei ist, umso effizienter ist es, sie in großen Blöcken abzuspeichern, lokale
Dateisysteme nutzen Blockgrößen im Bereich bis 100 KB [Tan09, S.354f]
5
2 Google File System
Einen Überblick über alle aktuell laufenden Chunkserver verschafft sich der Master mit so genannten "Heartbeat Messages". In regelmäßigen Abständen werden
so z.B. veraltete Chunks entdeckt oder primäre Chunkserver bestimmt [GGL03,
S. 30].
2.3 Korrektheit
Ein wichtiger Punkt des GFS ist die Datenintegrität. Das System muss sicherstellen, dass die Informationen in einer Datei immer vollständig sind, keine Informationen verloren gehen und replizierte Chunks konsistent sind und sich somit
nicht widersprechen. Die Anforderungen an die Eigenschaft "korrekt" sind für
das GFS relativ tolerant gehalten. Es kann z.B. sein, dass die Replikate eines
Chunks byteweise unterschiedlich sind. Für das GFS ist es nur wichtig, dass aus
den Chunks immer die gleiche Nutz-Information gelesen werden kann.
Es gibt diverse Funktionen um die Korrektheit sicherzustellen, diese sollen im
Folgenden vorgestellt werden:
2.3.1 Priorisierte Master-Operationen
Das Operation Log des Masters darf auf eine Client-Anfrage keine veralteten
Informationen herausgeben: Auf eine Anfrage wird erst geantwortet, nachdem
alle Metadaten-Änderungen auf dem Master durchgeführt und diese Änderungen
an die Sicherungen weitergereicht wurden [GGL03, S. 32].
2.3.2 Atomic Record Appends
Greifen mehrere Clients gleichzeitig schreibend auf die gleiche Region in einer
Datei zu, kommt es bei konventionellen Dateisystemen zu Konflikten. Daten können verloren gehen oder die ganze Datei kann zerstört werden. Zur Lösung dieses
Problems bietet das GFS die Funktion record append. Im Gegensatz zu einem
normalen write werden hierbei vom Client nur die Daten zur Verfügung gestellt,
nicht der exakte Ort in der Datei, an dem sie abgelegt werden sollen. Damit
bestimmt GFS den exakten Speicherort der Daten in der Datei und die Clients
benötigen keine Extra-Synchronisation. Nachdem die Daten auf allen Chunkservern bereit stehen, bestimmt der primäre Chunkserver den Speicherort der Daten
und teilt diesen allen anderen Chunkservern mit. Kommt es dabei zu einem Problem, wird der ganze Schreibvorgang wiederholt. Es kann also passieren, dass die
Daten auf einigen Servern schon geschrieben wurden, auf anderen aber noch nicht.
Die Konsequenz daraus ist, dass sich eigentlich gleiche Chunks unterscheiden können. GFS garantiert aber, dass die Daten mindestens einmal erfolgreich auf allen
Chunks an die gleiche Stelle geschrieben werden. Die Applikationen können mit
den inkonsistenten Bereichen in den Dateien umgehen [GGL03, S. 34].
6
2.3 Korrektheit
2.3.3 Locking
Änderungen am Namensraum (Dateien löschen, anlegen, umbenennen) sind atomar und werden nur über den Master ausgeführt. Die Operationen fordern geeignete Lese- und Schreib-Sperren an, um ihre Änderungen durchzuführen und
dabei andere Operationen so wenig wie möglich zu stören. Lese-Sperren können
an beliebig viele Clients vergeben werden. Eine Schreibsperre ist dagegen exklusiv und kann auch nur gewährt werden, wenn es auf der Datei/dem Ordner noch
keine Lesesperre gibt.
Ein Beispiel: Sollen von zwei unterschiedlichen Clients aus neue Dateien in einem Ordner angelegt werden, so werden beide eine Lesesperre auf den Ordner
setzen und eine Schreibsperre auf die neu anzulegenden Dateien. Versucht jetzt
ein dritter Client, den Ordner umzubenennen, erhält er keine Schreibsperre auf
den Ordner, da es bereits Lesesperren darauf gibt. Er muss warten, bis alle Lesesperren wieder freigegeben wurden. Damit können zwei Operationen parallel
arbeiten, die dritte Operation muss warten [GGL03, S. 35].
2.3.4 Prüfsummen
Für einzelne Blöcke in den Chunks werden Prüfsummen berechnet und im Speicher des Chunkservers gehalten. Diese Prüfsummen werden beim lesenden Zugriff
vom Chunkserver kontrolliert. Bei einem Fehler wird dem Client mitgeteilt, dass
er von einer anderen Replik lesen muss und der Master darüber informiert, dass
die Replik erneuert werden muss.
Der Chunkserver prüft außerdem in regelmäßigen Abständen die Prüfsummen
aller Chunks. Bei Bedarf wird der Chunk erneuert [GGL03, S. 38].
2.3.5 Stale Replica Detection
Chunks erhalten Versionsnummern. So kann der Master bei einem Update eines
Chunks feststellen, ob der Chunk veraltet ist. Typischerweise ist ein Chunk veraltet, wenn der Chunkserver oder eine Netzwerkverbindung ausgefallen ist und
dadurch ein Update verpasst wurde. Ein solcher veralteter Chunk wird nicht weiter genutzt, sondern gelöscht und wenn erforderlich durch eine neue Replik ersetzt
[GGL03, S. 37].
An dieser Stelle gibt es die Möglichkeit für einen Fehler: Wenn ein Chunk nicht
mehr aktuell ist, wird das erst bei der nächsten Heartbeat Message bemerkt. Hat
ein Client die Adresse des veralteteten Chunks in seinem Cache, so wird von diesem veralteten Chunk gelesen. Da die meisten Dateien bei Google aber durch
Anhängen und nicht durch Einfügen von Daten verändert werden, ist die Wahrscheinlichkeit einer fehlerhaften Leseaktion sehr gering. Ein Client wird in einem
solchen Fall zwar nicht alle Daten sehen, aber auch keine falschen [GGL03, S.
33].
7
2 Google File System
2.3.6 Gleiche Replizierung
Änderungen werden auf jeder Chunk-Replik in der gleichen Reihenfolge durchgeführt [GGL03, S. 34].
2.3.7 Applikationen
Wie bereits in der Einleitung erwähnt, war ein großer Vorteil bei der Entwicklung des GFS das stark eingeschränkte Anwendungsgebiet: Alle Anwendungen
können speziell für die Eigenschaften des GFS angepasst und optimiert werden.
Das bedeutet, dass Anwendungen die Daten praktisch immer durch Anhängen
verändern und selbst auch Korrektheitsprüfungen implementiert haben [GGL03,
S. 33].
2.4 Fehlertoleranz
Im GFS sind diverse Maßnahmen eingebaut, um Hardware-Fehler zu tolerieren,
da diese ab einer gewissen Anzahl von Teilkomponenten unausweichlich sind2 .
2.4.1 Replizierung
Wie schon zuvor besprochen werden Chunks auf andere Server repliziert. Das
betrifft aber nicht nur die Chunkserver, auch der Master sichert sein Operation
Log auf verschiedene andere Systeme. Fällt der Master aus, wird auf einer anderen
Maschine ein neuer Masterprozess gestartet. Der Ausfall des Masters wird von
Monitoring-Systemen außerhalb von GFS bemerkt [GGL03, S. 37].
2.4.2 Shadow Master
Auf den Maschinen, auf die das Operation Log repliziert wird, laufen weitere
Master-Prozesse. Diese nachrangigen Master werden "Schatten-Master" genannt.
Bei Ausfall des Masters muss nicht unbedingt ein neuer Master gestartet werden,
2
8
Ein Beispiel dazu: Dass eine Festplatte einen bestimmten Zeitraum funktioniert, hat eine
Wahrscheinlichkeit von 99%. Ein Verbund von zwei Festplatten hat dann eine Wahrscheinlichkeit von 98,01%, diesen Zeitraum ohne Funktionsverlust zu überstehen. Je mehr Festplatten wir dem Verbund hinzufügen, umso geringer wird die Wahrscheinlichkeit für einen
ausfallfreien Betrieb.
2.4 Fehlertoleranz
ein Schattenmaster kann auch zum primären Master werden. Diese Schattenmaster können auch zur Entlastung des Masters genutzt werden, z.B. bei Leseoperationen auf älteren, nicht mehr veränderten, Dateien3 oder für Applikationen, die
nicht auf den aktuellsten Stand angewiesen sind [GGL03, S. 37f].
2.4.3 Replica Placement
Repliken eines Chunks werden nicht nur über verschiedene Server verteilt, sondern
auch über verschiedene Racks4 . Damit wird auch der Ausfall eines ganzen Racks
abgesichert. Nachteil dieser Organisation ist die Kommunikation zwischen den
Racks, welche weniger performant ist als die Kommunikation direkt zwischen
zwei Servern in einem Rack [GGL03, S. 36].
2.4.4 Re-replication
Sobald die Anzahl der Repliken eines Chunks unter eine bestimmte Grenze fällt,
werden so bald wie möglich ein oder mehrere neue Repliken erstellt. Dabei gibt
es eine Prioritätssteuerung: Ein Chunk ohne Repliken wird eher repliziert, als
ein Chunk, welcher noch mindestens eine funktionierende Replik hat [GGL03, S.
36].
2.4.5 Verzögertes Löschen
Dateien werden nicht gelöscht, sondern umbenannt und erst nach einer bestimmten Zeit wirklich gelöscht [GGL03, S. 36].
2.4.6 Regelmäßige Kommunikation
Über die Heartbeat Message ist der Master immer auf einem relativ aktuellen
Stand über den Cluster und bemerkt nicht erst bei einem Client-Zugriff, dass ein
Chunkserver ausgefallen ist [GGL03, S. 30].
3
4
Zur Erinnerung: Einmal geschriebene Dateien werden selten verändert
Ein Rack ist ein spezieller Schrank oder ein Gestell, in dem mehrere Server betrieben werden.
Die Server können sich z.B. die Stromversorgung über den Rack teilen
9
3 Praktischer Einsatz
In den letzten Kapiteln wurden diverse theoretische Aspekte des GFS vorgestellt.
Von Google selbst gibt es aber auch einige interessante Zahlen zu GFS-Clustern,
welche im produktiven Einsatz sind.
Tabelle 3.1: Dimensionierung zweier
Cluster
Anzahl Chunkserver
Verfügbarer Speicherplatz
Verbrauchter Speicherplatz
Anzahl Dateien
Anzahl inaktiver Dateien
Anzahl Chunks
Metadaten Chunkserver
Metadaten Master
GFS-Cluster [GGL03, S. 39]
A
B
342
227
72 TB
180 TB
55 TB
155 TB
735.000 737.000
22.000
232.000
992.000 1.550.000
13 GB
21 GB
48 MB
60 MB
Wie man sieht, schafft es GFS sehr große Datenmengen zu verwalten. Die Zahlen sind umso beeindruckender, wenn man bedenkt, dass sie aus dem Jahr 2001
stammen. Auch interessant ist das Verhältnis der Metadaten des Masters zum
verbrauchten Speicherplatz und der Anzahl der Chunks. Es werden tatsächlich
sehr wenige Metadaten zur Verwaltung benötigt.
Weitere von Google veröffentlichte Zahlen betreffen den durchschnittlichen Datendurchsatz der Cluster. Diese Zahlen belegen vor allem die anfängliche Annahme,
Tabelle 3.2: Datendurchsatz zweier GFS-Cluster [GGL03, S. 40]
A
B
Cluster
Leserate (seit Neustart)
589 MB/s 49 MB/s
Schreibrate (seit Neustart)
25 MB/s 13 MB/s
Master-Operationen (seit Neustart) 202 OP/s 347 OP/s
dass mehr lesender Zugriff als schreibender Zugriff stattfindet. Zur tatsächlich
erreichbaren Geschwindigkeit gibt es noch Zahlen aus einem fiktiven Testszenario
mit 16 Chunkservern, 16 Clients und 3 Masterservern (davon zwei als Schattenmaster). Dabei wurde die theoretisch erreichbare Durchsatzrate mit der tatsächlich erreichten Durchsatzrate verglichen. Die Leserate liegt bei Zugriff aller
Clients bei ca. 75%, die Schreibrate bei ca. 50% der maximal erreichbaren Rate.
10
Die im Vergleich niedrige Schreibrate lässt sich vor allem damit erklären, dass
jeder Chunk mehrfach aktualisiert werden muss (Jede Kopie einmal) [GGL03, S.
40].
Auch die Wiederherstellungszeit beim Verlust eines Chunks wurde mit den echten Clustern A und B getestet. Der Verlust eines Chunks wurde nach 23 Minuten
ausgeglichen, der Verlust zweier Chunks (was eine hohes Datenverlust-Risiko bedeutet, da nur noch eine Kopie des Chunks existiert) nach 2 Minuten [GGL03, S.
40].
11
4 Fazit und Ausblick
4.1 Das Google File System
"Despite the name, GFS [..] is not just a file system. It also maintains data redundancy, supports low-costs snapshots, and, in addition to normal [..] operations
also offers a record append operation." [Har06]
Abschließend kann man das Fazit von Robin Harris nur bestätigen: GFS ist nicht
nur ein auf Geschwindigkeit optimiertes verteiltes Dateisystem, sondern kann
noch mehr: Es legt auch ein großes Augenmerk auf die Hochverfügbarkeit der
Daten und erfüllt die speziellen Bedürfnisse von Googles Applikationen. Die Anforderungen, welche zur Entwicklung des GFS geführt haben, werden mit dem
System erreicht. Systemausfälle werden abgefangen und automatisch ausgeglichen, große Dateien werden optimal unterstützt und der Lesezugriff wird besonders unterstützt. Konkurrierende Zugriffe verschiedener Schreiboperationen sind
möglich und die vorrangige Schreiboperation "Speichern durch Anhängen" wird
mit der speziellen Funktion record append unterstützt. Insbesondere die integrierte Datensicherung ist gut gelungen. Wenn das System optimal arbeitet, sind keine
besonderen Backup-Maßnahmen erforderlich.
Diese ganzen Vorteile sind aber auch ein deutlicher Hinweis auf die Nachteile des
GFS: Es ist ein System, welches speziell für Google entwickelt wurde und auch nur
dort seine ganze Leistungsfähigkeit ausspielen kann. GFS ist nicht performant,
wenn der Anwendungsfall viele kleine Dateien oder viele kleine Schreibzugriffe
an wahlfreie Stellen in die Dateien erfordert [GGL03, S. 30]. Außerdem erfordern einige Funktionen wie record append oder die recht pragmatische Erhaltung
der Konsistenz bei Schreibkonflikten eine gute Integration mit den Applikationen. Inkonsistente Bereiche in den Dateien sind möglich und müssen von den
Applikationen selbständig entdeckt werden.
4.2 Verwandte Technologien
Neben dem GFS gibt es noch weitere interessante Systeme, welche sich mit verteilter Datenspeicherung befassen.
12
4.2 Verwandte Technologien
4.2.1 Parallele Datenbanken
Die verteilte Speicherung von Daten, sei es zur Datensicherung oder auch für
eine bessere Verfügbarkeit oder Skalierbarkeit, kann auch mit verteilten Datenbankservern erreicht werden. Diese Systeme verhalten sich dem Anwender gegenüber im Wesentlichen wie eine normale Datenbank, auf die mit SQL zugegriffen
wird [Dad96, S. 5, KE 1]. Das bedeutet aber auch, das zur Synchronisierung der
verschiedenen Teilsysteme ein erheblicher Mehraufwand betrieben werden muss.
Konkurrierende Zugriffe müssen mittels Abstimmungsverfahren1 zwischen den
einzelnen Knoten abgesprochen werden, Schreibzugriffe mit mehreren beteiligten
Knoten müssen mittels eines speziellen Protokolls2 abgesichert werden. [Dad96,
S. 20ff, KE 5]
Abgesehen vom Unterschied Dateisystem/Datenbank zeigt sich hier der deutlichste Unterschied zu GFS: Da den Anwendungen bekannt ist, welche Restriktionen
mit GFS verbunden sind, können sie mit zusätzlichen Validierungen oder Wiederholungsfunktionen bestimmte Schwächen ausgleichen und mit einem angepassten Schreib- und Leseverhalten eine bessere Geschwindigkeit erreichen. Verteilte
Datenbanken hingegen werden für alle möglichen Anwendungen eingesetzt und
müssen dementsprechend robust und fehlerunanfällig sein.
4.2.2 AFS
Ein anderes verteiltes Dateisystem ist das Andrew File System (AFS). Dieses
System wurde ab 1982 an der Carnegie Mellon University mit dem Ziel entwickelt,
jedem Anwender einen zentralen Speicherort zur Verfügung zu stellen [H+ 88, S.
1]. Ähnlich wie beim GFS gibt es auch beim AFS viele vergleichsweise kleine
Server, auf welche die Last verteilt wird. Anders als GFS soll sich AFS aber
soweit wie möglich in ein lokales UNIX-Dateisystem integrieren [H+ 88, S. 2].
Darum unterscheidet sich AFS dann in zentralen Punkten von GFS:
• Client-Caching
Der Client lädt eine Datei vollständig auf die lokale Festplatte, bevor sie
bearbeitet werden kann. Wird die Datei verändert, wird sie anschließend
zurückkopiert. Vorteil dieser Vorgehensweise ist weniger Netzwerk- und Serverlast, Nachteil ist, das parallele Schreibzugriffe nicht möglich sind. Es gibt
für den Client aber die Möglichkeit, einen Konflikt zu erkennen: Wird eine Datei geschrieben, werden alle Clients, die diese Datei gecached haben,
darüber informiert [H+ 88, S. 2].
1
2
Eine Transaktion muss die Mehrheit der beteiligten Knoten für sich gewinnen
Zwei-Phasen-Commit: Jeder Knoten bringt sich selbst vor Ausführung des finalen commits in
einen Zustand, aus dem er selbst bei Systemabsturz sowohl einen Abbruch der Transaktion
als auch einen commit der Transaktion durchführen kann.
13
4 Fazit und Ausblick
• Unabhängigkeit von Anwendungen
Eine Anwendung soll nicht speziell für das AFS angepasst werden müssen.
Darum müssen alle UNIX-üblichen Befehle zur Verfügung stehen [H+ 88, S.
2].
• Ganze Dateien
Es werden nur ganze Dateien gespeichert, keine Blöcke. Das limitiert die
Größe einer Datei auf den auf dem Client zur Verfügung stehenden Speicherplatz [H+ 88, S. 2].
Ein weiterer Unterschied ist das integrierte Backup des GFS. Die Sicherung der
Dateien eines AFS ist Aufgabe eines Administrators und wird nicht automatisch
vom System erledigt. AFS bietet aber einige Unterstützung für das Backup wie
z.B. das Klonen von ganzen Speicherbereichen [H+ 88, S. 4].
Am Beispiel des AFS wird noch einmal deutlich, wie sehr sich ein verteiltes Dateisystem von einem anderen System unterscheiden kann, wenn der Anwendungszweck sehr verschieden ist. GFS ist eine Spezialanwendung für Google, AFS ist
viel näher an einem Standard-Dateisystem und wird in vielen verschiedenen Szenarien vor allem an Universitäten und Instituten erfolgreich eingesetzt [ope].
14
Literaturverzeichnis
[Dad96] Dadam, Dr. Peter: Datenbanken in Rechnernetzen. FernUniversität
in Hagen, 1996.
[DG04]
Dean, Jeffrey und Sanjay Ghemawat: Mapreduce: simplified data
processing on large clusters. In Proceedings of the 6th conference on
Symposium on Opearting Systems Design and Implementation - Volume 6, Seiten 137–149, 2004.
[GGL03] Ghemawat, Sanjay, Howard Gobioff und Shun-Tak Leung:
The Google File System. SIGOPS Oper. Syst. Rev., 37(5):29–43, Oktober 2003.
[H+ 88]
Howard, John H et al.: An overview of the andrew file system. Carnegie Mellon University, Information Technology Center, 1988.
[Har06]
Harris,
Robin:
Google
File
System
Eval:
Part
I.
http://storagemojo.com/google-file-system-eval-part-i/, Letzter Abruf:
2. Juni 2013, 2006.
[ope]
OpenAFS Success Stories. http://www.openafs.org/success.html, Letzter Abruf: 11. Juni 2013.
[Tan09] Tanenbaum, Andrew S.: Moderne Betriebssysteme. Pearson Studium, 3. Auflage, 2009.
iii
Fernuniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 2.3
Kontroverse: MapReduce vs. Parallele DBMS
Referentin: Alica Moser
Inhaltsverzeichnis
1 Gemeinsamkeiten der beiden Ansätze..........................................................................................1
2 Architekturunterschiede................................................................................................................1
2.1 Verteilung & Scheduling.......................................................................................................1
2.2 Datenformat .........................................................................................................................1
2.3 Programmiermodell..............................................................................................................2
2.4 Netzwerklast..........................................................................................................................2
2.5 Ausführungsstrategie.............................................................................................................2
3 Vor- und Nachteile........................................................................................................................2
3.1 Datenformat .........................................................................................................................2
3.2 Flexibilität.............................................................................................................................3
3.3 Komplexe Funktionen...........................................................................................................3
3.4 Fehlertoleranz........................................................................................................................3
3.5 Implementierungsaufwand....................................................................................................4
3.6 Indizes...................................................................................................................................4
3.7 Projektgröße .........................................................................................................................4
3.8 Benutzbarkeit & Wartbarkeit................................................................................................5
3.9 Systemkosten .......................................................................................................................5
4 Performanz....................................................................................................................................5
4.1 Startup...................................................................................................................................6
4.2 Scannen und Laden der Daten ..............................................................................................6
4.3 Komprimierung.....................................................................................................................7
4.4 Ausführungsstrategie.............................................................................................................8
4.5 Mergen der Ergebnisse .........................................................................................................8
4.6 Performancemessungen typischer Tasks...............................................................................8
4.6.1 Original MapReduce Grep Task....................................................................................8
4.6.2 Web Log Task................................................................................................................9
4.6.3 Join Task........................................................................................................................9
4.7 Fazit.....................................................................................................................................10
5 Zielgruppen und -anwendungen.................................................................................................10
5.1 MapReduce ........................................................................................................................10
5.2 Parallele Datenbanken ........................................................................................................11
6 Hybride Systeme.........................................................................................................................12
7 Quellen .......................................................................................................................................13
Abbildungsverzeichnis
Abbildung 1: Performanztest für den originalen Grep Task............................................................9
Abbildung 2: Performanztest für den Web Log Task.......................................................................9
Abbildung 3: Performanztest für den Join Task............................................................................10
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 1
1 Gemeinsamkeiten der beiden Ansätze
Beide Systeme, MapReduce sowie Parallele DBMS sind in der Lage, eine sehr große
Datenmenge zu managen und auf diesen Operationen abzuwickeln. Beide bieten die
Möglichkeit, die Daten sowie Zugriffe und Analyse auf den Daten auf unterschiedlichen
Rechnern zu parallelisieren. Ein großer Vorteil ist dabei, dass die Systeme von den Einzelheiten
der Parallelisierung abstrahieren. Sie kümmern sich um das Managen der Daten und den
Programmausführungen auf mehreren Knoten und kapseln die Kommunikation zwischen den
Maschinen. [DG10, S. 72]
Fast jede parallele Bearbeitungsaufgabe kann entweder mit einem Set an Datenbankabfragen
oder MapReduce Jobs implementiert werden. [PPR+09 und SAD+10, S. 64]
2 Architekturunterschiede
2.1 Verteilung & Scheduling
Typisch für parallele DBMS ist die horizontale Partitionierung der relationalen Tabellen sowie
die partitionierte Ausführung der SQL-Statements. Die Idee hinter horizontaler Partitionierung
ist, die Reihen von relationalen Tabellen auf die Knoten eines Clusters zu verteilen, so dass sie
parallel bearbeitet werden können. [SAD+10]. Auch die SQL-Statements werden so verändert
und unterteilt, dass sie auf verschiedenen Systemen ausgeführt werden. Das bedeutet, dass das
DBMS zu Beginn den verteilten Abfrageplan einmal erstellt und darauf Optimierungen ausführt.
Dieser Plan wird draufhin an die beteiligten Knoten weitergegeben. [SAD+10, S. 70]
Bei MapReduce liegen die Daten ebenfalls verteilt auf den Systemen, die Aufteilung erfolgt
ebenfalls horizontal. Allerdings wird das Scheduling nicht einmal zentral festgelegt und an die
Knoten weitergegeben wie bei den DBMS, sondern das Scheduling wird pro Speicherblock
getriggert. Das Laufzeit-Scheduling von MapReduce ist damit wesentlich teurer als die initiale
Scheduling-Festlegung von DBMS Systemen, hat aber den Vorteil, dass der MapReduce
Scheduler flexibel auf Performanzunterschiede zwischen den Knoten reagieren und entsprechend
die Knoten unterschiedlich auslasten kann. [SAD+10, S. 70]
In beiden Systemen werden die verteilt bearbeiteten Daten beispielsweise über Hash-Funktionen
wieder zusammengeführt.
2.2 Datenformat
Ein DBMS gibt ein klares Datenformat vor. Das zu verwendende Schema für die Daten wird
initial angelegt und daraufhin meist unverändert benutzt. Dies ermöglicht es einem DBMS
Optimierungen auf den Daten durchzuführen wie zum Beispiel Anlegen von Indizes. [PPR+09,
S. 167]. Es gibt jedoch noch eine weitere Implikation aufgrund eines klar definierten Schemas:
Die Daten in einem DBMS werden mit Hilfe des vorliegenden Schemas beim Laden des
Systems geparst. Da in MapReduce-Systemen beim Startup das Schema der Daten unbekannt ist,
werden erst mit Verwendung der speziell implementierten und explizit konfigurierten Readern
die Daten zur Laufzeit eingelesen. [PPR+09, S, 178] In MapReduce können Datenquellen
beliebigen Formats eingebunden werden, man ist somit unabhängig von einem bestimmten
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 2
Datenschema.
2.3 Programmiermodell
Das Programmiermodell von DBMS unterscheidet sich von dem Programmiermodell von
MapReduce. Datenbanksysteme bieten eine Abfragesprache, nämlich SQL, um die Verwaltung
der Datenmengen zu vereinfachen. SQL ist deklarativ. Es wird damit beschrieben, WAS gewollt
wird, nicht WIE. Andersrum verhält es sich beim prozeduralen Programmiermodell von
MapReduce. Dort wird ein Algorithmus implementiert, mit dem die Daten erfragt oder bearbeitet
werden.
2.4 Netzwerklast
Für die Bearbeitung großer verteilter Datenmengen ist es von Vorteil, wenn die Bearbeitung
weitestgehend lokal auf den Maschinen der Originaldaten stattfindet und dann die Ergebnisse
über das Netz geschickt werden. Dadurch können die Datenmengen zunächst gefiltert werden,
bevor sie an zentraler Stelle weiter verarbeitet werden und die meist größere Menge an
Originaldaten muss nicht über das Netzwerk geschickt werden.
Parallele DBMS nutzen die Kenntnis der Datenverteilung zu ihrem Vorteil. Die Query Planner in
parallelen DBMS übertragen Daten zwischen den verschiedenen Knoten nur, wenn es unbedingt
notwendig ist. So können Abfragen dahingehend optimiert werden, dass möglichst wenig über
das Netzwerk gesendet werden muss. Das Optimieren der Anfragen geschieht für den User
transparent, es findet automatisch im System statt. [PPR+09, S. 167]
Bei MapReduce wird die Entscheidung, wo welche Map-Jobs laufen auch vom System getroffen.
Allerdings findet darüber hinaus keine automatische Optimierung der Netzwerklast statt.
[PPR+09, S. 167] Die Map-Jobs sollten allerdings die Daten schon weitestgehend reduzieren, so
dass die Netzwerklast minimiert wird. Dies liegt jedoch in der Verantwortung des
Programmierers.
2.5 Ausführungsstrategie
Das parallele DBMS versucht die gestellten Anfragen zu optimieren dahingegen, dass möglichst
wenig Daten übertragen werden müssen. Und wenn Daten in einem DBMS ausgetauscht werden,
so geschieht dies über den Push-Mechanismus. Das bedeutet, die Daten werden vom
Produzenten zum Verbraucher gestreamt, ohne, dass sie zwischendurch in eine Datei geschrieben
werden. Diese Ausführungsstrategie unterscheidet sich von der Ausführungsstrategie von
MapReduce. In MapReduce schreibt der Producer die Ergebnisse in eine lokale Datenstruktur
und der Verbraucher „pullt“ die Daten. [SAD+10, S. 70]
3 Vor- und Nachteile
3.1 Datenformat
MapReduce ist unabhängig von der Art, wie Daten gespeichert sind und neue Datenquellen
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 3
können einfach eingebunden werden. Soll eine neue Datenquelle verwendet werden, müssen
Entwickler lediglich einen Reader bzw. einen Writer implementieren, der mit der Datenquelle
umgehen kann. [DG10, S. 74] Damit eignet sich MapReduce sehr gut für die Bearbeitung oder
Analyse von Daten in heterogenen Systemen mit unterschiedlichen Speichersystemen.
Diese Flexibilität wird jedoch durch eine Reihe von Nachteilen erkauft. Zunächst kann das
Schreiben der Reader, bzw. Writer als Nachteil empfunden werden. Muss für die meisten
Datenquellen eine eigene Implementierung geschrieben werden, wird das Schema der Daten mit
der Applikation vermischt. Bei DBMS liegt eine Trennung des Schemas und der Applikation vor,
denn das Schema wird in einem eigenen Systemkatalog abgelegt, der von der Applikation erfragt
werden kann. [PPR +09] Außerdem befreit die Freiheit bezüglich des Datenformats nicht von der
Anforderung, dass die Ein- und Ausgangsformate dennoch klar definiert und den Entwicklern
bewusst sein müssen. Es bestehen meist klare Regeln oder Constraints für die Daten, die
dennoch eingehalten werden müssen, auch wenn die Manipulation frei möglich ist.
Damit sind wir bei einem weiterern schwerwiegendem Nachteil der Formatunabhängigkeit von
MapReduce angelangt: Die fehlende Garantie für Integrität. DMBS Systeme können garantieren,
dass für definierte Constraints Datenintegrität besteht, bei Textfiles oder anderen Datenquellen
kann diese Integrität nicht garantiert werden. Diese Datenquellen können bei falscher
Manipulation leicht korrupt oder fehlerhaft werden. Dies ist ein Grund, warum MapReduce
problematisch für größere Projekte werden kann, da durch die hohe Anzahl an Entwicklern und
häufige Wechsel der Programmierer der Wunsch nach garantierter Datenintegrität höher ist.
3.2 Flexibilität
MapReduce bietet nicht nur bezüglich des Datenformats Flexibilität, sondern auch in der
Ausdrucksstärke. Während man Abfragen für parallele DBMS in SQL definiert, liegen sie für
MapReduce Systeme in einer objektorientierten oder imperativen Programmiersprache vor. In
dem bekanntesten MapReduce System Hadoop werden die Anfragen in Java geschrieben. Das in
DBMS genutzt SQL, eine deklarative Sprache, bietet nicht die Flexibilität, die mit einer
prozeduralen Sprache einhergeht.
Viele DBMS bieten heutzutage Unterstützung für sog. User-defined Functions in SQL. Sie bieten
zwar nicht die Möglichkeiten, die in MapReduce geboten werden, verbessern allerdings die
Flexibilität von Datenbanksystemen. [PPR+09, S. 168] Problematisch wird es allerdings, wenn
die Komplexität der Funktionen steigt.
3.3 Komplexe Funktionen
Oft sind Funktionen gerade im Map-Teil sehr komplex, so dass sie nur schwer mit SQL
ausgedrückt werden können. Ein Beispiel ist die Aufgabe, Links aus HTML Dokumenten zu
extrahieren und diese nach dem Wert des Target-Attributs zu aggregieren. [DG 10, S. 74]
Außerdem ist jede Art von User Defined Function, also einer selbst geschriebenen Operation, die
man auf den Daten ausführen möchte und deren Code eventuell schon in irgendeiner Sprache
vorliegt, leichter über MapReduce einzubinden als in SQL. [DG10, S.74]
3.4 Fehlertoleranz
Beide Systeme arbeiten mit Replikation um gegen Datenverlust bei Festplattenproblemen
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 4
gerüstet zu sein. Es können jedoch auch Fehler während der Bearbeitung eines Tasks bzw. einer
Anfrage auftreten, insbesondere Hardwarefehler. Dadurch, dass über die Zeit die Datenmenge
wächst und die Systeme auf größeren Clustern deployed werden, wächst die Wahrscheinlichkeit
von (Hardware-)Fehlern während einer Anfrage.
MapReduce kann mit fehlgeschlagenen Anfragen / Tasks weitaus besser umgehen als DBMSs.
Wenn eine Arbeitseinheit in MapReduce fehlschlägt, so kann der MR Scheduler automatisch
diesen Task auf einer anderen Einheit neu starten. Der Umfang der Arbeit, die verloren ist und
wiederholt werden muss ist minimal gegenüber eines DBMS.[PPR+09, S. 177] Diese
Fehlertoleranz ist dadurch gegeben, dass MapReduce die Ergebnisse der Map-Phase auf dem
Dateisystem persistiert und nicht an die Einheit, die für die Reduce Phase zuständig ist, streamt.
[PPR+09, S. 168] Somit muss bei einem Fehler während des Map Jobs nicht auch der ReduceJob neu gestartet werden. Diese Vorgehensweise bringt einen Performanzverlust mit sich.
Außerdem ist nicht völlig klar, wie signifikant der Vorteil der größeren Fehlertoleranz von
Hadoop in der Praxis ist.
Parallele Datenbanken haben durch das Streamen der Zwischenergebnisse größere
zusammenhängende Arbeitseinheiten, die im Fehlerfall im Gesamten neu gestartet werden
müssen. Es muss die ganze Transaktion erneut ausgeführt werden. Hier wird die Performance zu
Lasten der Fehlertoleranz erkauft.
3.5 Implementierungsaufwand
Sind die Daten im DBMS bereits vorhanden so müssen für die Analyse bzw. Bearbeitung
lediglich die SQL-Statements implementiert werden. Einen MapReduce-Job zu schreiben, der
die gleiche Aufgabe erfüllt produziert mehr Code und kann, je nach Komplexität der Aufgabe,
aufwendiger sein.
3.6 Indizes
Indizes von DBMS können den Datenzugriff stark beschleunigen. Sucht man ein Subset von
Daten, beispielsweise alle Mitarbeiter mit einem Gehalt > 50.000€, so kann dies mit Hilfe eines
Indexes optimiert werden. MapReduce stellt solch einen Mechanismus zur Indizierung nicht zur
Verfügung. Es kann jedoch ein DBMS problemlos als Datenquelle für ein MapReduce-System
dienen und damit auch die Möglichkeit der Indizierung genutzt werden.
3.7 Projektgröße
Das MapReduce Entwicklungsmodell ist effektiv bei einer kleinen Anzahl von Entwicklern und
einer begrenzten Applikationsdomäne. Es ist jedoch problematisch bei längerfristigen und
größeren Projekten. [PPR+09] Wie bereits in Kapitel 2.2 Datenformat ausgführt, ist durch die
Flexibilität beim Datenformat die Datenintegrität gefährdet. Wachsen Projekte, so arbeiten mehr
und öfter unterschiedliche Entwickler an dem System, die alle über die impliziten Constraints
der Daten Bescheid wissen müssen. Ist die Datenintegrität per se gegeben, wie beim DBMS
möglich, so bietet das einen klaren Vorteil für größere Projekte. Andersrum ist man mit dem
MapReduce Paradigma recht flexibel in kleineren Projekten.
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 5
3.8 Benutzbarkeit & Wartbarkeit
Die Verfasser des Artikels [PPR+09] empfanden den Aufwand, ein MapReduce System
aufzusetzen, zu konfigurieren und zum Laufen zu bringen, geringer gegenüber dem Aufsetzen
eines DMBS Systems. Die Erfahrung bezieht sich auf das MapReduce System Hadoop. Es war
hier nicht nötig, ein Schema zu konstruieren oder benutzerdefinierte Funktionen zu registrieren
um mit der Datenbearbeitung zu beginnen. [PPR+09, S. 177] Das Aufsetzen eines DBMS
Systems hingegen war mit größerem Aufwand verbunden und um eine Konfiguration zu
erstellen, die eine performante Benutzung erlaubte, war Support vom Hersteller nötig. [PPR+09,
S. 177]
In beiden Systemen ist es möglich, die syntaktische Korrektheit der Syntax für Abfragen on the
fly zu prüfen. Für Hadoop gibt es die Java Standard-Entwicklungsumgebungen, die vielen
Programmierern gut vertraut sind. Für SQL-Statements prüfen die DBMS, ob die Anfragen
korrekt geparst werden können.
Die parallelen DBMS bieten den Vorteil, dass die SQL-Statements von einem System ins andere
übertragen werden können. SQL ist damit besser portierbar und unabhängig von der
Systemauswahl. Anfragen und Bearbeitungen, die für Hadoop in Java geschrieben wurden,
können nicht ohne größere Änderungen in ein anderes MapReduce-System übertragen werden.
[PPR+09, S. 177]
Bezüglich Wartbarkeit können jedoch die DBMS wieder punkten. Die Verfasser von [PPR+09]
erweiterten ein Datenschema um weitere Spalten einiger Datensätze. Dazu war es im
MapReduce System notwendig, den MapReduce Code zu überprüfen und und festzustellen, ob
die Annahmen, die beim Aufsetzen des Codes gemacht wurden, auch nach der Schemaänderung
noch gültig sind. Dazu ist gegebenenfalls ein Refactoring des betreffenden Codes notwendig.
[PPR+09, S. 177] Bei DBMS-Systemen erfordert eine Erweiterung des Schemas keine
Überprüfung der SQL-Statements.
Hinzu kommt, dass beim Upgrade auf eine neue Hadoop-Version die Erfahrung gemacht wurde,
dass die API in der neuen Version einige benutzte Funktionalitäten als deprecated markiert hat,
was ein weiteres Refactoring erfordert hat. [PPR+09, S. 177]
Zusammenfassend lässt sich sagen, dass das Aufsetzen eines MapReduce-Systems im Vergleich
zu DBMS relativ einfach, jedoch die Wartbarkeit eines DBMS Systems besser als die eines
MapReduce-Systems ist.
3.9 Systemkosten
Ein Vorteil von MapReduce ist, dass es Open Source Implementierungen, wie beispielsweise
Hadoop, dafür gibt. Parallele DBMS-Systeme sind teuer und es existieren keine stabilen OpenSource-Implementierungen.
4 Performanz
Bei der Implementierung von MapReduce muss genauso wie beim Aufsetzen einer Datenbank
auf gewisse Einstellungen geachtet werden, um Performanz zu erzielen. Gute Performanz ist also
auch immer abhängig von einer guten Systemkonfiguration.
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 6
Beide Systeme haben Performanzschwachstellen, die je nach Anwendung mehr oder weniger ins
Gewicht fallen:
4.1 Startup
Ein gewisser Overhead besteht während des Startups von MapReduce. Das Starten der Jobs
benötigt Zeit und wirkt sich negativ auf die Performance aus, gerade wenn relativ wenige Daten
bearbeitet werden. Je größer die zu bearbeitende Datenmenge ist, desto weniger fällt eine hohe
Startup-Zeit ins Gewicht. Die Startup-Zeit steigt, je mehr Knoten in dem MapReduce
Framework genutzt werden. In [PPR+09, S. 176] wurde die Startup-Zeit von Hadoop in Version
0.19.0 auf einem Cluster mit 100 Knoten gemessen. Jeder Knoten besaß einen 2.4 Ghz Intel Core
2 Duo Prozessor, auf dem ein 64-Bit Linuxsystem lief. RAM war jeweils 4 GB vorhanden. Die
Testergebnisse zeigten, dass es 10 Sekunden benötigt hat, bis der Job zu dem Job Tracker
übermittelt wurde und der erste Map Task gestartet ist. Und insgesamt hat es 25 Sekunden
gedauert, bis alle Knoten in dem Cluster ihre Jobs ausführten.
Doch Google ist diesen Performance Issue bereits begegnet. Um den Startup Overhead zu
minimieren werden Arbeiterprozesse am Leben gehalten, die auf den nächsten MapReduceAufruf warten.
Einmal gestartet läuft ein DBMS als Service im Hintergrund und kann kurzfristig auf Anfragen
reagieren.
4.2 Scannen und Laden der Daten
Möchte man Daten mittels eines DBMS analysieren, so müssen diese zunächst geladen werden.
Dieses Laden in das DBMS ist teuer. Es konnte gezeigt werden, dass die Zeit, die benötigt wird,
um Daten in ein DBMS zu laden zu der Zeit, in der die Daten über einen MapReduce Job
gelesen und analysiert werden, im Verhältnis 10:1 steht. Hier kommt es jedoch darauf an, ob bei
der Art der Analyse lange Ladezeiten überhaupt ins Gewicht fallen. Werden die Daten nur einmal
geladen und dann sehr viele Queries darauf abgesetzt, spielt die Ladezeit weniger eine Rolle. Es
gibt jedoch Anwendungen, in denen die Daten jedoch nur ein- bis zweimal analysiert werden,
bevor sie wieder verworfen werden. In diesen Fällen spielt die Ladezeit eine große Rolle. [DG
10, S. 77]
Die Ursache für die lange Ladezeit eines DBMS ist die Tatsache, dass die Daten organisiert
werden, wenn sie geladen werden. So wird jedes Attribut in einer Tabelle separat gespeichert.
Dies erlaubt Optimierungen bei den Anfragen: Werden lesende Queries ausgeführt, die nur ein
Subset der Attribute einer Tabelle betreffen, so werden die anderen nicht angefragten Attribute
erst gar nicht ausgelesen und übertragen. Dies verhindert unnötigen Aufwand und I/O
Bandbreite. [PPR+09, S. 176]
MapReduce Systeme transformieren standardmäßig nicht ihre Inputdaten, wenn diese in das
verteilte Datensystem geladen werden. Damit wird auch nicht das Layout der Daten beim
Ladevorgang geändert, was eine Optimierung wie bei DBMS-Systemen nicht ermöglicht.
[PPR+09, S. 176]
Es scheint, dass MapReduce stets das ganze Set an Inputdaten bei Anfragen scannen muss und
nicht wie ein DBMS nur die Attribute lädt, die es braucht. Allerdings ist dem entgegenzuhalten,
dass lediglich das Input-Interface dermaßen implementiert sein muss, dass es nach den
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 7
relevanten Daten filtert. Somit muss auch der MapReduce Job nicht über alle Inputdaten laufen.
[DG 10, 76]
In MapReduce kann außerdem beim Schreiben der Reader der Vorteil von natürlichen Indizes
ausgenutzt werden. Diese sind beispielsweise in Zeitstempeln von Log-Files zu finden. [DG 10,
S. 77] Dies bedeutet, dass die Implementierung der Reader schon stark auf die Art der Anfragen
ausgereichtet ist.
Es gibt jedoch die Möglichkeit, die Performanz von DBMS-Systemen in MapReduce-Systemen
zu nutzen, indem eine entsprechende Datenabankabfrage als Input für MapReduce dient. Hier
können dann Abfragen verwendet werden, die Indizes benutzen, die das effiziente Filtern einer
indizierten Datenstruktur erlauben.[DG 10, S. 76]
Prinzipiell hat man bei MapReduce den Umstand, dass die Inputdaten zur Laufzeit gelesen, bzw.
deserialisiert werden. Parallele DBMS parsen ihre Eingabedaten bereits zur Ladezeit und können
zur Laufzeit sehr schnell auf die Daten zugreifen. Dieser Performanznachteil von MapReduce
zur Laufzeit kann jedoch verringert werden, indem ein effizientes binäres Format für
strukturierte Daten verwendet wird. Das Google Protocol Buffer ist beispielsweise ein solches
Format. Einfache Textformate sind eher ineffizent und sollten vermieden werden.[DG 10, S. 76,
77]
Es gab bei Hadoop bereits Bemühungen, dem Performanceproblem beim Parsen der Daten zu
begegnen. So erlaubt Hadoop Key/Value Paare als serialisierte Tupel zu speichern. Diese werden
SequenceFiles genannt. Es muss jedoch weiterhin der Value-Teil geparst werden, wenn dort
mehrere Attribute vorhanden sind. Nach [SAD+10, S. 69] bringt der Einsatz von SequenceFiles
sogar einen Performanzverlust gegenüber dem einfachen Textformat.
Wenn man prinzipiell die Zeit vergleicht, die benötigt wird um Daten zu laden, so geschieht dies
in Hadoop wesentlich schneller als in einem parallelen DBMS. In [PPR+09, S. 176] konnte
gezeigt werden, dass in Hadoop Daten bis zu dreimal schneller geladen werden können als in
dem parallelen DBMS Vertica. Gegenüber dem parallelen DBMS DBMS-X war es sogar 20 mal
schneller. Hieraus kann geschlossen werden, dass wenn die Daten für die Analyse nur einmalig
geladen werden müssen, es sich kaum lohnt, diese Daten in ein DBMS einzuspielen, zu
indizieren und zu reorganisieren. In dem Fall ist es meist effizienter, wenn die Daten über
MapReduce bearbeitet werden.
4.3 Komprimierung
Beide Systeme bieten Datenkomprimierung. Datenkomprimierung hat den Vorteil, dass es das
Datenvolumen der Daten verringert, die über das Netzwerk geschickt werden müssen. Allerdings
zeigt sich bei der Performanzmessung, dass das DBMS gegenüber Hadoop stärker von dem
Einsatz der Komprimierung profitiert. Das Einschalten der Komprimierung in Vertica und
DBMS-X hat die Performanz um den Faktor zwei bis vier verbessert [SAD+10, S. 69]. In
Hadoop hingegen konnte man maximal 15% bei optimaler getesteter Konfiguration feststellen
[SAD+10, S. 69]. Es wird vermutet, dass der Performanzunterschied darauf zurückzuführen ist,
dass die DBMS-Hersteller großen Wert auf das Einstellen der korrekten
Komprimierungsparameter und -algorithmen legen, damit der Benefit durch die geringeren I/OKosten nicht durch eine schlecht konfigurierte Komprimierung aufgehoben wird. [SAD+10, S.
70] Es kann jedoch davon ausgegangen werden, dass Hadoop die Effizienz der Komprimierung
in einer seiner zukünftigen Releases verbessert.
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 8
4.4 Ausführungsstrategie
Der Datentransfer zwischen den Map und den Reduce Jobs bringt einigen Overhead mit sich.
Wie bereits in 2.5. Ausführungsstrategie ausgeführt, „pullt“ der Reduce Job für seine Inputdaten.
Wenn man es mit vielen Jobs zu tun hat, kann es passieren, dass zwei Jobs die gleiche Datei
anfragen. Durch den Pull-Mechanismus in MapReduce müssen außerdem eine Reihe von
Kontrollnachrichten versendet werden, um die Prozesse zu synchronisieren. Dies wird in
parallelen DBMS anders gehandhabt. Dort wird zu Beginn einer Anfrage der komplette
Anfrageplan zu allen bearbeitenden Knoten gestreamt und damit sind keine
Synchronisationsnachrichten erforderlich. Insgesamt ist diese Ausführungsstragegie von
MapReduce mit sehr viel I/O-Aufwand verbunden und ein potentieller Performanz-Bottleneck.
[PPR+09, S. 168]
Es gibt jedoch einen guten Grund für diese Strategie in MapReduce: Die damit einhergehende
Fehlertoleranz. Dadurch, dass die Zwischenergebnisse in Dateien abgelegt werden, müssen im
Fehlerfall nicht alle Map-Jobs neu gestartet werden.
4.5 Mergen der Ergebnisse
Es läßt vermuten, dass das Mergen der Ergebnisse der MapReduce-Jobs in eine Datei ein
Performanzproblem darstellt. Dazu ist allerdings zu erwähnen, dass häufig der Output eines
MapReduce-Jobs der Input eines anderen ist und somit das Mergen in eine einzige Ergebnisdatei
nur am Ende der Kette stattfinden muss. Es ist auch vorstellbar, dass MapReduce die Ergebnisse
direkt in ein System schreibt, das die Daten selbst mergt, wie beispielsweise eine parallele
Datenbank.
4.6 Performancemessungen typischer Tasks
In [PPR+09] wurde Performanz bei einigen typischen Anwendungsfällen gemessen:
4.6.1 Original MapReduce Grep Task
Ein Performanzexperiment in [PPR+09] ist der „Grep Task“ aus dem original MapReduce Paper,
welcher dort beschrieben wurde als „repräsentativ für ein großes Subset von realen Programmen,
die mit MapReduce realisiert sind“.
Für diesen Task müssen die Systeme durch 100B große Datensets scannen und nach 3
aufeinanderfolgenden Zeichen suchen. Dabei bestehen die ersten 10B aus einem Key und die
folgenden 90B enthalten potentiell die gesuchten Zeichen. Insgesamt wurde in 1TB Daten
gesucht, die auf 100 Knoten verteilt wurden (10 GB / Knoten). Für den Task müssen die Systeme
durch alle Datensätze gehen und die Datenbanksysteme können keine Vorteile aus Indizierung
und Sortierung ziehen. Es zeigt somit, wie schnell ein System durch eine große Datenmenge
scannen kann. [SAD+10, S. 67/68] Die Untersuchungen in [PPR+09] erbrachten folgende
Ergebnisse:
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 9
Abbildung 1: Performanztest für den originalen Grep Task
Wie aus Abbildung 1: Performanztest für den originalen Grep Task ersichtlich, sind die
Datenbanksysteme Vertica und DBMS-X ungefähr zweimal schneller als Hadoop für den
gegebenen Task.[SAD+10, S. 68/69]
4.6.2 Web Log Task
Die Aufgabe besteht aus einer gewöhnlichen SQL Aggregation mit einer GROUP BY Klausel.
Diese Aggregation wird angewendet auf ein Webserver Log in Form einer Tabelle, die
Benutzerbesuche beinhaltet. Jedes System muss die gesamten Werbeeinkommen für jede
besuchte IP berechnen. Es handelt sich hier um eine typische Trafficanalyse. Für diesen Versuch
wurden 2TB Daten verwendet, die auf 100 Knoten verteilt wurden (20GB / Knoten). Wie auch
schon im vorherigen Task müssen die Systeme durch alle Datensätze gehen und die DMBS
haben keine Möglichkeit, Vorteile aus der Indizierung zu ziehen. [SAD+10, S. 69].
Die Untersuchungen in [SAD+10] brachten folgende Ergebnisse:
Abbildung 2: Performanztest für den Web Log Task
Auch hier schneiden die DBMS deutlich besser ab als Hadoop. Hier sei zu erwähnen, dass
Vertica eine spaltenbasierte Datenbank ist und das System in diesem Fall nur die Attribute lesen
muss, die für die Abfrage relevant sind. Aus diesem Grund schneidet Vertica besser ab als
reihenbasierte Speichersysteme wie DBMS-X und Hadoop / HDFS.
4.6.3 Join Task
Hierbei handelt es sich um eine Join-Operation über zwei Tabellen, die zusätzlich Aggregation
und Filterung ausführt. Das Datenset der Benutzerbesuche aus dem vorherigen Beispiel wird
gejoint mit einer zusätzlichen Tabelle von 100GB Größe, die die Page Rank Werte für 18
Millionen URLs enthält. Der Join-Task besteht aus zwei Untertasks, die Kalkulationen auf den
beiden Datensets durchführen. In dem ersten Teil des Tasks muss jedes System die IP-Adresse
finden, die den größten Ertrag innerhalb eines bestimmten Datumsbereiches in den User Visits
hat. Wenn diese Ergebnisse feststehen, muss das System den durchschnittlichen Page Rank aller
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 10
besuchten Seiten kalkulieren. [SAD+10, S. 69]
Folgende Ergebnisse wurden gemessen:
Abbildung 3: Performanztest für den Join Task
Die DBMSs waren um einen Faktor von 36 und 21 schneller als Hadoop. DBMS scheinen
besonders geeignet für analytische Abfragen zu sein, die komplexe JOIN-Operationen
beinhalten.
4.7 Fazit
Zunächst ist zu erwähnen, dass die großen Performanzunterschiede, die zwischen den Systemen
festgestellt wurden, das Ergebnis unterschiedlicher Implementierungsentscheidungen sind. Sie
sind nicht auf grundlegende Prinzipien beider Modelle zurückzuführen. Beispielsweise ist ein
MapReduce Task unabhängig vom zu Grunde liegenden Speichermodell und könnte auch wie
DBMS eines verwenden, das Indizierung und Kompression nutzt. [SAD+10, S. 69] Die Basis für
das Setup der Systeme hat sich an realen Anwendungsfällen orientiert [SAD+10, S. 69]
Die Tests in [PPR+09] zeigen, dass die Analysen und Abfragen des DBMS signifikant schneller
als die des MapReduce-Systems sind. Das Laden der Daten dauert jedoch länger als in
MapReduce Systemen. Aus diesem Grund ist es abhängig von der Art der Anwendung, welches
System zu bevorzugen ist.
5 Zielgruppen und -anwendungen
Obwohl fast jede Analyse oder Bearbeitung, die mit dem einen System ausgeführt auch mit dem
anderen umgesetzt werden kann, gibt es typische Anwendungsfälle für die jeweiligen Systeme.
Aufgrund der unterschiedlichen Struktur und Arbeitsweise der beiden Systeme nutzen
unterschiedliche Anwendungsgebiete jeweils die Vorteile eines Systems aus. Aus diesem Grund
stellt die MapReduce-Technologie weniger eine Konkurrenz für die DBMS dar, eher eine
Vervollständigung.
5.1 MapReduce
MapReduce eignet sich zum einen für Extract-Transform-Load (ETL) Systeme. In ExtractTransorm-Load-Systemen werden Daten schnell geladen, bearbeitet und verworfen. In diesen
Systemen werden Daten nur einmalig geladen und analysiert. Wie bereits in 4.2 Scannen und
Laden der Daten ausgeführt, ist das Laden der Daten in parallelen DBMS relativ teuer im
Vergleich zum MapReduce-System Hadoop. In Hadoop können Daten vergleichsweise schnell
geladen werden. Wird also nur einmalig auf die Daten zugegriffen, ist es effizienter, wenn die
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 11
Daten von einem MapReduce System geladen und analysiert werden. Die längere Ladezeit eines
DBMS verliert an Gewicht, wenn nach dem Laden der Daten mehrere Abfragen auf dem System
gemacht werden, denn das DBMS kann im Vergleich zu einem MapReduce-System eine relativ
schnelle Abfragezeit aufweisen [PPR+09, S. 170]
Typische Beispiele für den Einsatz von MapReduce sind das Lesen von Loginformationen aus
vielen verschiedenen Quellen oder das Parsen und Bereinigen von Logdaten. Diese Beispiele
haben gemeinsam, dass das System seine Daten einmalig verarbeitet und daraufhin meist
weitergibt an ein anderes Speichersystem. Ein MapReduce System kann also als einen
allgemeinen Typ oder Framework eines parallelen ETL Systems betrachtet werden. [SAD+10, S.
67]
Ein weiteres Beispiel für eine typische Anwendung von MapReduce ist ein zentrales Datenlager /
Data Warehousing. Eine Zentrale Datensammlung setzt sich aus verschiedenen Datenquellen
zusammen. Da sich MapReduce besonders beim heterogenen Systemen eignet, kann MapReduce
dazu genutzt werden, um ein solches Datenlager zu befüllen. [PetersonVuE2011]
MapReduce ist außerdem geeignet für komplexere Analysen auf den Daten, da in MapReduce die
Ausdrucksstärke von Programmiersprachen genutzt werden kann. Solche Aufgaben können
häufig nicht als einziges SQL-Statement ausgedrückt werden. Stattdessen nutzt man ein
komplexeres Programm, das die Bearbeitung beschreibt. Außerdem finden sich hier häufig
„Datenflußmaschinen“, bei denen der Output einer Programmeinheit als Input einer anderen
Programmeinheit dient. MapReduce ist sehr geeignet für diese Art von Programmen [SAD+10,
S. 67]. Komplexere Datenanalyse findet in verschiedenen Anwendungsformen statt. Beispiele
sind die Clickstream-Analyse, um Möglichkeiten zu finden, Webseiten zu optimieren, im
Bereich Machine Learning sowie der Graph-Analyse (Den kürzesten Weg von einem Knoten im
Graph zu allen anderen bestimmen).
Mit MapReduce Systemen können außerdem semistrukturierte Daten einfach gespeichert und
bearbeitet werden. [SAD+10, S. 67] Solche Daten liegen oft in Form von Key-Value-Paaren vor,
in denen die Anzahl der Attribute variiert. Es ist allerdings auch möglich, diese Daten in einem
DBMS zu speichern und für die Attribute, die in den Daten nicht angegeben sind, NULL in den
entsprechenden Spalten einzutragen. Hier ist es wohl eher von der Art der Analyse / Bearbeitung
der Daten abhängig, welches System verwendet wird. Als Beispiel: Werden mehrere analytische
Abfragen auf den Daten vorgenommen, könnte sich der Aufwand lohnen, die Daten in ein
DBMS zu übertragen. Werden hingegen die Daten transformiert, um sie in einem anderen
System zu speichern, sollte MapReduce vorgezogen werden. Es ist dabei auch denkbar, einen
MapReduce Job zu verwenden, um die Daten in ein DBMS zu übertragen.
Ein entscheidender Vorteil von MapReduce Systemen ist das schnelle Setup. Um ein DBMS
aufzusetzen und so zu konfigurieren, dass die Abfragen effizient laufen, benötigt es mehr
Aufwand als bei einem MapReduce System. Außerdem muss in einem DBMS erst noch ein
Schema für die Daten angelegt und die Daten in das System geladen werden. Steht man also
unter Zeitdruck und möchte schnell eine Lösung auf die Beine stellen, bietet MapReduce
Vorteile [SAD+10, S. 68]
5.2 Parallele Datenbanken
Das Anwendungsgebiet für Parallele Datenbanken bzw SQL ist vielseitig. Überall dort, wo
strukturierte Daten vorhanden sind und eine starke Konsistenz notwendig ist, sind relationale
Datenbanken bzw. SQL eine geeignete Lösung. Insbesondere in Domänen, die hohe Priorität auf
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 12
Einhaltung der ACID-Kriterien legen sind parallele Datenbanksysteme zu bevorzugen,
beispielsweise im Finanz- und Bankensektor.
Außerdem haben DBMS einen Performanzvorteil bei Abfragen, wodurch es sich besonders für
Anwendungen eignet, in denen Daten einmal geladen und mehrmals bearbeitet bzw. analysiert
werden.
6 Hybride Systeme
Generell bieten sich MapReduce-Systeme an für ETL Aufgaben oder Anwendungen, die
komplexe Analysen erfordern. DBMS hingegen sind von Vorteil, wenn die Anwendung Queryintensiv ist. Es ist naheliegend, dass Lösungen entstehen, die zum einen eine Schnittstelle für
MapReduce-Systeme bieten um beispielsweise komplexe Analysen zu machen. Anderesrum
können Schnittstellen zu DBMS Systemen genutzt werden, um qeryintensive Analysen zu
machen. Bekannte Vertreter hierfür sind HadoopDB und Hive.
Kontroverse: Parallele DBMS vs. MapReduce
Alica Moser – Seite 13
7 Quellen
•
[DG10]
Jerey Dean and Sanjay Ghemawat. Mapreduce: a flexible data processing tool. Commun.
ACM, 53(1):72-77, 2010.
•
[PPR+09]
Andrew Pavlo, Erik Paulson, Alexander Rasin, Daniel J. Abadi, David J. DeWitt, Samuel
Madden, and Michael Stonebraker. A comparison of approaches to largescale data
analysis. In SIGMOD '09: Proceedings of the 35th SIGMOD international conference on
Management of data, pages 165{178, New York, NY, USA, 2009. ACM.
•
[SAD+10]
Michael Stonebraker, Daniel Abadi, David J. DeWitt, Sam Madden, Erik Paulson,
Andrew Pavlo, and Alexander Rasin. Mapreduce and parallel dbmss: friends or foes?
Commun. ACM, 53(1):64{71, January 2010
•
[PetersonVuE2011]
Nils Peterson, „Vergleich und Evaluation zwischen modernen und traditionellen
Datenbankkonzepten unter den Gesichtspunkten Skalierung, Abfragemöglichkeit und
Konsistenz“, Diplomica Verlag, 2011
http://books.google.de/books?id=6-M_eVneQxYC&pg=PA43&lpg=PA43
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
HadoopDB & SQL/MapReduce
MICHAEL KÜPPER
HadoopDB & SQL/MapReduce
Inhalt
1.
Einführung .......................................................................................................................... 3
2.
HadoopDB .......................................................................................................................... 3
2.1.
Einführung HadoopDB ................................................................................................ 3
2.2.
Historie ........................................................................................................................ 3
2.3.
Anwendungsfälle ......................................................................................................... 4
2.3.1.
Hadoop ................................................................................................................. 4
2.3.2.
MapReduce Verfahren ......................................................................................... 4
2.3.3.
Hadoop versus parallelen Datenbanken ............................................................... 5
2.4.
HadoopDB ................................................................................................................... 5
2.4.1.
3.
Anforderungen ..................................................................................................... 5
2.5.
Hybridsystem HadoopDB............................................................................................ 6
2.6.
Architektur ................................................................................................................... 6
2.6.1.
Database Connector .............................................................................................. 7
2.6.2.
Data Loader .......................................................................................................... 7
2.6.3.
Catalog ................................................................................................................. 7
2.6.4.
Query Interface ..................................................................................................... 7
2.7.
HadoopDB Query Execution ....................................................................................... 7
2.8.
Benchmark ................................................................................................................... 8
2.9.
Zusammenfassung ....................................................................................................... 9
SQL/MapReduce .............................................................................................................. 10
3.1.
Einführung ................................................................................................................. 10
3.2.
Ziel ............................................................................................................................. 10
3.3.
Anwendungsfälle ....................................................................................................... 11
3.4.
Architektur ................................................................................................................. 11
3.5.
Syntax ........................................................................................................................ 11
3.6.
Benutzerdefinierte Abfragen ..................................................................................... 12
3.7.
Implementierung einer benutzerdefinierten Abfrage ................................................ 13
3.8.
Zusammenfassung SQL/MapReduce ........................................................................ 14
4.
Abbildungsverzeichnis ..................................................................................................... 15
5.
Literaturverzeichnis .......................................................................................................... 15
2
1. Einführung
HadoopDB ist ein Forschungsprojekt der Yale Universität um Professor Abadi, die mit
Datenbanksystemen im Big Data Umfeld forschen. Ziel ist dabei, die Vorteile der MapReduce
Verfahren (implementiert in z. B. Hadoop) mit dem der parallelen Datenbanken zu
vereinigen, ohne die Nachteile zu übernehmen. Anwendungsfälle für Big Data Analyse sind
Logfile-Auswertungen in der Werbebranche und Datawarehouse Aufgaben.
SQL/MapReduce ist eine Erweiterung des nCluster Datenbanksystems und ermöglicht das
einfache Implementieren von benutzerdefinierten Funktionen im Big Data Umfeld. Damit ist
es möglich, Abfragen auf einem hoch parallelen Datenbanksystem mit Standard SQL
Abfragen auszuführen. Die Abfragen werden dazu nach dem MapReduce Verfahren
verarbeitet.
Dieser Artikel zeigt die grundsätzlichen Eigenschaften von HadoopDB anhand der Artikel [1]
und [2] auf. Dabei wird die Funktion und Architektur beleuchtet, sowie die Ergebnisse des
TPC-H Benchmarks vorgestellt. SQL/MapReduce wird auf Basis des Artikels [3] von Eric
Friedman et al. vorgestellt.
2. HadoopDB
2.1. Einführung HadoopDB
In den letzten Jahren sind die auszuwertenden Datenvolumen massiv gewachsen. Facebooks
Datenbank wächst täglich um mehr als 500 GByte und hat aktuell eine Hadoop Instanz mit
mehr als 100 Petabyte [3]. Mit relationalen Datenbanksystemen sind diese Datenmengen nicht
zu bewältigen. Selbst parallele Datenbanken sind nur für Netzwerke mit weniger als 100
Knoten konzipiert und in großen Rechnernetzen nur schlecht einsetzbar, da Abfragen bei
einem Fehler erneut ausgeführt werden müssen. Bei einem Rechnernetz von tausenden von
Knoten ist ein Fehler aber sehr wahrscheinlich. HadoopDB ist für diese Problematik ausgelegt
und bietet gleichzeitig eine SQL Abfragemöglichkeit.
2.2. Historie
Hadoop ist aus dem Projekt „Nutch“ von Doug Cutting hervorgegangen. Nutch wurde 2002
als OpenSource Projekt als Alternative zu den großen Suchmaschinen entwickelt. Als Cutting
2006 bei Yahoo anheuerte, konnte er sich voll auf die Entwicklung von Hadoop
konzentrieren. Der Name Hadoop wurde von dem gelben Stoffelefanten seines Sohnes
übernommen [4]. Hadoop ist ein OpenSource Framework, welches MapReduce –Algorithmen
auf verteilen Dateisystemen anwendet. Ziel ist eine hochverfügbare und skalierbare
Verarbeitung von großen Datenmengen in einem nicht homogenen Rechnerumfeld. Basis von
Hadoop ist das HDFS (Hadoop Distributed File System) und das MapReduce Framework.
HadoopDB wurde 2009 von Azza Abouzeid et al. [1] vorgestellt und ist angetreten, die
Nachteile von Hadoop (lange Initialisierungsphase, keine adhoc Abfragen) durch
Unterstützung von parallelen Datenbanksystemen zu verringern, ohne die Nachteile der
parallelen Datenbanken (keine Skalierung auf mehr als 100 Knoten) zu übernehmen. Mit
3
HadoopDB steht ein hybrides System, bestehend aus Hadoop und einem parallelem
Datenbanksystem, zur Verfügung. In der ersten Vorstellung wurde PostgreSQL als
Datenbanksystem verwendet. Um aber von den Vorteilen einer spaltenbasierten Datenbank zu
profitieren, wurde 2011 VectorWise/X100 als Datenbanksystem integriert. Für die Abfrage
wurde die Hadoop Erweiterung Hive erweitert.
Mittlerweile gibt es mit Hadapt (www.hadapt.com) eine kommerzielle Adaption von
HadoopDB.
2.3. Anwendungsfälle
Ein typischer Anwendungsfall für Big Data ist die Datenanalyse zur Unterstützung von
Datawarehousesystemen, die zum Beispiel bei Klick- und Warenkorbanalysen zum Tragen
kommt. In der Logfileanalyse zum Onlinemarketing können täglich >100 GByte
Verbindungsdaten anfallen, die importiert und täglich ausgewertet werden sollen. Bei großen
Datenmengen kann es schnell dazu kommen, dass der Analysezeitraum das Analyseintervall
übersteigt. In diesem Fall sind spezielle Verfahren notwendig, damit sich die Daten überhaupt
auswerten lassen. Relationale Datenbanksysteme sind dabei nicht optimal, da deren
Optimierung auf Einfügeoperationen und den damit verbundenen Transaktionen ausgelegt
sind. Diese Funktion wird hier aber gar nicht benötigt.
2.3.1. Hadoop
Hadoop und HDFS ist dafür ausgelegt, eine große Menge von Daten in seinem verteilten
Dateisystem effizient abzulegen und Analysen darauf auszuführen. Dabei werden die Daten in
Blöcke fester Größe geteilt und durch eine zentrale NameNode auf die einzelnen Knoten
verteilt. Dieser zentrale Knoten übernimmt auch die Verwaltung der WorkerNodes.
HDFS ist hochverfügbar. Die einzelnen Knoten werden permanent überprüft (Heartbeat) und
bei Fehlern oder Geschwindigkeitseinschränkungen werden die Daten auf einen anderen
Knoten verschoben.
Die Abfrage der Daten erfolgt per MapReduce Verfahren, indem auf den einzelnen Knoten
lokal der Map Task ausgeführt wird und im Reduce Task die Daten zusammengefasst werden.
2.3.2. MapReduce Verfahren
MapReduce wurde 2004 von Dean et al. vorgestellt [5]. Die Abfrage der Daten erfolgt in 3
Schritten.
1. Die Map Tasks werden auf den einzelnen Knoten lokal gestartet.
2. Die Zwischenergebnisse werden partitioniert
3. Der Reduce Task wird parallel auf die Ergebnisse ausgeführt.
Die Vorteile des MapReduce Verfahrens sind ein geringer Netzwerkverkehr und eine hohe
Parallelisierung durch die Verteilung der Rechenaufgaben auf einzelne Knoten.
Beispiel Wörter zählen: Wenn man die Wörter in einer großen Menge von Texten zählen
möchte, wird in dem Maptask die Funktion zum Zählen ausgeführt. Diese zählt die Wörter für
eine Partition von Daten auf einem Knoten. Die Berechnung wird auf den einzelnen Knoten
unabhängig voneinander lokal ausgeführt. Durch diese Lokalität wird der Netzwerkverkehr
4
verringert. Das Ergebnis wird dem ReduceTask übergeben, der nur noch die Aggregation
vornimmt.
2.3.3. Hadoop versus parallelen Datenbanken
Der Nachteil von Hadoop ist die lange Initialisierungsphase, in der die Daten auf die
einzelnen Knoten verschoben werden und die schlechte Performance bei Join Operationen, da
keine Indices verwendet werden.
Ein Vorteil ist die hohe Skalierbarkeit auch in heterogenen Netzwerken und die geringe
Netzwerklast durch den Einsatz des MapReduce Verfahrens. Auch die Fehlertoleranz gehört
zu den Stärken von Hadoop.
Der Nachteil von parallelen Datenbanken ist die relativ geringe Skalierbarkeit und die
schlechte Fehlertoleranz.
Vorteile sind die guten und ausgereiften Optimierungsverfahren und die einfache
Abfragemöglichkeit per SQL.
2.4. HadoopDB
2.4.1. Anforderungen
HadoopDB ist angetreten, um die wesentlichen Anforderungen im Big Data Bereich zu
erfüllen:
1. Geschwindigkeit
Natürlich ist Geschwindigkeit das wesentliche Kriterium in den Anforderungen an ein
Datenbanksystem. Bei extrem großen Datenmengen muss der Geschwindigkeit ein noch
höherer Stellenwert eingeräumt werden. Hohe Geschwindigkeit bedeutet direkt auch
Kostenersparnis, da die Hardwareanforderungen geringer sind.
2. Ausfallsicherheit / Fehlertoleranz
Wenn in einem System mehrere hundert oder auch tausende Rechner (oder virtuelle Rechner)
betrieben werden, steigt die Ausfallwahrscheinlichkeit des Gesamtsystems, wenn keine
Fehlertoleranz implementiert wird. Dabei ist mit Fehlertoleranz nicht der Datenverlust im
Transaktionsumfeld gemeint, sondern, da es sich bei Analysefunktionen nur um Lesezugriffe
handelt, um die erfolgreiche Abfrage trotz eines oder mehrerer Fehler bei der Ausführung.
Das kann dadurch erreicht werden, indem Aufgaben der fehlerhaften Knoten an neue Knoten
übertragen werden. Mit dem gleichen Verfahren können auch langsame Knoten deaktiviert
werden.
3. Lauffähigkeit in heterogenen Umgebungen
Bei der Verwendung von mehreren tausend Rechnern in einem Netzwerk wird es schwierig,
für diese auch die gleiche Hardware- und Softwarekonfiguration bereitzustellen. Dies gilt
auch für virtuelle Maschinen, da auch diese auf echter Hardware ausgeführt werden. Die
Knoten werden in einem großen Netzwerk also sehr wahrscheinlich nicht homogen sein.
Zudem können auch Teile der Hardware ausfallen, ohne dass der Knoten komplett ausfällt.
5
4. Flexible Abfragemöglichkeit
Die Verwendung eines Datenbanksystems ist abhängig von der Abfragemöglichkeit und
damit der Integrationsmöglichkeit in andere (Datawarehouse-) Systeme. Üblicherweise wird
JDBC / ODBC zur Verbindung mit Datenbanksystemen verwendet. Darüber werden SQL
Abfragen angenommen und ausgeführt. Idealerweise können benutzerdefinierte Abfragen
(User defined Functions) implementiert werden.
2.5. Hybridsystem HadoopDB
Das Hybridsystem HadoopDB versucht, die Vorteile aus Hadoop und die eines parallelen
Datenbanksystem zusammenzuführen. Parallele Datenbanken haben jahrzehntelange
Entwicklung hinter sich und sind optimiert für den Zugriff auf Daten durch Indizierung,
Kompression, Caching und materialisierte Views. Ein ausgeklügelter Optimierer versucht,
den besten Ausführungsplan zu ermitteln und erreicht damit eine hohe Performance
(Anforderung 1).
Alle relevanten parallelen Datenbanksysteme verfügen über eine JDBC Konnektor und sind
somit per SQL abfragbar (Anforderung 4).
Leider sind parallele Datenbanken nicht hochskalierbar, da sie nicht besonders fehlertolerant
bei einer großen Anzahl von Knoten sind. Ein einzelner ausgefallener Knoten lässt die
gesamte Abfrage scheitern und diese muss dann erneut komplett neu ausgeführt werden. Auch
die Ausführung in einer heterogenen Umgebung ist in der Regel nicht möglich, da diese
Systeme ein homogenes Netz erwarten. Parallele Datenbanksysteme versuchen, bei der
Abwägung zwischen Geschwindigkeit und Fehlertoleranz, der Geschwindigkeit den höheren
Stellenwert einzuräumen und nehmen damit bei einem Ausfall einen hohen Aufwand beim
Wiederanlauf in Kauf. Bei einer Anzahl von mehreren hundert Knoten ist der Ausfall eines
Knotens aber sehr wahrscheinlich. Google hat in einer Statistik eine Fehlerrate von 1,2
Knoten bei einer Verwendung von 157 Knoten (29.423 Jobs) festgestellt [5].
MapReduce hingegen hat seine Stärken in der Ausführung in einem großen Netzwerk von
heterogenen Knoten (Anforderung 2+3). Bei einem Fehler, oder einem langsam laufenden
Knoten, wird redundant der Task auf einem weiteren Knoten ausgeführt. Damit wird die
Gesamtlaufzeit der Abfrage an die des schnellsten Knotens angenähert. Das größte Problem
bei MapReduce ist die Initialisierungsgeschwindigkeit, da die Datenabfrage erst erstellt, die
Daten geladen und dann abgefragt werden können.
Um die Vorteile aus den parallelen Datenbanken mit denen von MapReduce zu verschmelzen,
werden einzelne Datenbanksystem auf eigenen Knoten mit dem Hadoop Task Koordinator
und dem Netzwerklayer verknüpft. Abfragen werden per MapReduce an die
Datenbanksysteme auf den Knoten verteilt. Dabei werden die Mechanismen zur Herstellung
der Fehlertoleranz aus dem Hadoop Framework und zur schnellen Datenabfrage die
Funktionen des Datenbanksystem auf den einzelnen Knoten verwendet.
2.6. Architektur
HadoopDB verwendet zur Abfrage die Hadoop Komponente Hive. Hive stellt eine SQL
ähnliche Sprache HiveQL zur Abfrage des Hadoop Datenbanksystems bereit.
6
HadoopDB erweitert das Hadoop System um die, in Abbildung 1: HadoopDB Architektur
blau gefüllten Komponenten:
2.6.1. Database Connector
Der Database Connector ermöglicht den Zugriff auf die einzelnen Datenbankinstanzen auf
den einzelnen Knoten über eine JDBC Schnittstelle.
2.6.2. Data Loader
Der Dataloader lädt die Daten, indem die Daten mittels Hash partitioniert und in kleinen
Datenblöcken auf die Knoten verteilt werden.
2.6.3. Catalog
Der Catalog beinhaltet die Metadaten, auf welchen Knoten sich die Datenblöcke befinden,
sowie die Datenstatistiken.
2.6.4. Query Interface
Das Query Interface stellt die Schnittstelle der Abfragen über SQL oder MapReduce zur
Verfügung.
Abbildung 1: HadoopDB Architektur
2.7. HadoopDB Query Execution
Bei der Ausführung von Abfragen werden diese auf die einzelnen Datenbankinstanzen in den
Knoten aufgeteilt. Dabei müssen die Abfragen unabhängig voneinander ausgeführt werden
7
können. Das ist normalerweise bei Selektionen, Projektionen und partiellen Aggregationen
möglich. Beim MapReduce Verfahren sind dies die in der Map Phase ausgeführten Schritte.
Diese werden dort lokal ausgeführt und das Ergebnis zum Reduce Schritt wieder an die
MasterNode zurückgegeben.
2.8. Benchmark
In [2] wurde ein TPC-H Benchmarktest mit HadoopDB im Vergleich mit DBMS-X und Hive
mit Hadoop. DBMS-X ist ein kommerzielles zeilenbasiertes Datenbanksystem, welches hier
zum Vergleich herangezogen wurde. Im Benchmark (siehe Abbildung 2) wurde HadoopDB
mit einer PostgreSQL Datenbank (HDB-PSLQ) und mit einer VectorWise/X100 Datenbank
(HDB-VM) gegen DBMS-X (DBMS-X) und Hadoop mit Hive (Hive) verglichen.
Beim TPC-H Benchmark kommen eine Reihe unterschiedlicher Datenabfragen (nummeriert
mit 1 bis 20) zum Einsatz, die typische Aufgaben aus der Analyse von Datenbanksystemen
simulieren.
35000
30000
25000
20000
15000
10000
5000
0
1
2
3
4
5
6
7
DBMS-X
8
9
10
HDB-PSLQ
11
12
HDB-VM
13
14
15
16
17
18
19
HIVE
Abbildung 2: Benchmark Ergebnisse
Die HadoopDB Installation in Verbindung mit der spaltenbasierten VectorWise/X100
Datenbank zeigt eine sehr hohe Geschwindigkeit und ist in diesem Vergleich die schnellste
Lösung. Vor allen Dingen im Vergleich zu Hadoop/Hive schneidet HadoopDB-VM sehr gut
ab. Der Ersatz der PostgreSQL Datenbank durch das spaltenorientierte Datenbanksystem
VectorWise/X100 hat eine weitere wesentliche Geschwindigkeitsverbesserung ergeben.
8
20
2.9. Zusammenfassung
HadoopDB hat gezeigt, durch die Verbindung von einem parallelen Datenbanksystem mit
Hadoop ein hochskalierbares, fehlertolerantes und sehr schnelles Datenbanksystem entstehen
kann. Gerade in Umgebungen mit mehreren Terabyte Daten und einer Netzwerk mit mehreren
hundert Knoten ist HadoopDB eine hervorragende Lösung, die, neben der kommerziellen
Variante von Hadapt, auch als OpenSource Lösung unter der Apache Lizenz bereit steht.
HadoopDB kann in hohen Maß skaliert werden und ist auch in heterogenen Umgebungen
lauffähig. Die Verwendung von SQL Abfragen lässt einen einfachen Einstieg in Big Data
Datenbanksystemen zu.
9
3. SQL/MapReduce
3.1. Einführung
SQL/MapReduce (SQL/MR) wurde 2009 von Eric Friedmann et al. [6] vorgestellt.
Wie bei HadoopDB, geht es auch bei SQL/MapReduce darum, große Datenmengen effizient
auszuwerten. Dabei sollen die gleichen Funktionen, wie sie von den bekannten
Datenbanksystemen verwendet werden, wie zum Beispiel benutzerdefinierte Abfragen und
SQL, zum Einsatz kommen.
SQL/MapReduce ist eine Erweiterung für das hochskalierbare, hochparallele
Datenbanksystem nCluster von Aster (jetzt Teradata [7]) und ist kommerziell verfügbar.
nCluster ist ein Big Data Datenbanksystem mit einer shared nothing Architektur [6, p. 1].
Aktuelle Big Data Datenbanksysteme sind auf die Auswertung von großen Datenmengen
ausgelegt und haben in der Regel keine komfortable Abfragesprache. Beispielsweise müssen
bei Hadoop die Abfragen programmiert und verteilt werden. Um mit Hadoop SQL Abfragen
ausführen zu können, ist die Hadoop Erweiterung Hive notwendig oder der Einsatz von
HadoopDB. SQL/MapReduce bietet eine solche SQL Abfragemöglichkeit auf ein Big Data
Datenbanksystem mit den wesentlichen Sprachfunktionen von SQL.
3.2. Ziel
Ziel ist es, die Einschränkungen von aktuellen Big Data Lösungen aufzuheben und dem
Entwickler einen einfachen und bekannten Einstieg in die Anwendung zu erlauben, sowie die
Hürden der Abfragesprachen abzubauen. Dabei werden bei SQL/MapReduce wenige, nur die
notwendigsten Erweiterungen, dem SQL hinzugefügt. Aus diesen SQL Abfragen werden im
Weiteren per MapReduce Verfahren die Abfragen in dem Rechnernetz verteilt. Außerdem
wird die Erstellung von benutzerdefinierten Abfragen unterstützt. Diese können in
unterschiedlichen Programmiersprachen erstellt werden.
SQL/MR hat sich zum Ziel gesetzt, folgende Eigenschaften in Big Data Systemen zu lösen
oder zu verbessern:
Selbstbeschreibend, dynamisch und polymoph
Die Funktionen benötigen kein festes Schema, sondern können das Schema während der
Ausführungszeit abfragen und erzeugen. Dadurch können die Funktionen besser
wiederverwendet werden.
Von Grund auf parallel
Die Funktionen sind an sich parallel und damit einfach zu entwickeln und in beliebigen
parallelen Umgebungen einsetzbar. Die Parallelität unterstützt gleichzeitig auch die
Skalierbarkeit.
Komponierbar
Mehrere Funktionen können miteinander verknüpft werden, da diese sich wie SQL-Subquerys
verhalten und deren Ergebnisse in einer weiteren Funktion weiterverwendet werden können.
10
Einfach optimierbar
Da die Funktionen sich wie Subquerys verhalten, können die relationalen kostenbasierten
Optimierungen aus dem Datenbanksystem verwendet werden.
3.3. Anwendungsfälle
Typische Anwendungsfälle sind auch hier die Analyse von großen Datenbanken, sowie
Reporting, z.B. die Logfile- und Klickanalyse in der Werbebranche.
3.4. Architektur
SQL/MR verwendet als Basissystem das Aster nCluster Database System. nCluster teilt die
Knoten in Queen- und Worker- und Loadernodes ein (Abbildung 3: Architektur Aster
nCluster). Die Queennodes sind die Verwaltungsknoten und kümmern sich um die Verteilung
und Überwachung der Jobs, sowie das Zusammenführen der Daten. Auf den Workernodes
werden die Abfragen in einem lokalen Datenbanksystem verarbeitet. Die Loadernodes sind
für das Beladen und Exportieren von Daten zuständig. Die benutzerdefinierten Abfragen von
SQL/MR werden in den einzelnen Workernodes ausgeführt.
Abbildung 3: Architektur Aster nCluster Database
3.5.Syntax
Die Syntax von SQL wurde nur durch die zusätzliche Schlüsselwörter PARTITION BY und
einer Möglichkeit zum Aufruf der benutzerdefinierte Abfrage erweitert.
SELECT ...
11
FROM functionname(
ON table-or-query
[PARTITION BY expr, ...]
[ORDER BY expr, ...]
[clausename(arg, ...) ...]
)
Der Funktionsaufruf wird in der FROM Klausel vorgenommen. In den Funktionsklammern
wird mit der ON Klausel das Inputschema festgelegt.
Die Klausel PARTITION BY legt die Partitionierung fest und muss auf einen Teil der
Relation in der ON Klausel verweisen.
Mit ORDER BY wird die Sortierung festgelegt. Diese referenziert auf die Relation in der ON
Klausel.
Das Ergebnis einer Funktion ist eine Relation, die auf ganz normalem Wege weiterverarbeitet
werden kann. Mehrere Funktionen können so auch komponiert werden.
SELECT ts, userid, session
FROM
sessionize
(
ON
clicks
PARTITION
BY
userid
ORDER
BY
ts
TIMECOLUMN
(’ts’)
TIMEOUT
(60)
);
Abbildung 4: Beispiel eine Abfrage mit SQL/MapReduce
3.6. Benutzerdefinierte Abfragen
Benutzerdefinierte Abfragen (User Defined Functions / UDF) werden verwendet, um die von
dem Datenbanksystem bereitgestellten Funktionalitäten, zu erweitern (siehe Abbildung 4).
Ein wesentliches Merkmal von SQL/MR ist die Verwendung von benutzerdefinierten
Abfragen bei der Selektionsabfrage. Die benutzerdefinierte Abfragen können in
unterschiedlichen Programmiersprachen erstellt werden, wie z.B. Java, C#, C++, aber auch
Scriptsprachen wie Ruby oder Python. Dazu wird ein einfaches Interface implementiert.
Bei SQL/MapReduce werden zwischen Row Function (Map) und Partition Function
(Reduce) unterschieden. Es können in einer Abfrage beide oder jeweils nur eine der
Funktionen verwendet werden, abhängig, ob diese für die gestellt Abfrage Sinn ergeben.
Row Functions werden jeweils für eine Zeile der Datenmenge unabhängig voneinander
ausgeführt und können keine, eine oder mehrere Rows zurückgeben. Die Row Function
entspricht dem Map Schritt im MapReduce Verfahren.
12
Die Partition Function entspricht dem Reduce Schritt im MapReduce und wird jeweils auf die
Menge der Daten aus der PARTITION BY Klausel angewandt. Die Funktion wird für jede
der Partitionen unabhängig ausgeführt, womit eine hohe Parallelisierung erreicht wird. Auch
die Partition Function kann keine, einer oder mehrerer Zeilen zurückliefern.
3.7. Implementierung einer benutzerdefinierten Abfrage
Um die benutzerdefinierte Abfrage zu erstellen ist ein einfaches Interface zu implementieren.
Interface PartitionFunction {
public Sessionize (RuntimeContract contract);
public void operateOnPartition(..);
public void operrateOnSomeRow(..);
}
Der Name der Klasse wird als Name der benutzerdefinierten Abfrage verwendet. In der
Implementierung wird im Konstruktor die Ein- und Ausgabespalten festgelegt. An dieser
Stelle ist die Polymorphie sichtbar, da die Spalten dynamisch ausgewertet werden.
Die Methode operateOnPartition()ist für die Verarbeitung der Daten einer Partition
zuständig und entspricht dem Reduce Schritt im MapReduce Verfahren.
In der Methode operateOnSomeRow()können die Spalten einer Zeile verarbeitet werden.
Dies entspricht der Berechnung im Map Schritt.
Beispiel einer Funktion zum Zählen aller Wörter [8]:
public void operateOnSomeRows(RowIterator inputIterator,
RowEmitter outputEmitter) {
/*
// This method will be called once for each set of rows.
*/
String a;
StringTokenizer tokenizer;
while (inputIterator.advanceToNextRow()) {
a=inputIterator.getStringAt(0);
tokenizer=new StringTokenizer(a);
while (tokenizer.hasMoreTokens()) {
outputEmitter.addString(tokenizer.nextToken());
outputEmitter.addShort((short)1);
outputEmitter.emitRow();
}
}
}
Ist die Funktion implementiert, muss diese noch im Datenbanksystem installiert werden. Die
Installation wird per CREATE FUNCTION ausgeführt. Danach steht die Funktion zur
Verwendung in einer SQL-Abfrage bereit.
13
3.8. Zusammenfassung SQL/MapReduce
SQL/MapReduce bietet eine einfach zu verwendende Erweiterung von SQL, um auf das
Datenbanksystem nCluster zuzugreifen. Das Erstellen von benutzerdefinierten Abfragen ist
denkbar einfach und von vornherein auf Parallelisierung ausgelegt. Benutzerdefinierte
Abfragen können in unterschiedlichen Programmiersprachen entwickelt werden, was die
Einstiegshürden weiter senkt. Die Funktionen können miteinander kombiniert werden und
lassen sich mit vorhandenen Optimierungsverfahren verarbeiten. Die SQL Erweiterung sind
minimal, was einen schnellen Einsatz unterstützt.
Mit nCluster von Asterdata gibt es leider nur eine kommerzielle Implementierung von
SQL/MapReduce.
14
4. Abbildungsverzeichnis
Abbildung 1: HadoopDB Architektur ........................................................................................ 7
Abbildung 2: Benchmark Ergebnisse ......................................................................................... 8
Abbildung 3: Architektur Aster nCluster Database ................................................................. 11
Abbildung 4: Beispiel eine Abfrage mit SQL/MapReduce ..................................................... 12
5. Literaturverzeichnis
[1] A. Abouzeid, K. Bajda-Pawlikowski und D. Aba, „HadoopDB: An Architectural Hybrid
of MapReduce and,“ in VLDB ‘09, Lyon, 2009.
[2] K. Bajda-Pawlikowski, D. J. Abadi, A. Silberschatz und E. Paulsen, „Efficient Processing
of Data Warehousing Queries in a Split Execution Environment,“ in SIGMOD’11, Athen,
2011.
[3] J.
Constine,
„techcrunch,“
22.08.2012.
[Online].
Available:
http://techcrunch.com/2012/08/22/how-big-is-facebooks-data-2-5-billion-pieces-ofcontent-and-500-terabytes-ingested-every-day/. [Zugriff am 20.05.2013].
[4] Wartala, Hadoop, Open Source Press München, 2012.
[5] S. G. Jeffrey Dean, „MapReduce: Simplified Data Processing on Large Clusters,“ San
Francisco, 2004.
[6] E. Friedman, P. Pawlowski and J. Cieslewicz, "A practical approach to self-describing,
polymorphic, and parallelizable user-defined functions," in VLDB '09, Lyon, 2009.
[7] „asterdata,“ 3.3.2011. [Online]. Available: http://www.asterdata.com/news/110303Teradata-to-Acquire-Aster-Data.php. [Zugriff am 06.06.2013].
[8] „Tutorial: Aster Data Developer Express – Word Count,“ 11.08.2010. [Online]. Available:
http://www.asterdata.com/resources/assets/Tutorial-WordCountFunction.pdf. [Zugriff am
06.06.2013].
15
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 7
Hive
Referent: Markus Höhnerbach
7. Thema: Hive
Markus Höhnerbach
2
Inhaltsverzeichnis
1 Einführung in Hive
3
2 Hive benutzen
2.1 Die logische Struktur der Daten . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Die Sprache HiveQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
4
4
5
3 Hives Mechanismus der Datenspeicherung
3.1 Die physische Struktur der Daten . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Das SerDe-Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Dateiformate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
7
8
4 Die
4.1
4.2
4.3
Architektur von Hive
Metastore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Query Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Execution Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5 Zusammenfassung
9
10
10
12
13
7. Thema: Hive
Markus Höhnerbach
3
1 Einführung in Hive
Aufgrund der wachsenden Datenmenge verdeutlicht sich immer mehr, dass traditionelle
Data-Warehousing-Ansätze nicht gut genug skalieren. Hadoop ist eine Open-Source-Implementierung des von Google veröffentlichten MapReduce-Konzeptes [DG08]. Hadoop stellt dabei ein verteiltes Dateisystem namens HDFS und eine MapReduce-Plattform zur Verfügung.
Damit ermöglicht Hadoop die Analyse großer Datenmengen mit vertretbaren Kosten.
Allerdings hat dies einen Preis: Abfragen müssen von Hand in Programmform erstellt
werden. Dadurch braucht es einen Programmierer, um eine Abfrage zu erstellen. Zudem
stellen sich Fragen der Wartung und der Qualitätssicherung.
Das liegt daran, dass MapReduce im Vergleich zu konventionellen Datenbankabfragen
durch SQL auf einem sehr niedrigen Level operiert.
In [TSJ+ 10] wird Hive vorgestellt. Hive ist ein Open-Source-System, das auf Basis von
Hadoop ein Data-Warehouse implementiert. Dazu stellt Hive eine Sprache namens HiveQL
zur Verfügung, die stark an SQL angelehnt ist. Anfragen in dieser Sprache werden durch
Hive als MapReduce-Jobs an Hadoop weitergereicht.
Diese Umsetzung von HiveQL nach MapReduce vereinfacht die Nutzung der gespeicherten
Daten.
Außerdem bietet Hive diverse Möglichkeiten der Erweiterung, um die Datenspeicherung
zu optimieren oder um möglichst viele verschiedene Datenformate nutzbar zu machen.
Ursprünglich wurde Hive bei Facebook entwickelt. Dort war man zu Hadoop gewechselt,
da konventionelle Datenbanksysteme die anfallenden Daten nicht schnell genug auswerten
konnten. Hive ermöglicht einer Vielzahl Nutzer, die so gesammelten Daten auszuwerten.
Im Folgenden werden wir bei der Nutzersicht beginnend die Konzepte von Hive erläutern.
Von dieser werden wir uns immer weiter entfernen, bis wir mit einer Betrachtung des internen
Aufbaus enden.
7. Thema: Hive
Markus Höhnerbach
4
2 Hive benutzen
2.1 Die logische Struktur der Daten
Hive behandelt Daten nach dem aus konventionellen RDBMS üblichen Paradigma. Es werden
also die Daten in Tabellen unterteilt, die wiederum eine Anzahl von Zeilen enthalten.
Die Tabelle kann dabei anhand einer Anzahl von Spalten partitioniert sein. Es werden
also Zeilen, die in partitionierenden Spalten gleiche Werte haben, an einem gemeinsamen
Ort gespeichert. Das macht Selektionen auf Basis von partitionierten Spalten sehr schnell.
Jede Zeile enthält eine durch die Tabelle festgelegte Anzahl von Spalten. Die Gestalt der
in den Spalten gespeicherten Daten wird durch einen Datentyp festgelegt. Dabei unterstützt
Hive nicht nur konventionelle primitive Datentypen wie Ganzzahlen, Fließkommazahlen oder
Zeichenketten, sondern auch zusammengesetzte Datentypen.
Diese sind in einem relationalen Konzept eher unüblich, da eine Normalisierung der Daten
angestrebt wird. Zusammengesetzte Datentypen verletzen allerdings schon die erste Normalform (1NF). Die in Hive unterstützten zusammengesetzten Datentypen sind
• array<T> eine Liste von Elementen des Typs T
• struct<name: T, ...> eine Struktur, die unter name einen Wert vom Typ T verzeichnet
• map<P, T> ein assoziatives Array vom primitiven Typ P zum Typ T
Dabei lassen sich entsprechende Definitionen, wenn nicht anders erwähnt (siehe map) schachteln.
Die strikte relationale Organisation von Hive steht dabei im Kontrast zu dem Modell von
Hadoop, welches beliebige Daten zulässt. Damit fügt Hive eine relationale Struktur in die
Daten ein, die nötig ist, damit die Daten später relational abgefragt werden können.
2.2 Die Sprache HiveQL
Die Sprache, um solche Abfragen durchzuführen, ist HiveQL. HiveQL unterstützt dabei viele
Konstrukte von SQL. Dazu zählen
• SELECT
• JOIN
• GROUP BY
• UNION ALL
• Subselects
7. Thema: Hive
Markus Höhnerbach
5
Zusätzlich besitzt HiveQL einige auf das MapReduce-Paradigma abgestimmte Erweiterungen.
Zur Datendefinition (DDL) unterstützt HiveQL die Erstellung von Tabellen (CREATE TABLE),
wobei die o.g. Datentypen Verwendung finden. Außerdem können Tabellen gelöscht werden
(DROP) und ihre Struktur geändert werden (ALTER).
CREATE TABLE test_table (test_column int, tset_column array<int>)
Um Daten zu verändern (DML) unterstützt Hive die Kommandos LOAD und INSERT. Dabei
ist die Quelle für LOAD eine Datei, während es für INSERT eine HiveQL-Abfrage ist.
INSERT OVERWRITE TABLE test_table SELECT a, b FROM abcd
Das Resultat einer Abfrage wird dabei in das Format der Tabelle konvertiert und in die
Datei geschrieben. Dagegen ist bei LOAD vorausgesetzt, dass die Daten schon im entsprechenden Format vorliegen. Sie werden bloß an die entsprechende Stelle im HDFS kopiert.
Da HDFS selbst nur das Löschen ganzer Dateien bzw. das Anfügen an existierende Dateien
erlaubt, ist es nicht möglich, einzelne Zeilen zu löschen oder zu verändern.
Im Bezug auf die Datenabfrage unterstützt HiveQL eine Vielzahl der Kommandos, die von
SQL zur Verfügung gestellt werden. Damit lassen sich viele existierende Abfragen übernehmen.
Zudem gibt es die auf das MapReduce-Paradigma angepassten REDUCE- und MAP-Anweisungen. Durch diese kann mit Hilfe benutzerdefinierter Programme ein individueller Mapbzw. Reduce-Schritt durchgeführt werden. Um dabei eine möglichst breite Menge von Programmen zu unterstützen, erfolgt die Kommunikation mit ihnen per Standardeingabe und
Standardausgabe. Dadurch müssen die Daten wiederholt zwischen Hive und Text konvertiert
werden, was einen gewissen Overhead erzeugt.
Eine Besonderheit von Hive sind multi-table insert-Operationen. Es können mehrere Abfragen, die auf der gleichen Tabelle basieren, gemeinsam durchgeführt werden. Der Scan über
die Basistabelle geschieht dann nur einmal. Dies kann insbesondere dann nützlich sein, wenn
die Abfragen nicht direkt auf einer Tabelle, sondern auf einem Subselect operieren.
Die genauen Details von HiveQL sind im Hive Language Manual niedergelegt [HLM].
2.3 Ein Beispiel
Das folgende Beispiel ist an [TSJ+ 09, Abschnitt 2.3] angelehnt. Es geht dabei um StatusUpdates, die von Nutzern durchgeführt werden, wie es z.B. bei Facebook möglich ist. Dazu
sind alle Updates eines Tages in einer Datei gespeichert.
Zur Analyse der Daten wird eine Tabelle status_updates verwendet. ds ist das Datum,
von dem der Status stammt. Da die Daten täglich anfallen, ist es sinnvoll, sie nach dem
Datum zu partitionieren.
CREATE TABLE status_update (userid int, status string)
PARTITIONED BY (ds string)
Die Daten können dann per LOAD geladen werden. Der 23.6.1921 steht für das aktuelle
Datum1 .
LOAD DATA LOCAL INPATH ’/path/to/status/logs’
INTO TABLE status_updates PARTITION (ds=’1921-06-23’)
1
In diesem Fall ist es der Geburtstag von Alan Turing
7. Thema: Hive
Markus Höhnerbach
6
Diese können nun aggregiert werden:
SELECT COUNT(*)
FROM status_updates
WHERE ds=’1921-06-23’
Oder mit einer Tabelle profiles(userid int, school string, gender int) verknüpft
werden:
SELECT a.status, b.school, b.gender
FROM status_updates a
JOIN profiles b
ON (a.userid = b.userid AND a.ds=’2009-03-20’)
Auch auf Basis dieser Abfrage könnte man nun nach dem Geschlecht oder der Schule
auswerten, wodurch man zu folgender Abfrage gelangt (nach [TSJ+ 10, S. 1000]).
FROM (SELECT a.status, b.school, b.gender
FROM status_updates a JOIN profiles b
ON (a.userid = b.userid
AND a.ds=’1921-06-23’ )) subq1
INSERT OVERWRITE TABLE gender_summary
PARTITION(ds=’1921-06-23’)
SELECT subq1.gender, COUNT(1)
GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary
PARTITION(ds=’1921-06-23’)
SELECT subq1.school, COUNT(1)
GROUP BY subq1.school
Hier werden gleich mehrere Features genutzt:
• Subselects
• multi-table insert
• Partitionen
Letztendlich erhält man die Anzahl der Statuts-Updates mit Bezug auf das Geschlecht
und die besuchte Schule.
7. Thema: Hive
Markus Höhnerbach
7
3 Hives Mechanismus der
Datenspeicherung
Die Beeinflussung der Art der Datenspeicherung ist bei Hive besonders wichtig, da viele Datenquellen bei richtiger Einrichtung ohne Transformation in die Datenbank geladen
werden können. Dazu wird im folgenden erklärt, wo Hive was speichert. Danach wird der
Hive-Mechanismus der Anpassung erläutert. Daraufhin wird auch noch der darunter liegende
Eingabemechanismus von Hadoop erläutert.
3.1 Die physische Struktur der Daten
Die Daten werden physisch in HDFS gespeichert. Dazu gibt es einen durch einen Konfigurationsparameter festgelegten Pfad, unter dem das Hive-Warehouse abgelegt ist. Die Organisation erfolgt dann anhand des Dateisystems.
So entspricht eine Tabelle einem Verzeichnis unterhalb des für das Hive-Warehouse vorgesehenen Pfads. Partitionen entsprechen dann Verzeichnissen unterhalb des Tabellen-Verzeichnisses.
Die letzte Struktur sind, sofern sie eingerichtet wurden, Buckets. Das sind Dateien, über
die die Daten durch ein Hash-Verfahren verteilt wurden. Sie sollen es ermöglichen, schnell
eine repräsentative Teilmenge der Daten zu ermitteln. Wenn keine Buckets eingerichtet sind,
werden alle Daten der Partition in einer einzigen Datei im Verzeichnis der Partition gespeichert. Wenn keine Partitionen eingerichtet sind, werden die Daten im Verzeichnis der Tabelle
abgelegt.
Falls die Daten außerhalb des Warehouse-Verzeichnisses gespeichert werden, kann die Tabelle als EXTERNAL deklariert werden. Dadurch wird kein Verzeichnis für sie erstellt. Stattdessen werden die Daten, die an einem anderen Ort im HDFS gespeichert sind, so behandelt, als
seien sie im Warehouse. Ein wichtiger Unterschied zu regulären Daten ist, dass solche Daten
nicht durch den Befehl DROP TABLE gelöscht werden. Es werden lediglich die Metadaten aus
Hive entfernt, die eigentlichen Daten bleiben aber unangetastet.
3.2 Das SerDe-Interface
SerDes sind Schnittstellen, die von einer internen Repräsentation zu Hive und zurück übersetzen.
So können Daten in verschiedensten Formaten ausgewertet werden.
Standardmäßig nutzt Hive einen SerDe namens LazySerDe, der nur benötigte Spalten
in die interne Repräsentation umsetzt. Dadurch fallen für unbenutzte Spalten so gut wie
keine Kosten an. Dazu lassen sich einige Parameter angeben, wie das Trennzeichen zwischen
Zeilen oder Spalten. Dies geschieht durch die Anweisungen FIELDS TERMINATED BY und
LINES TERMINATED BY.
CREATE TABLE test_csv (a int, b int)
ROW FORMAT DELIMITED
FIELDS TERMINNATED BY ’,’
7. Thema: Hive
Markus Höhnerbach
8
Diese Tabelle nutzt als Trennzeichen zwischen den Spalten nun ein Komma. Damit lassen
sich nun CSV-Dateien laden, sofern sie als Trennzeichen ein Komma nutzen und nicht die
Escape-Logik aus CSV verwenden. Um CSV so zu implementieren, dass auch Escapes per
Anführungszeichen möglich sind, muss ein eigener SerDe implementiert werden.
Ein weiterer SerDe, der die Mächtigkeit des Konzeptes veranschaulicht, ist RegexSerDe
aus hive-contrib.jar. Der Datensatz wird dann mit einem regulären Ausdruck eingelesen.
Eine mögliche Anwendung (aus [TSJ+ 10, S. 1000]) dafür ist das Auswerten von WebserverLogs.
add jar ’hive_contrib.jar’;
CREATE TABLE apachelog(
host string,
identity string,
user string,
time string,
request string,
status string,
size string,
referer string,
agent string)
ROW FORMAT SERDE
’org.apache.hadoop.hive.contrib.serde2.RegexSerDe’
WITH SERDEPROPERTIES(
’input.regex’ = ’([^ ]*) ([^ ]*) ([^ ]*) (-|\\[[^\\]]*\\]) ([^
\"]*|\"[^\"]*\") (-|[0-9]*) (-|[0-9]*)(?: ([^ \"]*|\"[^\"]*\") ([^
\"]*|\"[^\"]*\"))?’,
’output.format.string’ = ’%1$s %2$s %3$s %4$s %5$s %6$s
%7$s %8$s %9$s’);
Über das Parameter SERDEPROPERTIES können beliebige Parameter angegeben werden.
3.3 Dateiformate
Hadoop, nicht Hive, stellt außerdem die Möglichkeit zur Verfügung, die Daten in verschiedenen Formaten zu speichern. Damit existiert eine weitere Ebene, in der die Speicherung
den Bedürfnissen des Nutzers angepasst werden kann. Die Dateiformate legen fest, wie die
einzelnen Datensätze in der Datei gespeichert werden.
Für Textdateien steht das Format TEXTFILE bereit, zur binären Speicherung dient das
Format SEQUENCEFILE.
Das Format wird in einer STORED AS-Anweisung festgelegt, wenn die Tabelle erstellt wird:
CREATE TABLE test_table (test_column int) STORED AS SEQUENCEFILE
Es ist ebenfalls möglich, eigene Formate zu erstellen. Dafür müssen die entsprechenden
Schnittstellen implementiert werden.
Ein weiteres Dateiformat ist RCFile. Es ermöglicht eine spaltenorientierte Speicherung der
Daten. So können Scans über eine Teilmenge der Spalten beschleunigt werden.
Dateiformate können also insbesondere auch dazu genutzt werden, spezielle Zugriffsmuster
zu implementieren.
7. Thema: Hive
Markus Höhnerbach
9
4 Die Architektur von Hive
Abbildung 4.1: Die Architektur von Hive (aus [TSJ+ 10, S. 1000]
Die Abbildung gibt einen Überblick über die Komponenten von Hive. Sie zeigt außerdem
die Schnittstelle mit Hadoop, den Driver.
Die oberste Schicht bilden bei Hive solche Subsysteme, die mit dem Benutzer interagieren.
Dazu zählt die Kommandozeilenschnittstelle (CLI), eine web-basierte Schnittstelle sowie die APIs ODBC und JDBC. Die APIs setzen auf dem Hive Thrift Server auf, der die
Ausführung von HiveQL von vielen verschiedenen Programmiersprachen per RPC ermöglicht.
Der Metastore fungiert als Datenkatalog. Er enthält sämtliche Schema-Informationen und
Metadaten. Damit ist er für viele der anderen Komponenten von zentraler Bedeutung. Deswegen wird ihm im Folgenden ein eigener Abschnitt gewidmet.
Der Driver ist die Komponente, die den gesamten Lebenszyklus einer Anfrage verwaltet.
Währenddessen werden auch statistische Daten wie die Ausführungszeit von ihm erhoben. Er
nimmt die Anfrage von einer der oben beschriebenen Schnittstellen in Empfang. Dann gibt
er sie an den Query Compiler weiter, der die Anfrage in einen Ausführungsplan umsetzt. Der
Ausführungsplan besteht aus mehreren MapReduce-Jobs. Dieser Ausführungsplan ist ein gerichteter azyklischer Graph (engl. directed acyclic graph, DAG). Dieser gibt die Abhängigkeit
der MapReduce-Jobs untereinander an.
Die Execution Engine führt dann diesen Ausführungsplan unter Berücksichtigung der
Abhängigkeiten aus. Damit bildet die Execution Engine letztlich die Schnittstelle zu Hadoop.
Im Folgenden wird noch ein genauerer Blick auf den Metastore, den Query Compiler und
die Execution Engine geworfen.
7. Thema: Hive
Markus Höhnerbach
10
4.1 Metastore
Der Metastore ist das Subsystem, das die Schema-Informationen verwaltet. Es werden Daten
über Tabellen, Partitionen, Konfigurationsparamtern, SerDes und Dateiformate festgehalten.
Die Tatsache, dass Metadaten gespeichert werden, unterscheidet Hive von einer Großzahl
anderer auf MapReduce aufsetzender Lösungen wie zum Beispiel Pig. Hive rückt damit in
die Richtung konventioneller Data-Warehousing-Anwendungen.
Die verwalteten Daten sind für die übrigen Subsysteme von elementarer Bedeutung. So
muss zum Beispiel der Query Compiler den Aufbau der abgefragten Tabellen kennen. Um
diesen Zugriff zu beschleunigen, ist der Metastore nicht auf Basis von Hadoop, sondern einem
konventionellen RDBMS implementiert. Denn da die benötigten Daten gering sind, ist die
Minimierung der Latenz wichtiger als die Maximierung des Durchsatzes.
Um eine Überlastung des Metastores dabei zu verhindern, greifen die ausführenden Mapper
und Reducer unter keinen Umständen auf den Metastore zu. Dies geschieht, indem alle zur
Laufzeit benötigten Informationen zwischengespeichert werden. So wird verhindert, dass eine
Skalierung im Bereich der Hadoop-Knoten automatisch zu einer Skalierung beim Metastore
führen muss.
Der Metastore ist eine kritische Komponente. Er sollte also häufig gesichert werden und
Teil einer Hochverfügbarkeitsstrategie sein.
Er skaliert zwar nicht mit der Menge der verarbeiteten Daten, aber er muss trotzdem mit
der Anzahl der ausgeführten Anfragen skalieren.
4.2 Query Compiler
Nachdem der Driver die HiveQL-Anfrage erhalten hat, reicht er sie an den Query Compiler weiter. Dieser verarbeitet sie in mehreren Schritten, bis er bei einem Ausführungsplan
angelangt ist.
Ein solcher Plan kann
• MapReduce-Operationen zur Datenauswertung
• HDFS-Operationen zur Datenspeicherung
• Metadaten-Operationen zur Datendefinition
enthalten. Dies hängt von der jeweiligen Anfrage ab.
• Die Anfrage wird mittels ANTLR, einem Parsergenerator, in einen AST geparst (Abstract Syntax Tree)
• Der Query Compiler fordert vom Metastore die benötigten Metadaten an. Mit diesen
Daten wird die Anfrage auf semantische Fehler überprüft. Dazu zählen die Benennung
von Spalten oder der Typ von Spalten. Außerdem werden einige Transformationen
durchgeführt. So wird das * in SELECT * in die Spaltennamen der Tabelle aufgelöst,
oder Konvertierungen durchgeführt. Aus dem um diese Informationen angereicherten
AST wird ein logischer Ausführungsplan, ein DAG, erstellt.
• Der logische Plan wird optimiert. Die geschieht anhand von festgelegten Regeln. Es
sind auch benutzerdefinierte Regeln möglich. Die Optimierung lässt sich auch durch
den Benutzer mit einer gewisse Art von Kommentaren steuern.
7. Thema: Hive
Markus Höhnerbach
11
Abbildung 4.2: Ein Ausführungsplan (aus [TSJ+ 10, S. 1003]
• Aus dem optimierten logischen Plan wird ein physischer Ausführungsplan erstellt, indem der logische Plan in Map- und Reduce-Tasks eingeteilt wird.
Dieser physische Ausführungsplan wird dann an die Execution Engine übergeben.
Die obige Abbildung zeigt einen solchen physischen Ausführungsplan für die Anfrage
[TSJ+ 10, S. 1003]
FROM (SELECT a.status, b.school, b.gender
FROM status_updates a JOIN profiles b
ON (a.userid = b.userid
AND a.ds=’2009-03-20’ )) subq1
INSERT OVERWRITE TABLE gender_summary
PARTITION(ds=’2009-03-20’)
7. Thema: Hive
Markus Höhnerbach
12
SELECT subq1.gender, COUNT(1)
GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary
PARTITION(ds=’2009-03-20’)
SELECT subq1.school, COUNT(1)
GROUP BY subq1.school
Dabei entsprechen die Knoten den Operatoren. Die Pfeile zeigen in diesem Kontext an,
dass Daten entlang der Pfeile weitergegeben werden.
Die Abbildung zeigt drei MapReduce-Jobs. Die Trennung zwischen ihnen erfolgt durch
einen FileSinkOperator, der dem Schreiben in eine Datei in HDFS entspricht. Innerhalb
eines MapReduce-Jobs erfolgt die Trennung durch den ReduceSinkOperator.
Die Pfeile zwischen den einzelnen MapReduce-Jobs zeigen eine Abhängigkeit des einen
von der Ausgabe des anderen an. Es können also die oberen beiden Jobs nicht ausgeführt
werden, bevor der untere fertig ist.
Diese Abhängigkeiten sind im Ausführungsplan verzeichnet und werden durch die Execution Engine überwacht (siehe unten).
4.3 Execution Engine
Die Execution Engine bildet die Schnittstelle zwischen Hive und Hadoop. Sie sorgt dafür,
dass die einzelnen vom Query Compiler ermittelten Aufgaben in der richtigen Reihenfolge
abgearbeitet werden. Dazu müssen insbesondere die Abhängigkeiten beachtet werden.
Zur Ausführung selbst werden sämtliche benötigten Daten in einer Datei abgelegt, die den
generischen Mappern und Reducern von Hive als Parameter dienen. Diese lesen dann die
Datei wieder ein und führen die enthaltenen Operationen aus.
Zwischenergebnisse werden dabei ins HDFS geschrieben und gelöscht, nachdem sie nicht
mehr benötigt werden.
7. Thema: Hive
Markus Höhnerbach
13
5 Zusammenfassung
Hive ist ein Open-Source-Projekt unter dem Schirm der Apache Foundation. Dies garantiert
langfristig das Fortbestehen des Projektes. Das System wird aktiv weiterentwickelt.
Hive bietet eine benutzerfreundliche Schnittstelle zu Hadoop. Benutzerfreundlich“ meint
”
in diesem Fall nicht nur, dass es für menschliche Nutzer einfach zu verstehen ist, sondern
auch, dass viele SQL-nutzende Systeme z.B. per JDBC auf den Datenbestand zugreifen
können.
Damit füllt Hive eine bestehende Lücke zwischen Hadoop und höheren Anwendungen. Dabei wird auch ein Teil der Komplexität von Hadoop verborgen. Es besteht also die Gefahr,
dass die durch Hive angestoßene Operation vom Nutzer unterschätzt wird. Wenn Beispielsweise eine Anwendung vermeintlich schnelle Operationen wie das Zählen von Datensätzen
anstößt, hat dies eventuell eine teure Anfrage zur Folge.
Bei aller Abstraktion ist also trotzdem wichtig, auf welcher Art System operiert wird,
nämlich einem auf Durchsatz optimierten System.
7. Thema: Hive
Markus Höhnerbach
14
Literaturverzeichnis
[DG08]
Jeffrey Dean and Sanjay Ghemawat. Mapreduce: simplified data processing on
large clusters. Commun. ACM, 51(1):107–113, January 2008.
[HLM]
Hive language manual. http://wiki.apache.org/hadoop/Hive/LanguageManual.
Abgerufen am 12.06.2013.
[TSJ+ 09] Ashish Thusoo, Joydeep Sen Sarma, Namit Jain, Zheng Shao, Prasad Chakka,
Suresh Anthony, Hao Liu, Pete Wyckoff, and Raghotham Murthy. Hive - a warehousing solution over a map-reduce framework. PVLDB, 2(2):1626–1629, 2009.
[TSJ+ 10] Ashish Thusoo, Joydeep Sen Sarma, Namit Jain, Zheng Shao, Prasad Chakka,
Ning Zhang, Suresh Anthony, Hao Liu, and Raghotham Murthy. Hive - a petabyte scale data warehouse using hadoop. In Feifei Li, Mirella M. Moro, Shahram
Ghandeharizadeh, Jayant R. Haritsa, Gerhard Weikum, Michael J. Carey, Fabio
Casati, Edward Y. Chang, Ioana Manolescu, Sharad Mehrotra, Umeshwar Dayal,
and Vassilis J. Tsotras, editors, ICDE, pages 996–1005. IEEE, 2010.
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 2.6
Pig
Referent: Roland Hellwig
Inhaltsverzeichnis
1 Einleitung: SQL, MapReduce und Pig.........................................................................................3
2 Das Pig-System.............................................................................................................................8
2.1 Apaches Pig Philosophy........................................................................................................8
2.2 Geschichte.............................................................................................................................8
2.3 Installation.............................................................................................................................8
2.4 Eingabe- und Ausführungsmodi............................................................................................9
2.5 Verarbeitung von PigLatin-Anweisungen...........................................................................10
2.5.1 Logischer Plan.............................................................................................................11
2.5.2 Physischer Plan............................................................................................................12
2.5.3 MapReduce-Plan.........................................................................................................13
3 Die Sprache PigLatin..................................................................................................................14
3.1 Grundlagen..........................................................................................................................14
3.2 Datentypen und Ausdrücke.................................................................................................14
3.3 Diagnose-Operatoren..........................................................................................................15
3.3.1 DUMP.........................................................................................................................15
3.3.2 DESCRIBE..................................................................................................................16
3.3.3 ILLUSTRATE.............................................................................................................16
3.3.4 EXPLAIN....................................................................................................................16
3.4 Relationale Operatoren........................................................................................................17
3.4.1 LOAD..........................................................................................................................17
3.4.2 STORE........................................................................................................................17
3.4.3 ORDER BY.................................................................................................................18
3.4.4 DISTINCT...................................................................................................................18
3.4.5 FILTER........................................................................................................................18
3.4.6 LIMIT..........................................................................................................................19
3.4.7 GROUP.......................................................................................................................19
3.4.8 FOREACH und FLATTEN.........................................................................................20
3.4.9 JOIN............................................................................................................................21
3.4.10 COGROUP................................................................................................................23
3.5 Funktionen..........................................................................................................................24
3.5.1 Built In-Funktionen.....................................................................................................24
3.5.2 User Defined Functions...............................................................................................24
Big Data Management
Pig
Seite 3
1 Einleitung: SQL, MapReduce und Pig
Als „BigData“ bezeichnet [14] „besonders große Datenmengen [..], die mit Hilfe von StandardDatenbanken und Datenmanagement-Tools nicht oder nur unzureichend verarbeitet werden
können. [...]. Das Volumen dieser Datenmengen geht in die Terabytes, Petabytes und Exabytes.“
RDBMS konkurrieren bei der Analyse dieser Datenmengen mit MapReduce-Implementierungen.
Die OpenSource MapReduce-Implementierung Hadoop [01] hat sich dabei in den letzten Jahren
als „Lingua franca für das Durchführen von Rechenprozessen mit großen Datenmengen“
entwickelt [06]. Verschiedene kommerzielle Anbieter traditioneller RDBMS wie Oracle [02],
Teradata [03] oder IBM [04] haben inzwischen in ihren Produkten entsprechende
Funktionalitäten oder Schnittstellen zu Hadoop implementiert.
Im Umfeld von Hadoop entstanden darüber hinaus weitere OpenSource-Produkte wie
– Hive [13], ein DataWarehouse System auf Basis von Hadoop oder
– Pig [05], eine Dataflow Language mit Ausführungsumgebung zur vereinfachten
Formulierung von Programmen zur Datenanalyse.
Im Rahmen dieser Seminarausarbeitung wird Pig vorgestellt. Dazu wird zunächst anhand eines
einfachen Fallbeispiels die Verarbeitung einer Textdatei mit
•
SQL in einem RDBMS,
•
der MapReduce-Technik in einer Hadoop-Installation
•
einem PigLatin-Skript in Verbindung mit der Pig-Ausführungsumgebung
verglichen.
Das Fallbeispiel – Ein Fangbuch eines Anglers
Das Fangbuch eines Anglers wird in Tabellenform geführt und besitzt folgenden Aufbau:
Nach vielen Einträgen stellt sich die Frage, was denn der größte gefangene Fisch jeder
Fischart war.
Ein traditioneller Ansatz für die Lösung dieser Aufgabe legt – aufgrund der
vorgegebenen tabellarischen Struktur des Fangbuchs – folgendes Vorgehen
nahe:
1. Anlegen einer Tabelle in einem relationalen Datenbanksystem
2. Importieren der Daten des Fangbuchs in diese Tabelle
3. Absetzen einer geeigneten select-Anweisung in SQL
Die Tabellenstruktur ergibt sich unmittelbar aus dem Aufbau des Fangbuchs, die select-Anweisung enthält eine group by-Klausel und mit max
eine Aggregratfunktion, die den größten Wert einer jeden Gruppe bestimmt: Abbildung 1: Tabelle
Fangbuch
Big Data Management
Pig
Seite 4
select fisch, max(laenge) from fangbuch group by fisch;
Implementiert wurde diese Lösung mit Hilfe der relationalen OpenSource-Datenbank Apache
Derby [07]:
–- Skript fangbuch.sql
–- Ermittelt die größten gefangenen Fische jeder Fischart
connect 'jdbc:derby:memory:fangDB;create=true';
create schema angeln;
create table angeln.fangbuch(fangdatum date, temperatur int, luftdruck int,
gewaesser varchar(35), fisch varchar(35), laenge int);
CALL SYSCS_UTIL.SYSCS_IMPORT_DATA('ANGELN', 'FANGBUCH', null, null,
'fangbuch2', null, null, null, 0);
select fisch, max(laenge) as "PBM" from angeln.fangbuch group by fisch;
connect 'jdbc:derby:memory:fangDB;drop=true';
Es wird zunächst eine in-memory-Datenbank „fangDB“ neu erstellt und ein Schema und eine
Tabelle werden angelegt. Mit einer Import-Funktion von Derby werden die Daten aus der
Textdatei in die Tabelle importiert, mit der select-Anweisung schließlich das Ergebnis ermittelt:
FISCH
|PBM
----------------------------------------------Aland
|35
Barsch
|32
Doebel
|30
...
So überzeugend einfach dieser Ansatz zunächst erscheint, so erweist er sich doch im Umfeld von
BigData als problematisch:
•
•
Dateien in Größen von mehreren Terabyte werden sich kaum als in-memory-DB
analysieren lassen – hier sind Produkte kommerzieller Anbieter gefordert.
Andererseits bieten kommerzielle RDBMS wesentlich mehr Funktionalität an, als im Anwendungskontext einer BigData-Analyse erforderlich ist: Diese beschränkt sich häufig
auf ein batch processing mit Filter-, Gruppierungs- oder Sortierfunktionen. Die Eingabedateien werden dabei lediglich vollständig gelesen – insert-, update- oder deleteOperationen sind in der Regel nicht erforderlich.
MapReduce ist ein von Google eingeführtes Programmiermodell zur Verarbeitung großer Datenmengen [15]. Eingabedateien werden dabei im Rahmen einer zweistufigen Verarbeitung (Mapund Reduce-Phase) analysiert.
Auch die oben beschriebene Aufgabenstellung lässt sich als MapReduce-Anwendung lösen: Die
Datei wird zeilenweise verarbeitet, jede Zeile wird dabei einem Map-Prozess zugewiesen. Der
Map-Prozess bestimmt ein key-value-Paar, im konkreten Fall bestehend aus der Fischart (key)
und der Länge (value).
Diese key-value-Paare werden im Shuffle-Prozess sortiert und den folgenden Reduce-Prozessen
zur Weiterverarbeitung vorgelegt. Dabei werden alle key-value-Paare mit dem gleichen key
jeweils demselben Reduce-Prozess zugeordnet.
Im Reduce-Prozess schließlich wird das Maximum aus den values für jeden key ermittelt und als
„Ergebnis“-key-value-Paar gespeichert:
Big Data Management
Pig
Seite 5
Abbildung 2: Datenanalyse mit MapReduce
Apache Hadoop ist ein OpenSource-Framework für MapReduce-Anwendungen. Eine Implementierung der oben beschriebenen MapReduce-Anwendung in Java erfordert zunächst die Bildung
von Subklassen der Mapper- und Reducer-Klasse des Frameworks:
class MaxFischMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
String[] felder = line.split(Pattern.quote(","));
String fish = felder[4];
int groesse = Integer.parseInt(felder[5]);
context.write(new Text(fish), new IntWritable(groesse));
}
}
class MaxFischReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
public void reduce(Text key, Iterable<IntWritable> values, Context
context)
throws IOException, InterruptedException {
int maxGroesse = Integer.MIN_VALUE;
for (IntWritable value:values) {
maxGroesse = Math.max(maxGroesse, value.get());
}
context.write(key, new IntWritable(maxGroesse));
}
}
Anschließend ist ein Job anzulegen und zu konfigurieren (Festlegung der Input- und OutputPfade, von Mapper- und Reducer-Klassen und von key- und value-Klassen des Outputs):
Big Data Management
Pig
Seite 6
public class MaxFisch {
public static void main(String[] args) throws Exception {
Job job = new Job();
job.setJarByClass(MaxFisch.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.setMapperClass(MaxFischMapper.class);
job.setReducerClass(MaxFischReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true)?0:1);
}
}
Die drei Klassen werden in ein jar-Archiv gepackt und dem Hadoop-System zur Ausführung
übergeben1:
hadoop jar FishMR.jar MaxFisch fangbuch3 output
Das Ergebnis wird von Hadoop im 'output'-Verzeichnis abgelegt:
Aland
Barsch
Doebel
...
35
32
30
Die Nutzung des Hadoop-Frameworks stellt sicher, dass auch die Verarbeitung großer
Datenmengen mit Hilfe des Hadoop File Systems (HDFS) und der MapReduce-Prozesse
möglich ist. Es gibt jedoch auch Nachteile wie in [08] und [09] beschrieben:
•
ein starrer, wenig flexibler 2-stufiger Datenfluss mit nur einer Datei als Eingabe
•
selbst häufig benutzte Operationen wie Projektionen, Filtern oder das Nutzen von
Aggregatfunktionen (z.B. Maximum, Durchschnitt) müssen manuell programmiert
werden
•
es entstehen viele schwer wartbare Java-Programme
Mit dem Pig-System versuchen die Entwickler, die Vorteile einer deklarativen Hochsprache wie
SQL mit dem prozeduralen MapReduce-Ansatz zu verbinden [08, 09]. [10] beschreibt Pig als „a
data flow language and execution environment for exploring very large datasets. Pig runs on
HDFS and MapReduce clusters.“
Pig bietet also zunächst eine Datenflusssprache, „PigLatin“ genannt an, in der Programme zur
Datenanalyse formuliert werden können. Das einfache SQL-Beispiel
select fisch, max(laenge) from fangbuch group by fisch;
lässt sich in PigLatin in einem Skript „fangbuch.pig“ wie folgt formulieren:
1
Hadoop wird hier im sog. „Standalone-Modus“ aufgerufen. In diesem Modus greift Hadoop auf das lokale
Dateisystem zu und nutzt weder das verteilte Dateisystem HDFS noch werden parallele Prozesse für die Mapund Reduce-Verarbeitung gestartet.
Big Data Management
Pig
Seite 7
–- Skript fangbuch.pig
–- Ermittelt die größten gefangenen Fische jeder Fischart
fang = load 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fisch = group fang by fischart;
pbm = foreach fisch generate group, MAX(fang.laenge);
store pbm into 'output';
'fang', 'fisch' und 'pbm' sind Aliase für Relationen. 'load' lädt die Datei 'fangbuch1' in die
Relation 'fang'. Mit 'as' wird der load-Operator dabei um die Angabe eines Schemas ergänzt.
'fisch' gruppiert 'fang' nach dem Datenfeld 'fischart', 'pbm' enthält für jede Gruppe ein Tupel
bestehend aus der 'fischart' und der maximalen Länge. 'store' speichert schließlich 'pbm' im
Verzeichnis 'output'.
Falls Pig in einer Produktivumgebung mit Hadoop-Anbindung läuft, wird zunächst die Eingabedatei (hier: 'fangbuch1') dem Hadoop File System übergeben:
hadoop fs -copyFromLocal fangbuch1 fangbuch1
Anschließend kann das PigLatin-Skript der Pig-Ausführungsumgebung übergeben werden:
pig fangbuch.pig
Diese überprüft das Skript auf syntaktische und semantische Korrektheit, optimiert ggf. die
Anweisungen, erstellt schließlich MapReduce-Jobs und stellt eine Verbindung zum HadoopCluster her:
Connecting to hadoop file system at: hdfs://localhost/
Connecting to map-reduce job tracker at: localhost:8021
Die MapReduce-Jobs werden übergeben und unter Hadoop ausgeführt. Hadoop vergibt eine
JobId und bestätigt schließlich die erfolgreiche Ausführung:
Abbildung 3: Hadoop-Ergebnis
Im HDFS findet man im Verzeichnis 'output' das Ergebnis des Verarbeitungslaufs:
Big Data Management
Pig
Seite 8
2 Das Pig-System
2.1 Apaches Pig Philosophy
In [11] und [16] beschreiben die Entwickler, welche grundlegenden Prinzipien sie bei der Entwicklung von Pig verfolgen:
•
„Pigs Eat Anything“
Die Eingabedaten, die Pig verarbeiten kann, sollen keinerlei Beschränkungen unterliegen:
Auch schemafreie, verschachtelte oder unstrukturierte Daten sollen verarbeitet werden
können. Pig bietet dazu neben verschiedenen Built In-Funktionen auch die Möglichkeit,
benutzerdefinierte Funktionen zum Laden und Speichern zu implementieren (s. Kap.
3.5.1, 3.5.2)
•
„Pigs Live Anywhere“
PigLatin ist konzipiert als Sprache für die parallele Datenverarbeitung. Aktuell benutzt
Pig das Hadoop-System, aber es soll nicht darauf beschränkt bleiben.
•
„Pigs Are Domestic Animals“
Pig ist nicht nur eine Sprache und eine Ausführungsumgebung für die parallele Datenverarbeitung, sondern auch ein Framework: Die Integration benutzerdefinierter Funktionen
zur Evaluierung, zum Laden oder zum Speichern von Daten wird unterstützt (s. Kap.
3.5.2).
•
„Pigs Fly“
Ziel ist eine effiziente, schnelle Datenverarbeitung. Pig optimiert dazu den Datenfluss (s.
Kap. 2.5, 3.4.5) oder bietet dem Benutzer Möglichkeiten, abhängig von den zu analysierenden Daten (s. Kap. 3.4.9) oder den zu verwendenden Funktionen (s. Kap. 3.5.2)
performante Alternativen bei der Verarbeitung zu wählen.
Der Name "Pig" soll einem Entwickler spontan eingefallen sein. Da die Bezeichnung kurz und
griffig war, beließ man es dabei – zumal damit diverse Wortspiele wie "Pig Philosophy", "grunt",
"PigLatin" oder "Piggy Bank" möglich wurden [11].
2.2 Geschichte
Die Entwicklung von Pig begann 2006 als Forschungsprojekt bei Yahoo! Research [19]. 2008
wurde das System auf der SIGMOD in Vancouver vorgestellt [08], 2009 liefen bereits die Hälfte
der Hadoop-Jobs bei Yahoo! unter Pig [11]. Seit 2010 wird Pig als Top-Level Apache Projekt
geführt [11]. Es ist damit als Open Source unter der Apache Lizenz [27] frei verfügbar.
Pig wird aber aktuell nicht nur bei Yahoo! eingesetzt, sondern auch bei Twitter, AOL, LinkedIn
oder Ebay [20]. Bei Amazon ist Pig Bestandteil von Amazons Elastic MapReduce (Amazon
EMR) Web-Service [12].
2.3 Installation
Pig ist eine Java-Anwendung und benötigt Java 1.6 oder neuer. Die Umgebungsvariable
JAVA_HOME muss auf das root-Verzeichnis der Java-Installation zeigen. Unter Windows wird
Cygwin [18] benötigt. Eine Hadoop-Implementierung ist nur erforderlich, wenn im PigAusführungsmodus "MapReduce" gearbeitet werden soll (s. Kap. 2.4).
Die offizielle Version von Apache Pig steht auf der Release-Seite [17] zur Verfügung. Aktuell
veröffentlicht ist die Version 0.11.1 vom 1. April 2013. Nach dem Download sollte das Archiv
Big Data Management
Pig
Seite 9
pig-n.n.n.tar.gz ausgepackt werden. Im Installationsverzeichnis pig-n.n.n findet man im
Verzeichnis bin das Shell-Skript pig zum Starten der Anwendung. Es empfiehlt sich, das binVerzeichnis in die PATH-Umgebungsvariable aufzunehmen.
2.4 Eingabe- und Ausführungsmodi
Pig kennt drei Möglichkeiten, PigLatin-Anweisungen entgegenzunehmen. [09] unterscheidet
zwischen dem „interactive mode“, „batch mode“ und „embedded mode“.
Im „interactive“ Modus kommuniziert der Benutzer interaktiv mit einer Shell, genannt „Grunt“.
Gestartet wird dieser Modus durch Eingabe von pig oder pig -x local. Die grunt-Shell meldet
sich mit
grunt>
und erwartet die Eingabe von Pig-Kommandos.
Im „batch“ Modus übergibt der Benutzer der Pig-Ausführungsumgebung ein PigLatin-Skript
(eine Folge von PigLatin-Anweisungen in einer Textdatei) durch Eingabe von pig -x local
skriptname bzw. pig skriptname.
Der „embedded“ Modus ermöglicht es, PigLatin-Anweisungen mit Hilfe von Methodenaufrufen
innerhalb von Java-Programmen auszuführen. Zentrale Klasse ist die Klasse PigServer im
Package org.apache.pig: Im Konstruktor wird zunächst der Ausführungsmodus (s.u.)
übergeben. Mit der Methode void registerQuery(String query) wird eine PigLatinAnweisung übergeben, mit der Methode store(.) die Ausführung angestoßen. Einzelheiten
liefert [26].
Neben den drei Eingabemodi kennt Pig zwei Ausführungsmodi. [10] unterscheidet zwischen
dem „local mode“ und dem „mapreduce mode“.
Im lokalen Ausführungsmodus läuft Pig in einer einzigen JVM und greift auf das lokale Dateisystem zu. Eine Hadoop-Installation wird nicht benötigt. Gestartet wird der lokale Ausführungsmodus durch die Eingabe von
pig -x local [skriptname]
Dieser Ausführungsmodus ist besonders geeignet zum Debuggen von PigLatin-Skripten, Testen
von PigLatin-Anweisungen oder zur Verarbeitung von kleinen Datenmengen.
Im MapReduce-Modus übersetzt Pig Anweisungen in MapReduce-Jobs und übergibt diese einem
Hadoop-System zur Ausführung. Ein Zugriff auf eine Hadoop-Installation ist daher Voraussetzung für diesen Modus: Pig benötigt Referenzen auf den NameNode2 und den JobTracker3 des
Hadoop Clusters. Gesetzt werden können die Referenzen in der Datei pig.properties im confVerzeichnis von Pig: fs.default.name=hdfs://<host>/
mapred.job.tracker=<host>:8021
Gestartet wird der MapReduce-Ausführungsmodus durch die Eingabe von
pig [-x mapreduce] [skriptname]
Falls die Verbindung zu einem Hadoop-Cluster erfolgreich hergestellt werden kann, meldet sich
die Pig-Ausführungsumgebung mit
Connecting to hadoop file system at: hdfs://<host>/
Connecting to map-reduce job tracker at: <host>:8021
2
3
Der NameNode verwaltet das HDFS.
Der JobTracker koordiniert die MapReduce-Jobs.
Big Data Management
Pig
Seite 10
Dieser Ausführungsmodus ist der Produktivmodus zur Verarbeitung großer Datenmengen.
Jeder Ausführungsmodus kann beliebig mit einem Eingabemodus kombiniert werden, typische
Anwendungsfälle sind die interaktive Eingabe im lokalen Modus oder die Skript-Verarbeitung
(Batch-Eingabe) im MapReduce-Modus.
2.5 Verarbeitung von PigLatin-Anweisungen
Die Verarbeitung von PigLatin-Anweisungen durch die Pig-Ausführungsumgebung ist gekennzeichnet durch einen „lazy style of execution“ [08]: Der Pig-Parser nimmt zunächst jede Anweisung entgegen, parst sie und prüft, ob die referenzierten Datenstrukturen gültig sind. Ist dies der
Fall, wird die Anweisung zu einem logischen Plan hinzugefügt [09]. Anschließend werden ggf.
Optimierungen (z.B. der Ausführungsreihenfolge, s. Kap. 3.4.5) vorgenommen. Die Ausführung
des logischen Plans wird erst dann angestoßen, wenn Pig ein STORE- oder DUMP-Kommando
als Eingabe erhält. In diesem Fall wird aus dem logischen Plan ein physischer Plan generiert
und dieser anschließend in einen MapReduce-Plan compiliert: Pig generiert danach das jarArchiv mit den benötigten Map- und Reduce-Klassen und übergibt es Hadoop zur Ausführung
[09].
Abbildung 4: aus [09]: Ausführungsphasen in Pig
Die folgenden Kapitel beschreiben die verschiedenen Pläne, die bei der Ausführung des oben
beschriebenen PigLatin-Skripts „fangbuch.pig“ entstehen.
Big Data Management
Pig
Seite 11
2.5.1 Logischer Plan
Der logische Plan sieht zunächst das Laden einer Relation aus einer Datei vor (LOLoad).
Jeder gelesene Satz (LOForEach) der Datei besteht aus sechs Datenfeldern, für die eine Typumwandlung (Cast) zum im Schema angegebenen Typ erfolgen muss.
Anschließend wird eine neue Relation durch Gruppierung nach dem 4. Datenfeld erstellt
(LOCogroup).
Für jedes Tupel dieser Relation (LOForEach) wird dann aus Feld 1 ein Feld dereferenziert. Auf
das Ergebnis wird eine Funktion (UserFunc) angewendet.
Das Ergebnis der Funktion wird mit dem Wert aus Feld 0 gespeichert (LOStore).
Abbildung 5: Logischer Plan
Big Data Management
Pig
Seite 12
2.5.2 Physischer Plan
Die Erstellung des physischen Plans wird von der Pig-Ausführungsumgebung angestoßen,
sobald Pig als Eingabe ein STORE- oder DUMP-Kommando erreicht. Die wichtigsten
Unterschiede zum logischen Plan sind
•
die Relationen werden mit ihren Aliasen benannt,
•
die konkreten Pfade zu den Eingabedateien und zum Ausgabeverzeichnis werden
eingesetzt,
•
die zu verwendenden Speicher- und Ladefunktionen werden ermittelt (im Beispiel: Builtin Funktion PigStorage).
Die wichtigste Änderung ist aber, dass die LOCogroup-Anweisung durch drei Phasen ersetzt
wird [11] und damit die Voraussetzung geschaffen wird für die spätere Aufteilung in eine Mapund Reduce-Phase:
•
Local Rearrange-Phase:
Sie repräsentiert das lokale Vorbereiten der (key, value)-Paare am Ende der Map-Phase
vor der Shuffle-Phase.
•
Global Rearrange-Phase:
Sie ist ein Platzhalter für die Shuffle-Phase.
•
Package-Phase:
Sammelt und packt die values zu Beginn der Reduce-Phase.
Abbildung 6: Physischer Plan
Big Data Management
Pig
Seite 13
2.5.3 MapReduce-Plan
Nach der Erstellung des physischen Plans wird der MapReduce-Plan generiert. In einem ersten
Schritt wird analysiert, wie eine Aufteilung in Map- und Reduce-Phasen erfolgen soll: Jedes
LocalRearrange kennzeichnet das Ende einer Map-Phase, Package den Beginn einer neuen
Reduce-Phase. GlobalRearrange-Phasen entfallen. Im konkreten Beispiel führt dies zu einer
Map- und einer Reduce-Phase.
Anschließend prüft Pig, ob Optimierungen, z.B. durch Einfügen einer Combine-Phase oder
durch Nutzen des Sortierens in der Shuffle-Phase von Hadoop vorgenommen werden können
[11]. Im Beispiel wird eine Combine-Phase zusätzlich implementiert: Pig erkennt, dass die
verwendete MAX-Funktion algebraisch ist (s. Kap. 3.5.2) und daher bereits am Ende der MapPhase und in einer Combine-Phase angewendet werden kann.
Abbildung 7: MapReduce-Plan
Big Data Management
Pig
Seite 14
3 Die Sprache PigLatin
3.1 Grundlagen
PigLatin ist eine Datenflusssprache. Jede Anweisung verarbeitet eine Relation und liefert als
Ergebnis eine neue Relation. Eine Anweisung wird beendet mit einem Semikolon. Ein Beispiel:
fang = load 'fangbuch1' as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
'fang' ist der Name der Relation, die sich aus dem Laden der Datei 'fangbuch1' ergibt. 'fang' wird
auch als Alias bezeichnet. Aliase können ebenfalls vergeben werden für die Bezeichnung von
Feldern: 'datum', 'temperatur', 'luftdruck', 'gewaesser', 'fischart' und 'laenge' sind Aliase für Felder
in einem Tupel der Relation 'fang'.
Die Sprache kennt reservierte Schlüsselwörter, die nicht als Bezeichner für Relationen oder
Felder verwendet werden dürfen. Im obigen Beispiel sind load, as, chararray und int
reservierte Schlüsselwörter. Eine vollständige Liste der Schlüsselwörter liefert [23].
Die Namen von Relationen und Felder sowie PigLatin-Funktionsbezeichner sind unter
Beachtung der Groß-/Kleinschreibung zu verwenden. Dies gilt allerdings nicht für PigLatinOperatoren. So bezeichnen 'fang' und 'FANG' zwei verschiedene Relationen, 'load' und 'LOAD'
allerdings den gleichen relationalen Operator.
Kommentare können in PigLatin mit -- (einzeiliger Kommentar) oder mit /* */ (mehrzeilige
Kommentare) angegeben werden.
PigLatin-Skripte bestehen aus einer Folge von Anweisungen. Anweisungen zur Steuerung des
Kontrollflusses sind nicht vorhanden – falls erforderlich, kann Pig dazu im ″embedded mode″
ausgeführt werden (s. Kap. 2.4).
Die Behandlung von null-Werten erfolgt in Pig analog zu SQL [23]. Null-Werte können z.B. auftreten als Ergebnis von Operatoren (z.B. Outer Join, s. Kap. 3.4.9) oder als Ergebnis des Imports
inkonsistenter Daten (z.B. LOAD, s. Kap. 3.4.1).
3.2 Datentypen und Ausdrücke
Pig unterscheidet zwischen einfachen und komplexen Datentypen. Die einfachen Typen entsprechen den bekannten Datentypen aus diversen Programmiersprachen und werden in den
Parameter-Deklarationen der Interfaces des Pig-Frameworks durch java.lang-Klassen abgebildet.
[11] beschreibt folgende sechs einfache Datentypen:
int
Ganze Zahl, 4 Byte
java.lang.Integer
12
long
Ganze Zahl, 8 Byte
java.lang.Long
500L
float
Gleitkommazahl, 4 Byte java.lang.Float
3.14f
double
Gleitkommazahl, 8 Byte java.lang.Double
3.14
chararray Zeichenkette
java.lang.String
'pig'
bytearray Byte-Feld
org.apache.pig.data.DataByteArray kapselt ein Java byte[]
Als komplexe Datentypen kennt Pig
• Tuple
Ein Tupel besteht aus einer festen Anzahl von Feldern. Jedes Feld enthält ein
Big Data Management
•
•
Pig
Seite 15
Datenelement und kann von einem beliebigen Typ sein. Ein Tupel ist vergleichbar mit
einer Zeile einer Tabelle oder einem Satz in einer Datei.
Einem Tupel kann ein Schema zugewiesen werden, welches für jedes Feld des Tupels
den Typ und einen Namen angibt. Wird kein Schema angegeben, so nimmt Pig als
Feldtyp bytearray an. Tupel-Konstanten werden in ()-Klammern angegeben, z.B. ('Hecht',
55).
Bag
Ein Bag ist eine ungeordnete Menge von Tupeln. Sollte ein Schema assoziert werden, so
beschreibt das Schema alle Tupel im Bag. Ein Bag ist vergleichbar mit einer Tabelle oder
einer Datei. Bag-Konstanten werden in {}-Klammern angegeben, z.B. {('Hecht', 55),
('Barsch', 32)}.
Map
Ein Map ist ein key-value-Typ in Pig. Der key muss vom Typ chararray sein, der Typ des
values ist beliebig. Map-Konstanten werden in []-Klammern angegeben, z.B.
['name'#'Hecht', 'laenge'#94].
Das Datenmodell von Pig besitzt eine gewisse Analogie zum Datenmodell relationaler DBMS –
so lässt sich ein Bag mit einer Tabelle vergleichen und ein Tuple mit einer Zeile einer Tabelle.
Die extreme Flexibilität und insbesondere die beliebig zulässigen Verschachtelungen gehen aber
weit über das hinaus, was in normalisierten Relationenschemata relationaler DB zulässig ist.
Ausdrücke werden in PigLatin in Zusammenhang mit verschiedenen relationalen Operatoren
und Funktionen verwendet. [08] zeigt die verschiedenen Ausdruckstypen und ihre Funktionsweise:
Abbildung 8: Ausdrücke in Pig (aus [08])
3.3 Diagnose-Operatoren
Pig stellt einige Diagnose-Operatoren zur Verfügung, die Entwickler bei der Erstellung von PigSkripten unterstützen sollen.
3.3.1 DUMP
Syntax: DUMP alias;
Big Data Management
Pig
Seite 16
Mit der DUMP-Anweisung kann eine Relation auf der Standardausgabe – in der Regel also auf
dem Bildschirm ausgegeben werden. Verwendung findet dieser Operator vor allem beim
Debugging und schnellen Prototyping. Zu beachten ist, dass jedes Tupel der auszugebenden
Relation mit () umgeben wird.
Ein Beispiel: s. Kap. 3.4.5
3.3.2 DESCRIBE
Syntax: DESCRIBE alias;
Der DESCRIBE-Operator gibt das Schema einer Relation aus.
Ein Beispiel: s. Kap. 3.4.7
3.3.3 ILLUSTRATE
Syntax: ILLUSTRATE {alias|­script scriptfile};
Mit dem ILLUSTRATE-Operator kann die Funktionsweise eines Skriptes oder das Bilden einer
Relation schrittweise nachvollzogen werden: Pig beschreibt jede Relation mit Hilfe von Beispieldaten, die ein Beispieldaten-Generator aus den Eingabedaten extrahiert oder selber generiert.
Ein Beispiel: ILLUSTRATE fangbuch.pig;
Ausgabe:
---------------------------------------------------------------------------------------------------------------------------------------| fang
| datum:chararray
| temperatur:int
| luftdruck:int
| gewaesser:chararray
| fischart:chararray
| laenge:int
|
---------------------------------------------------------------------------------------------------------------------------------------|
| 20.07.2011
| 22
| 1010
| Lippe
| Doebel
| 27
|
|
| 25.09.2009
| 18
| 1025
| Bocholter Aa
| Doebel
| 30
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| fisch
| group:chararray
|
fang:bag{:tuple(datum:chararray,temperatur:int,luftdruck:int,gewaesser:chararray,fischart:charar
ray,laenge:int)}
|
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Doebel
| {(20.07.2011, ..., 27), (25.09.2009, ..., 30)}
|
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| pbm
| group:chararray
| :int
|
-----------------------------------------|
| Doebel
| 30
|
------------------------------------------------------------------------------------------| Store : pbm
| group:chararray
| :int
|
-------------------------------------------------|
| Doebel
| 30
|
--------------------------------------------------
3.3.4 EXPLAIN
Syntax: EXPLAIN [­script skriptdatei] [­dot] [­out dateiname] [­
brief] [alias]
Big Data Management
Pig
Seite 17
Der EXPLAIN-Operator erklärt, wie Pig ein Skript bzw. eine Relation in einen MapReduce-Job
für Hadoop übersetzt. Er erstellt dazu Graphen im Textformat bzw. im DOT-Format4 (Option
-dot) für den logischen Plan, physischen Plan und MapReduce-Plan.
Defaultmäßig erfolgt die Ausgabe auf der Standardausgabe (Bildschirm), mit -out kann sie in
eine Datei umgeleitet werden. Die Option -brief erstellt einen kleineren, kompakteren Graphen.
3.4 Relationale Operatoren
Die Datenverarbeitung erfolgt in Pig mit Hilfe von relationalen Operatoren. Pig kennt in der aktuellen Version 0.11.1 20 Operatoren, eine Auswahl wird in den folgenden Kapiteln vorgestellt.
[23] enthält eine vollständige Liste relationaler Operatoren inklusive aller zulässigen Klauseln
und optionalen Parametern.
3.4.1 LOAD
Syntax: alias = LOAD 'eingabedatei' [USING function] [AS schema];
Mit dem LOAD-Operator werden die Eingabedaten spezifiziert. Per default wird die
Eingabedatei im Benutzer-Verzeichnis des HDFS (/users/login) oder im aktuellen lokalen
Verzeichnis gesucht. Aber auch die Angabe eines relativen Pfades oder einer vollständigen URL
sind zulässig.
Mit der optionalen USING-Klausel kann eine spezielle Funktion zum Laden der Daten angegeben werden (z.B. using HbaseStorage() zum Lesen aus HBase). Wird sie nicht verwendet, wird
die Built-in Funktion PigStorage als default verwendet: Diese liest eine Textdatei, deren Felder
durch Tabulatoren getrennt sind. Die Behandlung anderer Trennzeichen ist durch die Angabe als
Parameter von PigStorage möglich, z.B. USING PigStorage(';').
Die optionale AS-Klausel ermöglicht die Angabe eines Schemas: Name und Typ der Felder der
Datei können angegeben werden. Entspricht der Typ des tatsächlich vorgefundenen, zu importierenden Datums nicht dem spezifizierten Typ und ist eine Typumwandlung nicht möglich (z.B.
Zeichenkette statt int), so wird der Datensatz dennoch als Tupel importiert, allerdings mit einem
null-Wert im beteffenden Feld [10]. Wird keine AS-Klausel verwendet, kann die Dereferenzierung der Felder später über ihre Position mit dem dot-Operator (z.B. alias.$3) erfolgen. Als
Typ wird in diesem Fall immer bytearray angenommen.
Das Ergebnis des LOAD-Operators ist eine Referenz auf eine Bag, die eine Menge von Tupeln
enthält und damit logisch die Datei repräsentiert. Die Referenz wird mit dem angegebenen Alias
verknüpft. Dieser dient in späteren Verarbeitungsschritten als Eingabe für weitere relationale
Operatoren.
Ein Beispiel: s. Kap. 3.4.5
3.4.2 STORE
Syntax: STORE alias INTO 'verzeichnis' [USING function];
Der STORE-Operator dient zum Speichern von Daten. Wie beim LOAD-Operator ist beim
STORE-Operator das Speichern im Benutzer-Verzeichnis des HDFS, im lokalen
Dateiverzeichnis, unter einem relativen Pfad oder einer URL möglich.
Spezielle Funktionen zum Speichern von Daten können mit der optionalen USING-Klausel
4
DOT ist ein Textformat zur Beschreibung von Graphen [24]. Eine Visualisierung kann mit Graphviz [25]
erfolgen. Alle Graphen-Abbildungen der Kap. 2.5.1, 2.5.2, 2.5.3 und 3.5.2 wurden mit EXPLAIN im DOTFormat erstellt.
Big Data Management
Pig
Seite 18
angegeben werden. Wird diese nicht angegeben, so speichert Pig per default mit der PigStorageFunktion in einer Tabulator-getrennten Textdatei – wie bei LOAD können andere Trennzeichen
als Parameter übergeben werden.
Beim Speichern in ein Dateisystem ist zu beachten, das Hadoop lediglich ein Verzeichnis anlegt,
in dem die Ergebnisdateien als nummerierte Teile abgelegt werden. Die Anzahl der Ergebnisdateien hängt vom Grad der Parallelität, d.h. im wesentlichen von der Anzahl der Reduce-Task
der Hadoop-Verarbeitung ab. Im lokalen Ausführungsmodus wird aufgrund der seriellen Verarbeitung immer nur eine Ergebnisdatei erstellt.
Ein Beispiel:
store pbm into 'output';
3.4.3 ORDER BY
Syntax: alias = ORDER alias BY field_alias [ASC|DESC], ...;
Der ORDER-Operator sortiert die Tupel einer Relation nach einem Schlüssel (Feldbezeichner
oder Position). Die Sortierung nach mehreren Schlüsseln ist möglich. Standardmäßig wird
aufsteigend sortiert, die Angabe von DESC nach einem Schlüssel führt zu einer absteigenden
Sortierung.
Ein Beispiel: s. Kap. 3.4.5, 3.4.6
3.4.4 DISTINCT
Syntax: alias = DISTINCT alias;
Der DISTINCT-Operator entfernt aus einer Relation identische Tupel. Da die Sätze von der
MapReduce-Anwendung erst gesammelt werden müssen, um sie vergleichen zu können,
erzwingt die Angabe eines DISTINCT eine Reduce-Phase.
Ein Beispiel: s. Kap. 3.4.8
3.4.5 FILTER
Syntax: alias = FILTER alias BY expression;
Der FILTER-Operator ermöglicht es, aus einer Relation die Tupel auszuwählen, die für eine
weitere Verarbeitung zur Verfügung stehen sollen: Tupel, für die der Filter-Ausdruck zu 'true'
ausgewertet wird, werden in die Ergebnisrelation übernommen. Pig kennt die üblichen
Vergleichsoperatoren wie ==, !=, <, >, <=, >=, EQ oder NEQ sowie die booleschen Operatoren
AND, OR und NOT.
Ein Beispiel:
Big Data Management
Pig
Seite 19
-- Skript lippe.pig
-- Ausgabe aller in der Lippe gefangener Fische,
–- absteigend sortiert nach Größe
fang = LOAD 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
sortiert = ORDER fang BY laenge DESC;
lippe = FILTER sortiert BY gewaesser eq 'Lippe';
STORE lippe INTO 'lippe';
Ergebnis:
20.07.2011
22
1010
Lippe Doebel
27
Dieses Beispiel zeigt gleichzeitig, wie der logische Optimierer in Pig arbeitet: Die Ausführung
von FILTER-Operatoren wird im logischen Plan so weit wie möglich nach oben verschoben [11].
Abbildung 9: Logischer Plan
des Skripts 'lippe.pig'
3.4.6 LIMIT
Syntax: alias = LIMIT alias n;
Mit der LIMIT-Anweisung kann eine Menge auf eine maximal zulässige Anzahl n von Tupeln
beschränkt werden.
Big Data Management
Pig
Seite 20
Ein Beispiel:
-- Skript Top3.pig
-- Ermittelt die 3 größten Hechte
fang = LOAD 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
hechte75 = FILTER fang BY fischart EQ 'Hecht' AND laenge >= 75;
hechte75sortiert = ORDER hechte75 BY laenge DESC;
top3Hechte = LIMIT hechte75sortiert 3;
DUMP top3Hechte;
Ausgabe:
(14.11.2009,14,1005,Goersee,Hecht,94)
(17.10.2009,11,1020,Bocholter Aa,Hecht,86)
(25.10.2008,12,1025,Bocholter Aa,Hecht,79)
3.4.7 GROUP
Syntax: alias = GROUP alias {ALL|BY expression};
Der GROUP-Operator ermittelt alle Tupel einer Relation mit dem gleichen Schlüssel und nimmt
sie in eine neue Bag auf. Jedes Tupel der Ergebnisrelation enthält zwei Felder: Schlüssel und
Bag. Das Schlüsselfeld trägt die Bezeichnung 'group', die Bag den gleichen Namen wie die
Relation, die gruppiert wurde.
Ein Beispiel:
-- Skript fisch.pig
-- Ermittelt zu jeder Fischart alle Fänge
fang = LOAD 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fisch = group fang by fischart;
DESCRIBE fisch;
Ausgabe:
fisch: {group: chararray,fang: {(datum: chararray,temperatur: int,luftdruck:
int,gewaesser: chararray,fischart: chararray,laenge: int)}}
Die Relation fisch ist vom Typ Bag und enthält Tupel mit zwei Feldern: fisch.$0 ist vom Typ
chararray und hat den Bezeichner 'group'. fisch.$1 ist vom Typ Bag und hat den Bezeichner
'fang'.
Mit der GROUP ALL-Anweisungen werden alle Tupel in einer Bag zusammengefasst.
Ein Beispiel: s. Kap. 3.4.8
3.4.8 FOREACH und FLATTEN
Syntax: alias = FOREACH alias GENERATE expression;
Die FOREACH-Anweisung ist sicherlich eine der zentralen Operatoren in PigLatin. Sie
Big Data Management
Pig
Seite 21
ermöglicht es, Ausdrücke auf jeden Tupel einer Relation anzuwenden und ein neues Tupel als
Ergebnis zu generieren.
Ein Beispiel:
-- Skript anzahl.pig
-- Bestimmt die Anzahl der gefangenen Fische
fang = LOAD 'fangbuch1';
alle = GROUP fang ALL;
anzahl = FOREACH alle GENERATE 'anzahl', COUNT(alle.$1);
Die Relation 'alle' enthält genau ein Tupel: Ein Feld mit dem Alias 'group', welches das
Gruppierwort enthält (in diesem Fall die Zeichenkette 'all') und eine Bag mit dem Alias 'fang',
die sämtliche Tupel der Relation 'fang' enthält.
describe alle;
alle: {group: chararray, fang: {()}}
Ausgabe:
dump anzahl;
(anzahl,140)
Der FLATTEN-Operator wird in Zusammenhang mit dem FOREACH-Operator angewandt, um
eine Verschachtelungsebene aufzulösen: FLATTEN angewandt auf einen Tupel löst diesen Tupel
auf und macht aus jedem Feld des Tupels ein „top-level“ Feld [11].
Beispiel aus [23]: Gegeben sei ein Tupel t = (a, (b, c)).
GENERATE $0, FLATTEN($1) liefert ein Tupel (a, b, c).
FLATTEN angewandt auf eine Bag führt zur Bildung eines Kreuzproduktes zwischen jedem
Tupel in der Bag und den anderen Ausdrücken im GENERATE-Operator [11].
Beispiel aus [23]: Gegeben sei ein Tupel t = (a, {(b,c), (d,e)}).
GENERATE $0, FLATTEN($1) führt zu den Ergebnistupeln (a, b, c) und (a, d, e).
Wird FLATTEN auf eine leere Bag angewandt, so wird kein Ergebnistupel generiert.
Mit Hilfe einer geschachtelten FOREACH-Anweisung (Nested foreach) ist es möglich, mehrere
relationale Operatoren oder Ausdrücke auf jedes Tupel einer Relation anzuwenden.
Syntax: alias = FOREACH alias {nested_block};
Die Operatoren und Ausdrücke sind innerhalb geschweifter Klammern {} anzugeben, die letzte
Zeile vor der schließenden Klammer muss die GENERATE-Anweisung enthalten. Innerhalb der
geschweiften Klammern sind als Operatoren nur DISTINCT, FILTER, LIMIT und ORDER
zulässig.
Ein Beispiel:
Big Data Management
Pig
Seite 22
-- Skript tipp.pig
-- Bestimmt für jede Fischart die optimalen Fanggewässer
fang = load 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fischarten = group fang by fischart;
tipp = foreach fischarten {
alleGewaesser = fang.gewaesser;
gewaesser = distinct alleGewaesser;
generate group, gewaesser;
};
dump tipp;
Die Relation 'tipp' wird also wie folgt gebildet: Für jedes Tupel in der Relation 'fischarten'
werden zunächst alle Gewässer in einer Bag zusammengefasst. Anschließend werden die
mehrfach vorhandenen Gewässer entfernt und ein neues Tupel aus dem Feld 'group' (enthält die
Fischart) und der Bag 'gewaesser' gebildet.
Ausgabe:
(Aland,{(Bocholter Aa)})
(Barsch,{(Aa-Strang),(Bocholter Aa),(Goersee),(Rhein),(Unterbacher See)})
(Doebel,{(Bocholter Aa),(Lippe)})
...
3.4.9 JOIN
Syntax: alias = JOIN alias BY expression [LEFT|RIGHT|FULL], alias BY expression , ...[USING 'replicated'|'skewed'|'merge'];
Der JOIN-Operator verknüpft Tupel mindestens zweier Eingaberelationen mit Hilfe eines
Schlüssels: Realisiert ist lediglich der Equi Join, d.h. zwei Tupel werden verknüpft und in die
Ergebnisrelation übernommen (Inner Join), wenn die Schlüsselwerte identisch sind. Das
Ergebnistupel enthält alle Datenfelder beider Relationen.
Es werden Outer Joins unterstützt, d.h. abhängig von den Schlüsselwörtern LEFT, RIGHT und
FULL werden Tupel der linken, der rechten bzw. beider Relationen auch dann in die Ergebnisrelation übernommen, wenn in der jeweils korrespondierenden Relation kein übereinstimmender
Schlüssel vorhanden ist. Voraussetzung ist, dass Pig das Schema der korrespondierenden
Relation kennt um die erforderlichen null-Werte generieren zu können. Bei einem Inner Join
können eine beliebige Anzahl von Relationen verknüpft werden, bei einem Outer Join lediglich
zwei.
Abhängig von der Struktur der zu verarbeitenden Daten können mit Hilfe der USING-Klausel
verschiedene JOIN-Implementierungen genutzt werden. Abweichend von relationalen DBMS,
bei denen der Optimierer die zu nutzende Join-Implementierung bestimmt, ist hier der Benutzer
gefragt: „In the Pig team we like to say that our optimizer is located between the user's chair and
keyboard.“ [11]. Es stehen unter anderem zur Verfügung:
•
fragment-replicate join
Ein „fragment-replicate join“ sollte dann eingesetzt werden, wenn „kleine“ Datenmengen
mit umfangreichen Relationen verknüpft werden sollen: In diesen Fall wird die
„kleinere“ Relation in den „distributed cache“ der Hadoop-MapReduce-Anwendung
geladen und steht somit allen Map-Tasks unmittelbar zur Verfügung – eine Reduce-Phase
Big Data Management
Pig
Seite 23
ist nicht mehr erforderlich!
•
skew join
Der „skew join“ ermöglicht eine bessere Auslastung von Reduce-Tasks auch in den
Fällen, in denen die Werte der Schlüsselfelder in den zu verknüpfenden Relationen
äußerst ungleich verteilt sind.
•
merge join
Der „merge join“ findet Anwendung, wenn die Tupel der zu verknüpfenden Relationen
bereits sortiert sind. Auch diese JOIN-Implementierung kommt ohne Reduce-Phase aus.
Ein Beispiel:
Neben der Datei 'fangbuch1' sei eine Datei 'mindestmasse' gegeben, die zu jeder Fischart das
zulässige Mindestmaß für den Fang enthält.
-- Skript mindestmass.pig
-- Gibt zu jeder gefangenen Fischart das Mindestmaß an
fang = load 'fangbuch1'as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fischarten = foreach fang generate fischart;
gefangen = distinct fischarten;
mindestmasse_nrw = load 'mindestmasse' as (fischart:chararray,
laenge:chararray);
mindestmasse = join gefangen by $0 left, mindestmasse_nrw by fischart using
'replicated';
ergebnis = foreach mindestmasse generate $0, (($1 is null)?'kein
Mindestmass':$2);
dump ergebnis;
Ausgabe:
(Aland,25)
(Hecht,45)
(Barsch,kein Mindestmass)
...
3.4.10COGROUP
Syntax: alias = COGROUP alias {ALL|BY expression}, alias {ALL|BY expression}, ...; Wie der JOIN-Operator ermöglicht auch der COGROUP-Operator die Verknüpfung von mehreren Eingaberelationen mit Hilfe eines Schlüssels: Alle Tupel einer Eingaberelation, die im
angegebenen Schlüssel übereinstimmen, werden in einer Bag gesammelt. Das Gruppierungsfeld
(der Schlüssel) und die so erstellten (ggf. auch leeren) Bags einer jeden Eingaberelation bilden
ein neues Tupel in der Ergebnisrelation.
Folgendes Beispiel aus [11] beschreibt das Vorgehen:
Big Data Management
Pig
Seite 24
-- Skript cogroup.pig
A = load 'input1' as (id:int, val:float);
B = load 'input2' as (id:int, val2:int);
C = cogroup A by id, b by id;
describe C;
Ausgabe:
C: {group:int, A: {id:int, val:float}, B: {id:int, val2:int);
Die COGROUP-Anweisung wird auch als „first half of a JOIN“ [11] bezeichnet: Eine JOINAnweisung lässt sich als COGROUP-Anweisung mit nachfolgender FOREACH-Anweisung
inkl. FLATTEN-Operator darstellen [08, 11].
Ein Beispiel: Es wird das Skript aus Kap. 3.4.9 verwendet – anstelle des JOIN-Operators wird
allerdings der COGROUP-Operator mit einer anschließender FOREACH- und FLATTENOperation eingesetzt:
-- Skript mindestmass2.pig
-- Gibt zu jeder gefangenen Fischart das Mindestmaß an
fang = load 'fangbuch1' as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fischarten = foreach fang generate fischart;
gefangen = distinct fischarten;
mindestmasse_nrw = load 'mindestmasse' as (fischart:chararray,
laenge:chararray);
mindestmasse = cogroup gefangen by $0, mindestmasse_nrw by fischart;
mindestmasse_notEmpty = foreach mindestmasse generate group, $1,
((IsEmpty($2))?{('kein Mindestmass')}:$2.$1);
ergebnis = foreach mindestmasse_notEmpty generate flatten($1), flatten($2);
dump ergebnis;
Ausgabe:
(Aland,25)
(Hecht,45)
(Barsch,kein Mindestmass)
...
3.5 Funktionen
In den vorherigen Kapiteln wurden bereits im Rahmen von Ausdrücken verschiedene Funktionen
wie COUNT, MAX, SUM oder PigStorage verwendet. Dabei handelt es sich um in Pig
integrierte Built In-Funktionen (s. Kap. 3.5.1). Darüber hinaus können vom Benutzer eigene
Funktionen, die User Defined Functions (s. Kap. 3.5.2), eingebunden werden.
3.5.1 Built In-Funktionen
Pig unterstützt in der aktuellen Version über 70 verschiedene Built In-Funktionen in den
Kategorien Eval Functions, Load/Store Functions, Math Functions, String Functions, Datetime
Big Data Management
Pig
Seite 25
Functions und Tuple, Bag, Map Functions [21]. Im Unterschied zu den User Defined Functions
(s. Kap. 3.5.2) ist weder eine Registrierung noch eine Qualifizierung beim Aufruf erforderlich.
Abweichend von der Verwendung der relationalen Operatoren ist aber die
Groß-/Kleinschreibung beim Funktionsaufruf zu beachten.
Folgendes Beispiel nutzt zwei Datumsfunktionen und eine Evaluierungsfunktion:
-- Skript top3Monate.pig
-- Ermittelt die 3 besten Fangmonate im Jahr
fang = load 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
monate = group fang by GetMonth(ToDate(datum, 'dd.MM.yyyy'));
anzImMonat = foreach monate generate group, COUNT(fang.$1);
sortiert = order anzImMonat by $1 desc;
top3Monate = limit sortiert 3;
dump top3Monate;
Ausgabe:
(9,44)
(8,31)
(6,18)
3.5.2 User Defined Functions
Neben der Verwendung von Built In-Funktionen können auch benutzerdefinierte Funktionen
(User Defined Function, UDF) implementiert und in PigLatin verwendet werden. Obwohl auch
eine Implementierung in anderen Programmiersprachen (z.B. Python, JavaScript u.a.) möglich
ist, empfiehlt [22] die Entwicklung in Java. Die Implementierung ist sorgfältig vorzunehmen:
UDFs werden parallel im Hadoop-Cluster auf vielen Rechnern ausgeführt – kostspielige
Operationen wie z.B. das Anmelden an Datenbanken sollten daher vermieden werden [11].
Um UDFs in PigLatin-Skripten verwenden zu können, müssen diese mit dem Schlüsselwort
REGISTER registriert werden:
Syntax: REGISTER path;
path bezeichnet dabei den Pfad zu dem jar-Archiv, dass die UDF enthält.
Optional kann die UDF auch mit einem Alias bezeichnet werden:
Syntax: DEFINE alias function;
Im PigLatin-Skript ist nun ein Aufruf der UDF mit dem Alias möglich.
Piggy Bank [22] ist ein Repository, in dem Pig-Benutzer ihre entwickelten Java-UDFs zur Veröffentlichung bereitstellen und austauschen können.
Abschließend wird die Implementierung von UDFs in Java näher betrachtet - [22] unterscheidet
zwischen Evaluierungsfunktionen und Funktionen zum Laden und Speichern. Pig übernimmt in
beiden Fällen die Rolle eines Java Black-Box-Frameworks: UDFs werden als Subtypen
abstrakter Pig-Klassen aus dem Paket org.apache.pig implementiert.
Evaluierungsfunktionen
Zentrale Klasse der Klassenhierarchie für Evaluierungsfunktionen ist die abstrakte Klasse
Big Data Management
Pig
Seite 26
EvalFunc<T>.
Sie enthält die abstrakte Callback-Methode T exec(input Tuple), die in den
konkreten Unterklassen zu implementieren ist.
Abbildung 10: Klassenhierarchie Evaluierungsfunktionen
Einfache Evaluierungsfunktionen werden als Subklassen der abstrakten Klasse EvalFunc<T>
implementiert. Bei der Subklassenbildung wird der Typparameter T durch den gewünschten Typ
ersetzt – dieser ist gleichzeitig der Typ des Rückgabewertes der exec-Methode. Die execMethode wird von Pig mit jeweils einem Tupel als Übergabewert aufgerufen. In PigLatinSkripten können diese Evaluierungsfunktionen z.B. in FOREACH-Statements eingesetzt werden.
Eine Aggregatfunktionen erhält als Eingabe eine Bag und liefert als Rückgabe einen skalaren
Wert. Typische Beispiele sind MAX oder COUNT. Die Implementierung erfolgt zunächst
ebenfalls als Subtyp von EvalFunc<T> - auch die exec-Methode ist wie bei den einfachen
Evaluierungsmethoden zu implementieren.
Big Data Management
Pig
Seite 27
Als Beispiel wird eine eigene MAX-Funktion zur Bestimmung des Maximums einer Menge von
Integer-Werten implementiert:
package myudfs;
import java.util.Iterator;
import org.apache.pig.EvalFunc;
import org.apache.pig.backend.executionengine.ExecException;
import org.apache.pig.data.DataBag;
import org.apache.pig.data.Tuple;
/**
* Eigene MAX-Funktion zur Bestimmung des Maximums einer Menge.
* Ohne Typprüfung, null-Prüfung, Fehlerbehandlung
*/
public class MAX extends EvalFunc<Integer> {
@Override
public Integer exec(Tuple input) throws ExecException {
DataBag values = (DataBag)input.get(0);
int max = Integer.MIN_VALUE;
Iterator<Tuple> it = values.iterator();
while (it.hasNext()) {
Tuple t = it.next();
Integer i = (Integer)t.get(0);
max = java.lang.Math.max(max, i);
}
return max;
}
}
Die Klasse MAX wird compiliert, in ein jar-Archiv gepackt und im PigLatin-Skript
'fangbuch2.pig' verwendet:
–- Skript fangbuch2.pig
–- Ermittelt die größten gefangenen Fische jeder Fischart
-– Nutzt eine UDF
register MAX.jar;
define MYMAX myudfs.MAX();
fang = load 'fangbuch1'
as (datum:chararray, temperatur:int, luftdruck:int,
gewaesser:chararray, fischart:chararray, laenge:int);
fisch = group fang by fischart;
pbm = foreach fisch generate group, MYMAX(fang.laenge);
store pbm into 'output';
Bei der Ausführung erstellt Pig folgenden MapReduce-Plan:
Big Data Management
Pig
Seite 28
Abbildung 11: MapReduce-Plan bei Verwendung der
UDF "MYMAX"
Vergleicht man ihn mit dem MapReduce-Plan, der bei Nutzung der Built In-Funktion MAX entsteht (s. Kap. 2.5.3), so fällt auf, dass für MYMAX die Combine-Phase nicht genutzt wird, der
Hadoop-Job also mit einer deutlich schlechteren Performance ablaufen wird.
Um die Combine-Phase nutzen zu können, muss das Interface Algebraic implementiert werden
– dies sollte allerdings nur geschehen, falls es sich bei der UDF auch um eine algebraische Funktion5 handelt. Die Implementierung erfolgt in zwei Schritten:
•
Aufnahme statischer innerer Klassen Initial, Intermed und Final als Subklassen von
EvalFunc<T>. Diese Klassen implementieren jeweils eine eigene exec-Methode.
•
Implementierung der Methoden getInitial(), getIntermed() und getFinal(), die
jeweils als String den Klassennamen der korrespondierenden inneren Klasse zurückgeben.
Pig garantiert, dass die
– exec-Methode der Klasse Initial während der Map-Phase,
– die exec- Methode der Klasse Intermed während der Combine-Phase und
– die exec- Methode der Klasse Final während der Reduce-Phase
des Hadoop-Jobs aufgerufen wird [22].
Das Accumulator-Interface ermöglicht die schrittweise Verarbeitung großer Bags: Mit der Methode accumulate (Tuple b) übergibt Pig Teilmengen einer Bag. Die Methode wird von Pig
wiederholt aufgerufen – hier ist die Verarbeitung der Teilmengen und das Speichern von
Zwischenergebnissen zu realisieren. Die Methode T getValue() wird von Pig aufgerufen, wenn
5
Eine algebraische Funktion im Sinne von Pig liegt vor, ″if it can be divided into inital, intermediate, and final
functions […], where the initial function is applied to subsets of the input set, the intermediate function is
applied to results of the initial function, and the final function is applied to all of the results of the intermediate
function.″ [11]
Big Data Management
Pig
Seite 29
alle Teilmengen einer Bag vollständig verarbeitet wurde – Pig ruft hier das Endergebnis ab. Mit
der Methode cleanup() ermöglicht Pig das Aufräumen und Initialisieren vor der Verarbeitung
von Teilmengen der nächsten Bag.
Einzelheiten zur Implementierung der Interfaces Algebraic und Accumulator können [11] und
[22] entnommen werden.
Die von Pig durchführbaren Optimierungen hängen also wesentlich von der Implementierung der
UDF durch den Benutzer ab. Dabei ist sorgfältig vorzugehen, denn nicht jede Aggregatfunktion
ist algebraisch – die Nutzung des Interface Algebraic bei der Ermittlung des Medians einer
Menge wäre z.B. ein Fehler [10].
Eigene Filterfunktionen können als Subklasse von FilterFunc modelliert werden. Die execMethode erhält wieder ein Tupel zu Verarbeitung, der Rückgabewert ist aber immer vom Typ
Boolean. Diese Filterfunktionen können daher in PigLatin-Skripten an allen Stellen, an denen
boolesche Werte verwendbar sind, eingesetzt werden – dies gilt insbesondere natürlich für den
FILTER-Operator.
Funktionen zum Laden und Speichern
Pig ermöglicht die Entwicklung eigener Klassen zum Laden oder Speichern von Daten. Jede
Lade- oder Speicherfunktion ist als Unterklasse der abstrakten Klassen LoadFunc bzw.
StoreFunc zu implementieren. Zentrale Callback-Methoden sind Tuple getNext() bzw. void
putNext(Tuple f), die jeweils ein Tupel-Objekt lesen bzw. speichern.
Abbildung 12: Klassenhierarchie Lade- und Speicherfunktionen
Einzelheiten zur Implementierung und zur Verwendung weiterer optionaler Interfaces können
wieder [11] und [22] entnommen werden.
Big Data Management
Pig
Seite 30
Literaturverzeichnis
[01]
Welcome to Apache Hadoop!, http://hadoop.apache.org/ (02.06.2013, 11:01)
[02]
Oracle: Big-Data-Appliance mit Hadoop-Unterbau | heise open,
http://www.heise.de/open/meldung/Oracle-Big-Data-Appliance-mit-Hadoop-Unterbau1410227.html, (02.06.2013, 11:03)
[03]
Teradata Delivers Hadoop Data to the Enterprise, http://www.teradata.com/NewsReleases/2013/Teradata-Delivers-Hadoop-Data-to-the-Enterprise/, (02.06.2013, 11:05)
[04]
IBM InfoSphere BigInsights, http://www-01.ibm.com/software/data/infosphere/biginsights/,
(02.06.2013, 11:06)
[05]
Welcome to Apache Pig!, http://pig.apache.org/ (02.06.2013, 11:09)
[06]
Born, Achim: Raffinierte Daten, in: iX, 2013, Ausgabe 5, S. 86 bis 93
[07]
Apache Derby, http://db.apache.org/derby/, (02.06.2013, 11:25)
[08]
Christopher Olston, Benjamin Reed, Utkarsh Srivastava, Ravi Kumar, and Andrew
Tomkins. Pig latin: a not-so-foreign language for data processing. In SIGMOD Conference, pages 1099-1110,
2008
[09]
Alan Gates, Olga Natkovich, Shubham Chopra, Pradeep Kamath, Shravan Narayanam, Christopher Olston,
Benjamin Reed, Santhosh Srinivasan, and Utkarsh Srivastava. Building a highlevel data ow system on top of
mapreduce: The pig experience. PVLDB, 2(2):1414-1425, 2009
[10]
White, Tom: Hadoop: The Definitive Guide, O'Reilly 2012
[11]
Gates, Alan: Programming Pig, O'Reilly 2011
[12]
Amazon Elastic MapReduce, http://aws.amazon.com/de/elasticmapreduce/ (02.06.2013, 13:31)
[13]
Welcome to Hive!, http://hive.apache.org/ (02.06.2013, 13:47)
[14]
Big Data – Wikipedia, http://de.wikipedia.org/wiki/Big_Data, (02.06.2013, 13:50)
[15]
Jeffrey Dean and Sanjay Ghemawat. Mapreduce: simplied data processing on large clusters. In Proceedings of
the 6th conference on Symposium on Opearting Systems Design & Implementation - Volume 6 , OSDI'04,
pages 10-10, Berkeley, CA, USA, 2004. USENIX Association
[16]
Apache Pig Philosophy, http://pig.apache.org/philosophy.html, (02.06.2013, 16:37)
[17]
Apache Pig Releases, http://pig.apache.org/releases.html, (02.06.2013, 16:43)
[18]
Cygwin, http://www.cygwin.com/, (02.06.2013, 16:45)
[19]
Pig – The Road to an Efficient High-level language for Hadoop, http://developer.yahoo.com/blogs/hadoop/pigroad-efficient-high-level-language-hadoop-413.html, (02.06.2013, 16:53)
[20]
Process your data with Apache Pig, http://www.ibm.com/developerworks/library/l-apachepigdataquery/,
(02.06.2013, 16:59)
[21]
Built In Functions, http://pig.apache.org/docs/r0.11.1/func.html, (03.06.2013, 08:45)
[22]
User Defined Functions, http://pig.apache.org/docs/r0.11.1/udf.html, (03.06.2013, 13:03)
[23]
Pig Latin Basics, http://pig.apache.org/docs/r0.11.1/basic.html, (04.06.2013, 10:36)
[24]
DOT (graph description language), http://en.wikipedia.org/wiki/DOT_language, (05.06.2013, 14:31)
[25]
Graphviz | Graphviz – Graph Visualisation Software, http://graphviz.org/, (05.06.2013, 14:32)
[26]
Control Structures, http://pig.apache.org/docs/r0.11.1/cont.html, (06.06.2013, 14:00)
[27]
Apache Licence, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0, (06.06.2013, 14:47)
Fakultät für Mathematik und Informatik
Lehrgebiet Datenbanksysteme für neue Anwendungen
Prof. Dr. Ralf Hartmut Güting
Seminar
Big Data Management
Thema 3.1:
Grundlagen von NoSQL-Datenbanken
Verfasser: Steffi Uhl
Datum: 20.06.2013
Inhaltsverzeichnis
1. Begriffserläuterung NoSQL ................................................................................................. 2
2. CAP-Theorem ...................................................................................................................... 3
2.1 Die Begriffe des CAP-Theorems ................................................................................... 3
2.2 Formulierung des Theorems .......................................................................................... 3
2.3 Das große Ganze ............................................................................................................ 5
2.4 Gegenmaßnahmen ......................................................................................................... 5
3. Grundlegende Prinzipien ..................................................................................................... 7
3.1 ACID bei SQL-Datenbanken ......................................................................................... 7
3.2 BASE bei NoSQL-Datenbanken.................................................................................... 8
4. NoSQL-Datenbank Typen ................................................................................................. 10
4.1 Key Value Stores ......................................................................................................... 10
4.2 Spaltenorientiert ........................................................................................................... 10
4.3 Dokumentorientiert ...................................................................................................... 10
4.4 Graph-orientiert ........................................................................................................... 11
5. Modellierung...................................................................................................................... 12
5.1 Konzeptionelle Techniken ........................................................................................... 12
5.2 Generelle Modellierungs-Techniken ........................................................................... 13
5.3 Hierarchische Modellierungs-Techniken ..................................................................... 14
6. Fazit ................................................................................................................................... 15
7. Literaturverzeichnis ........................................................................................................... 16
Abstract
In der Ausarbeitung werden zunächst die verschiedenen Bedeutungen des Begriffes
NoSQL erläutert. Es folgt ein theoretischer Exkurs zum CAP-Theorem, das als direkte
Implikationen die beiden grundlegenden Prinzipien von SQL- und NoSQL-Datenbanken liefert: das ACID- und das BASE-Prinzip.
Das nächste Kapitel ist der Vorstellung der verschiedenen NoSQL-Datenbank-Typen
gewidmet. Es folgen Kapitel über die Modellierung von NoSQL Datenbanken und das
Fazit, in dem auf Stärken und Schwächen von NoSQL eingegangen wird.
1
1. Begriffserläuterung NoSQL
Der Begriff „NoSQL“ oder auch „NOSQL“ wird in vielen Quellen [Wal12, Cat11,
Fow01, BLS+11] erläutert, es gibt jedoch keine einheitliche Definition. Auch der unter
NoSQL verstandene Inhalt kann unterschiedlich definiert sein. Während einige von
Datenbanken oder Datenbanksystemen sprechen, sehen andere in NoSQL eher ein
Konzept oder einen Trend.
Wie Martin Fowler in [Fow12] ausführt, tauchte der Begriff zuerst bei einem Treffen
im Juni 2009 in San Francisco auf, bei dem Vertreter von Voldemort, Cassandra, Dynomite, HBase, Hypertable, CouchDB und MongoDB ihre Produkte in Präsentationen
vorstellten. Er wurde dort ein Sammelbegriff für Datenbanken, die „anders“ sind als
traditionelle, übliche SQL-Datenbanken bzw. proprietäre Produkte.
Oft wird in den o.g. Quellen erwähnt, das NoSQL entweder für „no SQL“, also „kein
SQL“ stehe oder für „not only SQL“ – „nicht nur SQL“. Beides ist sprachlich nicht
stimmig, da auch ein Microsoft SQL Server als „nicht nur SQL“ bezeichnet werden
könnte und die Auslegung als „kein SQL“ eben alle Datenbanken bzw. Datenbanksysteme einschließt, die nicht SQL verwenden – z.B. ältere Produkte wie IMS oder
MUMPS oder auch die frühen Ingres Versionen.
Betrachten wir also NoSQL als nicht eindeutig definierte, aber etablierte Bezeichnung
für Datenbanklösungen, die einige Eigenschaften gemeinsam haben.
Kurzgefasst sind diese Eigenschaften wie in [Wal12] beschrieben der Verzicht auf ein
starres Schema, der Einsatz anderer Protokolle als SQL und eine Verbesserung der,
vor allem horizontalen, Skalierbarkeit.
Martin Fowler verwendet für seine Definition in [Fow12] eine bunte Mischung aus
Eigenschaften, Funktionsumfang, rechtlichen Merkmalen und abstrakten Begriffen.
So fasst er alles als NoSQL auf, das
- weder ein relationales Modell noch SQL benutzt
- Open Source ist
- für den Betrieb auf großen Clustern entwickelt wurde
- auf den Bedürfnissen von Web-Artefakten des 21. Jahrhunderts basiert
- kein Schema besitzt und so das Hinzufügen von Feldern ohne Kontrolle erlaubt.
Rick Cattell dagegen liefert für seinen großen Vergleich von SQL und NoSQL Datenspeichern im Kontext von Data Warehouse-Anwendungen eine Definition für NoSQL,
die sich auf technische Eigenschaften konzentriert. Er listet in [CAT11] folgende sechs
Merkmale auf:
- Die Fähigkeit, einfache Operationen horizontal über viele Server zu skalieren
- Die Fähigkeit, Daten über viele Server zu replizieren und zu verteilen
- Eine einfache Schnittstelle für Aufrufe oder ein einfaches Protokoll (im Gegensatz zu SQL)
- Ein schwächeres Modell für gleichzeitige Zugriffe als die ACID-Transaktion
in traditionellen (SQL) Datenbanksystemen
- Effizienter Gebrauch von verteilten Indizes und Arbeitsspeicher
- Die Fähigkeit, dynamisch neue Attribute zu Datensätzen hinzuzufügen
2
2. CAP-Theorem
Das CAP-Theorem besagt zusammengefasst, dass ein verteiltes System von drei Eigenschaften C = Konsistenz, A = Verfügbarkeit und P = Partitionstoleranz jeweils nur
zwei garantieren kann. Das CAP-Theorem wurde zunächst als Behauptung von Eric
Brewer in seiner Ansprache auf dem PODC-Symposium on Principles of Distributed
Computing am 19. Juli 2000 geäußert [Bre00].
Im Jahr 2002 formulierten Seth Gilbert und Nancy Lynch das Theorem axiomatisch
und bewiesen es [GL02]. Dabei definierten sie zunächst die Begriffe Konsistenz, Verfügbarkeit und Partitionstoleranz und stellten dann ein Theorem auf, das sie per Widerspruch bewiesen.
2.1 Die Begriffe des CAP-Theorems
Als Konsistenz verwenden Gilbert und Lynch den Begriff des atomaren Datenobjekts,
bei dem jede Operation so aussieht, als ob sie in einem Moment passiert wäre (es also
keine „Zwischenzustände“ gibt) bzw. bei einem verteilten Speicher verhalten sich atomare Datenobjekte, als ob sie auf einem einzelnen Knoten gespeichert werden. Die
Definition dieser Konsistenz weicht vom Begriff der Konsistenz, wie er für das ACIDPrinzip verwendet wird, ab.
Als Verfügbarkeit verwenden Gilbert und Lynch analog zu den atomaren Datenobjekten den Begriff „verfügbare Datenobjekte“ und verstehen darunter ein System, das auf
jede Anfrage antwortet. Somit muss ein vom Server genutzter Algorithmus auf jeden
Fall terminieren, wobei die Definition keine Einschränkung macht, wie lange er dazu
benötigen darf.
Schließlich definieren sie Partitionstoleranz damit, dass Nachrichten in einem Netzwerk beliebig verloren gehen können. Ob nun nur Nachrichten verloren gehen, ganze
Knoten ausfallen oder sogar Teile eines Netzwerks abgetrennt werden, ist dabei irrelevant für ihr Theorem und dessen Beweis.
2.2 Formulierung des Theorems
Das in [GL02] postulierte Theorem lautet übersetzt:
Es ist unmöglich, in einem asynchronen Netzwerk ein Lese-/Schreib-Objekt zu implementieren, so dass die folgenden Eigenschaften für alle korrekten Ausführungen garantiert werden:
- Verfügbarkeit
- Atomare Konsistenz
Die Eigenschaft der Partitionstoleranz ist dabei immer implizit gegeben, da das Theorem sich auf ein asynchrones Netzwerk bezieht, in dem es keine Uhr zur Synchronisation gibt und alle Knoten ihre Entscheidungen allein auf den empfangenen Nachrichten
und ihren eigenen Berechnungen fällen müssen.
3
Gilbert und Lynch beweisen dieses Theorem durch Widerspruch. Sie nehmen zunächst
das Gegenteil an, dass es also einen Algorithmus gibt, der alle drei Eigenschaften erfüllt. Man nehme nun ein Netzwerk mit zwei Knoten, N1 und N2, und nehme nun an,
dass alle Nachrichten zwischen den Knoten verloren gehen. Erfolgt nun ein
Schreibvorgang auf Knoten N1, kann ein darauffolgender Lesevorgang in N2 niemals
das korrekte Ergebnis liefern. Einen solchen Algorithmus kann es nicht geben und so
wird das Kriterium der atomaren Konsistenz verletzt.
Julian Browne hat in [Bro09] dieses Szenario in zwei Bildern dargestellt, die diesen
Widerspruch leicht deutlich machen:
Das Bild oben zeigt den fehlerfreien Fall. Die beiden Netzwerkknoten N1 und N2 teilen sich ein Datenobjekt V0. Beide führen einen korrekten, fehlerfreien Algorithmus
aus, N1 den Algorithmus A und N2 den Algorithmus B. Dabei schreibt A einen neuen
Wert V1 in das Datenobjekt. Durch eine Nachricht M werden die anderen Knoten im
Netzwerk (in diesem Fall nur N2) über die Änderung informiert und liefern dann, wenn
wie in Schritt 3 der Algorithmus B auf den Wert des Datenobjektes zugreift, den neuen
Wert V1 zurück.
Das zweite Bild zeigt den Fall, dass in Schritt 2 die Nachricht von N1 an N2 verloren
geht. Dann bekommt Algorithmus B auf Knoten N2 in Schritt 3 nicht den korrekten
Wert des Datenobjektes – die atomare Konsistenz wurde verletzt.
4
Gilbert und Lynch stellen in [GL02] weiterhin ein Korollar auf, nach dem Verfügbarkeit und atomare Konsistenz in einem asynchronen Netzwerk auch dann nicht garantiert werden können, wenn keine Nachrichten verloren gehen. Der Grund dafür ist,
dass ein Knoten nicht wissen kann, ob eine Nachricht verloren ging oder nicht. Sogar
in einem teilweise synchronen Netzwerk, in dem die Knoten über eine Uhr verfügen,
können Verfügbarkeit und atomare Konsistenz nicht gleichzeitig garantiert werden, da
in diesem Szenario die Knoten zwar eine gewisse Zeitspanne auf Rückmeldungen warten, aber trotzdem nicht wissen können, ob eine Nachricht verloren ging. In dem Szenario oben würde N1 zwar auf die Rückmeldung seiner Nachricht M warten, aber N2
könnte in dieser Zeitspanne dennoch Zugriffe auf das Datenobjekt inkorrekt beantworten.
2.3 Das große Ganze
Gilbert und Lynch haben in einem zweiten Schriftstück mit dem Titel „Perspectives
on the CAP Theorem“ [GL12] gute zehn Jahre später den theoretischen Kontext nochmals erweitert und dort das CAP-Theorem als Spezialfall eines viel allgemeineren und
älteren Konfliktes beschrieben: die Unmöglichkeit, sowohl Sicherheit als auch Lebendigkeit in einem unzuverlässigen verteilten System zu garantieren.
Das Kriterium der Konsistenz wird zu Sicherheit und Verfügbarkeit wird zu Lebendigkeit, wobei beide Begriffe deutlich weiter gefasst sind. Unzuverlässigkeit kann
nicht nur aufgrund von Nachrichtenverlusten oder Netzwerkausfällen entstehen, sondern z.B. auch durch Attacken.
Das Verhältnis von Sicherheit und Lebendigkeit ist schon länger ein Thema im Kontext von verteilten Systemen. Bereits 1985 wurde gezeigt, dass eine fehler-tolerante
Zustimmung in einem asynchronen Netzwerk nicht möglich ist [FLP85]. Und genauso
wie heute wurden auch damals Wege aus dem Dilemma gesucht. Als Lösungsmöglichkeiten wurden neben Synchronisation, Fehler-Detektion und expliziten Annahmen
auch die Frage betrachtet, wie viel Konsistenz in einem System mit x Fehlern garantiert werden kann. Die Antwort darauf wendet Erkenntnisse aus der Topologie für die
Theorie der Berechenbarkeit in verteilten Systemen an und für sie erhielten Maurice
Herlihy, Michael Saks, Nir Shavit und Fotios Zaharoglou 2004 den Gödel-Preis.
2.4 Gegenmaßnahmen
Wenn man von drei Eigenschaften nur zwei haben kann, muss man eine Entscheidung
treffen. Mittlerweile weiß man, dass der Übergang eher graduell ist, und dass man in
einem System auch nicht zu jeder Zeit zu 100 Prozent Verfügbarkeit und Konsistenz
trotz Nachrichtenverlusten oder Netzwerkfehlern benötigt. Es muss auch nicht eine
Entscheidung sein, die für ein ganzes System gilt, sondern es sind Kompromisse und
Mixturen möglich, die je nach betroffener Funktionalität eingesetzt werden.
5
Als Implikationen des CAP-Theorems sehen Gilbert und Lynch in [GL12] vier mögliche Lösungsansätze:
1. Bestmögliche Verfügbarkeit: Bei garantierter Konsistenz ist eine möglichst
hohe Verfügbarkeit dann sinnvoll, wenn das System üblicherweise zuverlässig
ist, also z.B. Server, die in einem gemeinsamen Datenzentrum stehen.
2. Bestmögliche Konsistenz: Wenn eine Antwort garantiert werden muss, muss
man möglicherweise inkorrekte Daten in Kauf nehmen. Das eignet sich für
stark verteilte Applikationen und ist üblicherweise im Web Caching zu finden.
3. Abwägung von Konsistenz und Verfügbarkeit: Je nach Situation können Daten, die eine Stunde, aber nicht einen Tag alt sind, akzeptabel sein. Es könnte
also verschiedene Level für Konsistenz und Verfügbarkeit geben, z.B. bei der
Buchung von Tickets für einen Flug. Zu Beginn, wenn noch viele Plätze verfügbar sind, ist Verfügbarkeit wichtiger als Konsistenz. Wenn später nur noch
wenige Plätze verfügbar sind, ist es wichtig, konsistent zu sein, also definitiv
zu wissen, ob der Platz noch frei ist – dafür sind dann ggfs. längere Antwortzeiten in Kauf zu nehmen.
4. Segmentierung von Konsistenz und Verfügbarkeit: Verschiedene Aspekte eines Systems benötigen unterschiedliche Entscheidungen für Verfügbarkeit und
Konsistenz. Diese Segmente können z.B. nach Art der Daten, nach Operationen, funktional, nach Benutzer oder hierarchisch gebildet werden, für die dann
jeweils unterschiedliche Schwerpunkte bei Konsistenz und Verfügbarkeit gebildet werden.
Letztendlich verzögern alle diese möglichen Maßnahmen nach [Bre12] aber nur den
finalen Punkt der Entscheidung, an dem ein Programm schließlich wählen muss:
 Entweder die Operation abzubrechen und damit die Verfügbarkeit zu senken
 Oder die Operation durchzuführen und damit Inkonsistenz zu riskieren.
Bei sehr zuverlässigen Systemen empfiehlt Brewer daher als immer anwendbare Lösungsstrategie, dass Konsistenz und Verfügbarkeit meistens gewährleistet werden sollten. Im Fall von Netzwerkproblemen oder Fehlern tritt eine vorab definierte Strategie
in Kraft. Diese Strategie besteht aus drei Schritten: Fehlererkennung, den Wechsel in
einen limitierten Modus mit teilweise eingeschränkten Operationen und schließlich
Wiederherstellung der Konsistenz.
Dass solch aufwendige Fragestellungen wichtig und sinnvoll sind, belegt Julian
Browne in [Bro09] mit zwei eindrucksvollen Zahlen:
 Amazon verliert 1% an Umsatz für jedes Zehntel einer Sekunde, die eine Antwort länger benötigt.
 Google beobachtet, dass für eine um eine halbe Sekunde gestiegene Latenz der
Netzwerkverkehr um ein Fünftel fällt.
6
3. Grundlegende Prinzipien
Das eben vorgestellte CAP-Theorem ist Grundlage für zwei gegensätzliche Prinzipien,
die im Bereich von Datenbanken gelten. Dabei steht das erste, ACID, oft synonym für
relationale, traditionelle Datenbanksysteme, während das zweite, BASE, kreiert
wurde, um den Design-Bestrebungen bei NoSQL-Systemen einen Namen zu geben.
Kurzgesagt steht das ACID-Prinzip für die Bevorzugung von Konsistenz gegenüber
Verfügbarkeit, während das BASE-Prinzip die Verfügbarkeit der Konsistenz vorzieht.
Natürlich sind auch Systeme, die das ACID-Prinzip implementieren, verfügbar und
BASE-verwendende Systeme sind eventuell auch konsistent.
3.1 ACID bei SQL-Datenbanken
Die Entwicklung von Computersystemen ist Ende der 70er Jahre so weit fortgeschritten, dass es Datenbanken gibt, und dass auf Systemen mehrere Benutzer zeitgleich
arbeiten können. Dies führt allerdings zur Problematik, dass ein Nutzer Daten manipulieren kann und dabei auch Auswirkungen auf andere Nutzer erzeugt. Stellt der erste
Nutzer nun fest, dass seine Manipulationen nicht korrekt waren, kann es leicht vorkommen, dass eben diese Daten bereits vom zweiten Nutzer verwendet wurden.
Im Zuge dieser Betrachtungen wurden Transaktionskonzepte entwickelt und schließlich 1983 folgende vier Eigenschaften von Theo Härder und Andreas Reuter in [HR83]
beschrieben. Die Verarbeitungsschritte in Datenbanksystemen sollen diese Eigenschaften erfüllen, um eine fehlerfreie Verarbeitung (auch in Mehrbenutzersystemen)
und einfache Wiederherstellung im Falle von Fehlern zu gewährleisten.
A – Atomarität
Atomarität oder Abgeschlossenheit bedeutet, dass eine Folge von Verarbeitungsschritten entweder ganz oder gar nicht durchgeführt wird. Ein einzelner Verarbeitungsschritt
wie z.B. „Schreibe 3 in Zelle xy“ ist an sich bereits atomar, aber oft benötigt man eine
ganze Reihe von Verarbeitungsschritten für eine einzelne Operation. Die Reihe von
Schritten wird üblicherweise in einer Transaktion zusammengefasst.
C – Konsistenz
Konsistenz eines Systems bedeutet hier, dass es sich in einem widerspruchsfreien und
korrekten Zustand befindet und zielt vor allem auf die inhaltliche und referentielle Integrität ab. Die Eigenschaft der Konsistenz garantiert am Ende einer Transaktion wiederum einen konsistenten Zustand, wenn das System zu Beginn der Transaktion konsistent war. Erreicht wird dies durch Normalisierung (Vermeidung von Redundanz),
Integritätsbedingungen, Schlüssel- und Fremdschlüsselbedingungen.
7
I – Isolation
Das Kriterium der Isolation oder Abgrenzung besagt, dass nebenläufige Daten-Operationen sich nicht gegenseitig beeinflussen dürfen. Es wird erst bei Härder und Reuter
erwähnt. Ältere Schriften wie z.B. [Gra81] von Jim Gray erwähnen nur Konsistenz,
Atomarität und Dauerhaftigkeit als Eigenschaften von Transaktionen. Härder und
Reuter beschreiben mit Isolation die Notwendigkeit, auch die einzelnen Schritte der
Transaktion vor anderen Nutzern zu verstecken, da sie sonst auch für Lese-Operationen eventuell fehlerhafte Daten bekommen.
D – Dauerhaftigkeit
Schließlich besagt die Eigenschaft der Dauerhaftigkeit, dass alle Daten nach Abschluss
der Transaktion dauerhaft gespeichert sein sollen und so auch im Falle folgender Ausfälle kein Datenverlust entstehen kann.
Diese vier Eigenschaften werden normalerweise von relationalen Datenbanksystemen
erfüllt. In einer verteilten Datenbank wird es allerdings schwierig oder teuer, alle Bedingungen zu erfüllen.
3.2 BASE bei NoSQL-Datenbanken
BASE steht für „Basically Available, Soft state, Eventually consistent“ und stellt damit
die Garantie von Verfügbarkeit über die Konsistenz. Der Begriff wurde von Armando
Fox, Steven D. Gribble, Yatin Chawathe, Eric A. Brewer und Paul Gauthier 1997 in
ihrem Artikel „Cluster-Based Scalable Network Services“ eingeführt und steht für folgende Eigenschaften [FGC+97]:
Stale Data
Veraltete Daten können für eine gewisse Zeit toleriert werden, solange sie eventuell
wieder konsistent werden, z.B. werden DNS-Einträge nur konsistent wenn bestimmte
Zeitüberschreitungen stattgefunden haben.
Soft State
Die Daten sind nicht dauerhaft gespeichert. Sie können zwar wiederhergestellt werden,
allerdings nur mit zusätzlichen Berechnungen. Die Performanz eines Services kann
durch die nicht sofort erforderliche Schreiboperation verbessert werden.
Approximate Answers
Statt langsam gelieferter, exakter Antworten, mag es für den Nutzer besser sein, ungefähre schnelle Antworten zu bekommen. Daten können aufgrund der beiden vorgenannten Merkmale veraltet oder fehlerhaft sein.
8
Man kann sagen, dass alles, was nicht strikt den ACID-Kriterien folgt, eigentlich
BASE ist. Der Hauptvorteil liegt darin, dass sich mit BASE Fehler in verteilten Systemen mit weniger Komplexität und damit Kosten handhaben lassen.
Abschließend kann man noch feststellen, dass weder ACID noch BASE besser ist. Es
sind zwei gegensätzliche Paradigmen, die je nach Anwendungsfall oder -situation besser oder schlechter geeignet sind. In der Regel wird man sogar beide Prinzipien in
einem System finden, da es Daten geben wird, die unbedingt dem ACID-Prinzip folgen (z.B. Rechnungsdaten), während andere Daten mit dem BASE-Prinzip gehandhabt
werden können (z.B. Proxies oder Caches).
9
4. NoSQL-Datenbank Typen
Da unter den Begriff NoSQL eine Vielzahl sehr unterschiedlicher Datenbanken fällt,
wurden verschiedene Kategorien gebildet. Meistens werden dabei die vier nachfolgend kurz vorgestellten Typen unterschieden [Wal12, Cat11].
4.1 Key Value Stores
Key Value Stores speichern Schlüssel-Wert-Paare, wobei der Schlüssel zur eindeutigen Identifikation des Wertes dient und der Wert ein beliebiges Objekt sein kann, also
beispielsweise eine Zeichenkette. Diese Art von Datenbanken gibt es schon länger, sie
wurden z.B. als Embedded-Datenbanken im Unix-Umfeld verwendet (dbm, gdbm).
Key Value Stores lassen sich in zwei Kategorien aufteilen: In-Memory oder On-Disk.
In-Memory bedeutet, dass die Daten im Speicher vorgehalten werden. Das bietet hohe
Performanz und ist gut als Cache geeignet. Bei On-Disk-Key-Value-Stores werden
alle Daten auf der Festplatte gespeichert. Sie sind ein traditioneller Datenspeicher.
Schemalosigkeit und gute Skalierbarkeit sind die Vorteile von Key Value Stores, dafür
bieten sie aufgrund der unstrukturierten Daten nur beschränkte Abfragemöglichkeiten
und auch keine Abfrageoptimierungen wie Sekundär-Indizes.
Beispiele für Key Value Stores sind Voldemort, Riak, Redis, Scalaris und Tokyo Cabinet.
4.2 Spaltenorientiert
Ebenso wie relationale Datenbanken Zeilen und Spalten aufweisen, bestehen auch
spaltenorientierte Datenbanken aus Spalten und Zeilen. Allerdings rücken hier die
Spalten in den Vordergrund und sie werden so gebildet, dass sich Daten gut aggregieren lassen. Dadurch und da sie das Hinzufügen von weiteren Spalten vereinfachen,
werden sie oft für Data Mining- und Analyse-Programme verwendet. Die Skalierung
wird durch Verteilen von Spalten bzw. Spalten-Gruppen über mehrere Knoten oder
Server erreicht. Neben der guten Skalierbarkeit ist ein weiterer Vorteil die gute Eignung für große Datenmengen (Petabyte-Bereich). Allerdings sind Schreibprozesse
über mehrere Spalten hinweg im Vergleich zu relationalen Datenbanken relativ teuer.
Vertreter von spaltenorientierten Datenbanken sind HBase, HyperTable, Google’s
BigTable und Cassandra.
4.3 Dokumentorientiert
Dokumente sind bei diesem Datenbanktyp nicht im herkömmlichen Sinne zu verstehen, sondern als beliebige Texte beliebiger Länge mit unstrukturierten Daten bzw. im
Fall von XML-Datenbanken semi-strukturierte Daten. Ähnliche Dokumente werden
in Gruppen oder Kollektionen zusammengefasst und die meisten Produkte bieten Sekundär-Indizes, verschiedene Dokument-Typen pro Datenbank, verschachtelte Dokumente und Listen an.
Abfragen sind durch die Zusammenfassung von thematisch zusammengehörigen Daten sehr schnell. Such-Abfragen sind in der Regel im Dokumenten-Inhalt möglich.
10
Unter Dokumentenorientierte Datenbanken fallen z.B. SimpleDB, CouchDB, MongoDB und Terrastore.
4.4 Graph-orientiert
Bei Graph-orientierten Datenbanken werden Graphen, also Knoten und deren Beziehungen gespeichert, keine Datensätze in tabellarischer Form. Das ermöglicht das Speichern von Daten, wie sie z.B. in sozialen Netzwerken verwendet werden und ebenso
effiziente Abfragen auf diesen Daten. Skalierung wird entweder durch Replikation oder durch Partition des Graphen erreicht.
Unter die Gattung der Graph-orientierten Datenbanken fällt z.B. Neo4J und OrientDB.
Eine grafische Darstellung der verschiedenen NoSQL-Datenbanktypen findet sich bei
[Kat12], der auch im nachfolgenden Kapitel über die Modellierung noch Eingang findet.
11
5. Modellierung
Die Modellierung von NoSQL-Datenbanken unterscheidet sich wegen der Unterschiede in den grundlegenden Konzepten stark von der Modellierung traditionellerer
SQL-Datenbanken. Insbesondere kann man nicht von einer SQL-Datenbank „einfach
mal so“ auf ein NoSQL-Produkt wechseln. Neben der Datenbank-Architektur sollte
der Wechsel sogar von einer Anpassung der Anwendungs-Architektur begleitet sein
[Zyp10].
In [Zyp10] ist ein möglicher Ansatz skizziert, bei dem eine 2-gliedrige Architektur
vorgestellt wird. Während sich die NoSQL-Datenbank in der ersten Schicht auf die
Speicherung von Daten konzentriert und darüber hinaus nur einen – hoch performanten, skalierbaren – Datenzugriff auf niedrigster Ebene gestattet, liegt darüber eine Daten-Verwaltungsschicht, die für Konsistenz und Integrität sorgt. Diese frei programmierbare (und damit perfekt an die jeweilige Anwendung anpassbare) Schnittstellenschicht sollte neben den komplexeren Datenzugriffen auch Komponenten für Validierung und Replikation bzw. Sicherung enthalten.
Ilya Katsov von GridDynamics liefert in seinem Blog [Kat12] neben einigen grundlegenden Aussagen auch eine ganze Reihe von Modellierungs-Techniken für NoSQLDatenbanken. Dabei stellt er zunächst ein paar Unterschiede zur SQL-Modellierung
heraus:
 Das Modell basiert bei SQL auf der Struktur der verfügbaren Daten („What
answers do I have?“) gegenüber einer anwendungsspezifischen Struktur auf
Basis der gewünschten Abfragen („What questions do I have?“) bei NoSQL.
 NoSQL erfordert ein tieferes Verständnis für die Daten als das für SQLDatenbanken der Fall ist.
 Datenredundanz und Denormalisierung sind ausdrücklich erlaubt und erwünscht bei NoSQL.
 Während relationale Datenbanken für hierarchische oder Graphen-orientierte
Daten wenig geeignet sind, sind bei den NoSQL-Datenbanken natürlich die
Graphen-orientierten Vertreter absolut dafür zu empfehlen, aber auch verschiedene andere NoSQL-Vertreter sind für solche Daten gut geeignet.
5.1 Konzeptionelle Techniken
1. Denormalisierung: Darunter ist das Kopieren von Daten in mehrere Dokumente oder Tabellen zu verstehen, um Abfragen zu vereinfachen oder um Daten in ein vordefiniertes Datenmodell zu bringen. Dabei muss man immer zwischen dem gesamten Datenvolumen und dem Abfragedatenvolumen und der
Komplexität abwägen.
2. Aggregation: Darunter fallen auch die Möglichkeiten des „soft schema“, also
dass es bei NoSQL oft keine strikten Datentypen gibt. So können Daten ähnlichen Typs leicht aggregiert werden, was zu einem zu einer Verringerung der
1:n-Beziehungen führt und zum anderen die rein technischen Unterschiede von
gleichartigen Fach-Entitäten maskiert.
12
3. Anwendungsseitige Joins: Joins werden üblicherweise nicht von NoSQL-Lösungen unterstützt und daher oft bereits in der Entwurfsphase berücksichtigt.
In vielen Fällen können Joins durch Denormalisierung und Aggregation vermieden werden, aber nicht in allen. Besonders oft kommen sie bei n:m-Beziehungen zum Einsatz oder als Alternative zur Aggregation, bei denen einzelne
Entitäten oft geändert werden müssen.
5.2 Generelle Modellierungs-Techniken
1. Atomare Aggregation: Da NoSQL-Lösungen in der Regel nur eine sehr begrenzte Unterstützung für Transaktionen liefern, modelliert man ACIDEigenschaften (wo sie benötigt werden) oft mit Hilfe von Aggregation.
2. Zählbare Schlüssel: Diese Technik für Key-Value-Stores basiert entweder auf
atomaren Zählern, wie sie von einigen NoSQL-Lösungen angeboten werden,
oder auf der Partitionierung von Daten in Container (z.B. Logs, die tageweise
in Container sortiert werden).
3. Reduzierung der Dimensionalität: Die Technik wird verwendet, um mehr-dimensionale Daten in ein Key-Value-Store oder andere nicht-mehrdimensionale
Modelle umzuwandeln.
4. Index-Tabellen: Eine sehr einfache Methode für Spalten-orientierte Datenbanken, um von Indizes zu profitieren, wenn die NoSQL-Lösung keine Indexierung anbietet, ist die Erstellung und Speicherung eigener Index-Tabellen. Natürlich schlägt sich das Aktualisieren der zusätzlichen Index-Tabellen in der
Datenbank-Performanz nieder und es kann eventuell zu Inkonsistenzen kommen.
5. Verbundschlüssel-Indizes: Die Idee dieser Technik ist es, zusätzliche Indizes
aufzubauen, deren Verbundschlüssel bereits Teile einer Sortierung enthalten
(z.B. einem Schlüssel aus Staat, Stadt und UserID, der eine besonders schnelle
Lieferung von möglichen UserIDs nach Staat oder Stadt ermöglicht).
6. Aggregation von Verbundschlüsseln: Ebenso wie die Verbundschlüssel-Indizes wird hier statt Indizes Aggregation verwendet, um verschiedene Arten von
Gruppierungen zu erhalten, ebenso wie die Verbundschlüssel-Indizes für spaltenorientierte Datenbanken.
7. Invertierte Suche – Direkte Aggregation: Diese Methode ist eher ein Datenzugriffsmuster. Wenn man eine Tabelle mit dem Schlüssel UserID und dem Eintrag Kategorien hat und z.B. die User pro Kategorie zählen möchte, ist eine
invertierte Suche angebracht. Diese Suche kann nun noch optimiert werden,
indem das Zusammenzählen als direkte Aggregation abgebildet wird.
13
5.3 Hierarchische Modellierungs-Techniken
1. Baum-Aggregation: Bäume und Graphen können als einziger Datensatz oder
als ein Dokument modelliert werden. Das bietet sich an, wenn der Baum bzw.
Graph i.d.R. als Gesamtes abgefragt wird. Dafür sind Suche und Aktualisierung einzelner Teile eher ineffizient bzw. sogar problematisch.
2. Adjazenzlisten: Die Listen der direkten Nachbarn sind ein bekanntes Mittel,
um Graphen auszudrücken. Es ermöglicht eine gute Navigation durch den
Baum bzw. Graph, aber ist ungeeignet für die Abfrage von ganzen Teilbäumen
oder Teilgraphen.
3. Materialisierte Pfade: Diese Methode soll das rekursive Traversieren auf
baumartigen Strukturen vermeiden und ist als eine Art Denormalisierung zu
verstehen. Dabei wird z.B. das hierarchische Geflecht einer Produktkategorisierung in geordnete Einträge der einzelnen Produkte umgewandelt (Produkt:
Slipper, Kategorie: Schuhe, Männerschuhe, Slipper).
4. Geschachtelte Datensätze: Dies ist eine bekannte Technik für baumartige
Strukturen, die sehr effiziert für unveränderliche Daten ist und es ermöglicht,
alle Blätter eines gegebenen Knoten ohne Traversierungen zu erhalten.
5. Nummerierte Feldnamen: Um eine Liste von Einträgen, die jeweils aus zwei
oder mehr Attributen bestehen, korrekt in einem eindimensionalen Datensatz
oder Dokument zu speichern, kann man z.B. auf nummerierte Feldnamen zurückgreifen. Gibt es z.B. zu jedem User die Möglichkeit, seine Interessen und
deren Stärke zu speichern (User: „John“, Interesse: „Autos“ – Stärke: „sehr
stark“, Interesse: „Hausbau“ – Stärke: „Weniger stark“ etc.), so würde eine
Abfrage unter Umständen die einzelnen Stärken den falschen Interessen zuordnen. Die Nummerierung würde dann Attribute wie „Interesse_1“, „Interesse_2“ etc. daraus machen, aber sofort die Abfragekomplexität deutlich erhöhen. Eine Alternative ist, die Einträge Interesse und Stärke jeweils in einen
Eintrag zu kombinieren („Autos – sehr stark“, „Hausbau – weniger stark“),
wobei dann wiederum Abfragen wie „alle User und ihre sehr starken Interessen“ komplizierter werden.
Die konzeptionellen und generellen Modellierungs-Techniken sind für Key-ValueStores, Dokumenten-orientierte und Spalten-orientierte Datenbanken geeignet, der
Einsatz der Anwendungsseitigen Joins ist auch bei Graph-orientierte Datenbanken
möglich. Die Techniken der hierarchischen Modellierung sind für Key-Value-Stores
und Dokumentorientierte Datenbanken gedacht.
14
6. Fazit
NoSQL-Datenbanken bieten eine ganze Reihe neuer Ansätze und verschiedenste Lösungen und Produkte. Sie bieten viele Vorteile und haben im Vergleich zu SQLDatenbanken auch einige Nachteile. Dennoch gibt es bereits wie in [HS12] geschildert,
Einsatzgebiete jenseits der Nischen und Spezialfälle, in denen eine NoSQL-Lösung
die beste Möglichkeit ist.
Als Vorteile bieten NoSQL-Produkte einen flexiblen Umgang mit variablen Daten, die
Möglichkeit, Beziehungen effizient abzubilden, und vor allem die besseren Möglichkeiten zur Skalierbarkeit [Wal12]. Dabei gibt es auf diesem Gebiet aber bereits Fortschritte auf dem SQL-Sektor mit Produkten wie MySQL Cluster, VoltDB oder
Clustrix [Cat11].
Weitere Eigenschaften, die die Verbreitung und Akzeptanz von NoSQL-Produkten
fördern, sind deren (teilweise) Quelloffenheit und die an der Praxis orientierte Weiterentwicklung. Dazu kommen die gute Verständlichkeit für einfache Anwendungsfälle
(Key-Value-Stores) und der Verzicht auf starre Tabellenschemata.
Als größter Nachteil wird oft die nicht vorhandene Unterstützung für ACIDTransaktionen genannt, die auch tatsächlich überall da ins Gewicht fällt, wo sie benötigt wird. Dazu kommt die lange Erfahrung und mittlerweile etablierte Vormachtstellung der SQL-Produkte im Geschäftsumfeld und die zumindest grob einheitliche
Schnittstelle SQL. SQL-Datenbanken machen außerdem teure Operationen über verschiedene Knoten und Tabellen hinweg sehr einfach, während NoSQL-Produkte diese
Art von Operationen entweder gar nicht bieten oder sie durch teuren Programmieraufwand selbst zu implementieren sind.
15
7. Literaturverzeichnis
[BLS+11]
REDUCE, YOU SAY: What NoSQL can do for Data Aggregation and
BI in Large Repositories; 2011; L. Bonnet, A. Laurent, M. Sala, B.
Laurent, N. Sicard;http://dl.acm.org/citation.cfm?id=2065353.2065430
[Bre00]
Towards Robust Distributed Systems; 2000; Eric A. Brewer
http://dl.acm.org/citation.cfm?id=343477.343502
[Bre12]
CAP Twelve Years Later: How the „Rules“ Have Changed; 2012;
Eric A. Brewer; http://www.infoq.com/articles/cap-twelve-years-laterhow-the-rules-have-changed
[Bro09]
Brewer’s CAP Theorem – The kool aid Amazon and Ebay have been
Drinking; 2009; Julian Browne
http://www.julianbrowne.com/article/viewer/brewers-cap-theorem
[Cat11]
Scalable SQL and NoSQL Data Stores; 2011; Rick Cattell
http://dl.acm.org/citation.cfm?id=1978915.1978919
[FGC+97]
Cluster-Based Scalable Network Services; 1997; Armando Fox, Steven
D. Gribble, Yatin Chawathe, Eric A. Brewer, Paul Gauthier
http://dl.acm.org/citation.cfm?id=268998.266662
[FLP85]
Impossibility of distributed consensus with one faulty process; 1985;
M.J. Fischer, N.A. Lynch, M.S. Paterson;
http://dl.acm.org/citation.cfm?id=3149.214121
[Fow12]
Nosql Definition; 2012; Martin Fowler
http://martinfowler.com/bliki/NosqlDefinition.html
[GL02]
Brewer’s Conjecture and the Feasibility of Consistent, Available,
Partition-Tolerant Web Services; 2002; Seth Gilbert, Nancy Lynch
http://dl.acm.org/citation.cfm?id=564585.564601
[GL12]
Perspectives on the CAP Theorem; 2012; Seth Gilbert, Nancy A. Lynch
http://dl.acm.org/citation.cfm?id=2360751.2360958
[Gra81]
The Transaction Concept: Virtues and Limitations; 1981; Jim Gray
http://dl.acm.org/citation.cfm?id=1286831.1286846
[HR83]
Principles of Transaction-Oriented Database Recovery; 1983;
T. Härder, A. Reuter; http://dl.acm.org/citation.cfm?id=190956.190985
[HS12]
SOA-basierte NoSQL-Lösung im Mobile-Umfeld; 2012;
M. Hüttermann, D. Schneller; Java Spektrum, Ausgabe 03/2012
[Kat12]
NoSQL Data Modeling Techniques; 2012; Ilya Katsov
http://highlyscalable.wordpress.com/2012/03/01/nosql-data-modelingtechniques/
[Wal12]
NoSQL im Überblick; 2012; Dj Walker-Morgan
http://www.heise.de/open/artikel/NoSQL-im-Ueberblick1012483.html
[Zyp10]
NoSQL Architecture; 2010; Kris Zyp
http://www.sitepen.com/blog/2010/05/11/nosql-architecture/
16
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 3.2
Dynamo
Referentin: Jana Stehmann
Jana Stehmann
Thema 3.2 Dynamo
Seite 2
Inhaltsverzeichnis
1.
Einführung ............................................................................................................................... 3
2.
Hintergrund .............................................................................................................................. 4
3.
Anforderungen ......................................................................................................................... 5
4.
5.
6.
3.1.
Verfügbarkeit .................................................................................................................... 5
3.2.
Konsistenz ........................................................................................................................ 5
3.3.
Skalierbarkeit .................................................................................................................... 5
3.4.
Sicherheit .......................................................................................................................... 5
Aufbau...................................................................................................................................... 6
4.1.
Consistent Hashing ........................................................................................................... 6
4.2.
Vector Clocks ................................................................................................................... 7
4.3.
Sloppy Quorum und Hinted Handoff ............................................................................... 9
4.4.
Anti-Entropie durch Merkle-Bäume ................................................................................. 9
4.5.
Gossip-basiertes Protokoll .............................................................................................. 10
Optimierungen ....................................................................................................................... 11
5.1.
Verbesserte Partitionierung ............................................................................................ 11
5.2.
Quorumanpassungen ...................................................................................................... 11
5.3.
Anfragesteuerung............................................................................................................ 12
Zusammenfassung.................................................................................................................. 13
Literaturliste .................................................................................................................................. 14
Jana Stehmann
Thema 3.2 Dynamo
Seite 3
1. Einführung
Internetbestellungen werden immer beliebter, die Anzahl der Artikel, die bestellt werden kann,
steigt immer weiter an, die Lieferzeiten sind meistens kurz und die Preise häufig günstiger als im
Geschäft. Online-Versandhäuser müssen daher einen sehr großen Datenbestand (z.B. Kunden,
Artikel, Bestellungen) verwalten und der Zugriff auf diese Daten muss sehr schnell erfolgen.
Um dieses zu gewährleisten werden NoSQL-Datenbanken verwendet. Eine Möglichkeit für
einfache NoSQL-Datenbanken sind Key-Value-Stores (vgl. [1], Kapitel 2). Bei diesem
Verfahren werden die Daten schemalos gespeichert und der Zugriff auf einen Datensatz erfolgt
ausschließlich über den Primärschlüssel.
Ein Beispiel für dieses Verfahren ist die Datenbank Dynamo von Amazon. In dieser Arbeit
werden zunächst der Hintergrund und die Anforderungen von Amazon vorgestellt und im
Weiteren auf die verwendeten Verfahren genauer eingegangen. Zum Abschluss werden einige
Optimierungsmöglichkeiten betrachtet.
Jana Stehmann
Thema 3.2 Dynamo
Seite 4
2. Hintergrund
Amazon ist ein weltweites Online-Versandhaus mit mehr als 200 Millionen Kunden (vgl. [7]).
Um alle Kunden schnell bedienen zu können gibt es mehrere 10.000 Server, die in Datenzentren
über die ganze Welt verteilt sind. Die Anforderungen an Stabilität und Performance sind sehr
hoch. Gerade im Weihnachtsgeschäft ist auch die Skalierbarkeit ein sehr wichtiges Thema.
Amazon nutzt eine dezentralisierte, nur lose
miteinander
gekoppelte,
dienstbasierte
Architektur,
die
aus
hunderten
verschiedenen Diensten (z.B. Warenkorb,
Bestsellerlisten, Produktkatalog) besteht
(vgl. Abb. 1). Die Verfügbarkeit dieser
Dienste muss immer gewährleistet sein.
Es muss einem Kunden bspw. immer
möglich sein, einen Artikel seinem
Warenkorb hinzuzufügen, selbst wenn ein
Datenzentrum ausfällt.
Bei der Anzahl an Hardware fällt immer
irgendwas aus oder muss ersetzt werden. Die
Ausfälle sollten den Kunden nicht
beeinflussen und der kurzfristige Ausfall
einzelner Knoten muss ohne manuelles
Eingreifen kompensierbar sein.
Eine Herausforderung ist hierbei die
Heterogenität der verwendeten Hardware
und die Tatsache das Standardkomponenten
eingesetzt werden, die nicht für einen hohen
Abbildung 1: Amazons Architektur (Abb. aus [2])
Datendurchsatz optimiert sind.
Aus dem Hinzufügen eines leistungsfähigeren
Servers darf nicht resultieren, dass alle anderen
Server dieselben Leistungsdaten haben müssen.
Jana Stehmann
Thema 3.2 Dynamo
Seite 5
3. Anforderungen
3.1. Verfügbarkeit
Beim Aufruf einer Seite sind, aufgrund der dezentralisierten, dienstbasierten Architektur, viele
verschiedene Dienste involviert. Damit die Seite schnell aufgebaut werden kann, fordert Amazon
für diese Dienste eine Antwort innerhalb von 300ms für 99,9% der Anfragen bei 500 Anfragen
pro Sekunden (vgl. [2], Seite 207). Diese gilt sowohl für Lesezugriffe, als auch für
Schreibzugriffe.
Dieser hohe Prozentsatz wird vor allem gefordert, um nicht nur den Standardkunden zufrieden
zu stellen, sondern auch diejenigen Kunden, die schon sehr viel bei Amazon gekauft haben und
eine entsprechend große Historie haben.
3.2. Konsistenz
Normalerweise werden an Datenbanken die ACID (Atomarität, Konsistenzerhaltung, Isolation
und Dauerhaftigkeit) Anforderungen gestellt. Um die geforderte Verfügbarkeit zu erreichen
müssen die Anforderungen an die Konsistenz reduziert werden. Wenn immer solange gewartet
werden würde, bis alle Server die Daten gelesen oder geschrieben haben, wären die
Antwortzeiten immer so hoch, wie der langsamste Server braucht. Es wird daher nach dem
Prinzip der „eventual consistency“ gearbeitet. Dieses bedeutet, dass die Daten irgendwann
wieder konsistent sein werden, nicht aber direkt nach dem Abschluss einer Transaktion (vgl. [1],
Kapitel 1).
Dieses kommt dadurch zustande, dass die Daten für einen erfolgreichen Abschluss einer
Transaktion nur auf einem Teil der Server geschrieben werden müssen und die anderen Server
dafür mehr Zeit haben. Hierdurch kann es aber zu Problemen beim Lesen der Daten kommen, da
die verschiedenen Server einen unterschiedlichen Datenbestand haben und deshalb verschiedene
Antworten zurückliefern. Um dieses Problem zu beheben müssen Verfahren gefunden werden
um zu entscheiden wie mit den unterschiedlichen Versionen umgegangen werden soll.
3.3. Skalierbarkeit
Gerade im Weihnachtsgeschäft werden deutlich mehr Bestellungen getätigt, als im Rest des
Jahres. Am Spitzentag des Weihnachtsgeschäfts des Jahrs 2009, am 14. Dezember, wurden bis
zu 110 Produkte pro Sekunden gekauft (vgl. [6]). Auch diese Belastungsspitzen müssen
zuverlässig abgedeckt werden, da ansonsten die Kunden über lange Antwortzeiten verärgert sind
und beim nächsten Mal lieber woanders einkaufen.
Wenn Amazon aber über das ganze Jahr so viele Server betreiben würde, wie im
Weihnachtsgeschäft, wäre dies eine Verschwendung von Kapazitäten. Daher muss das System
durch ein einfaches Hinzufügen und Entfernen von Servern skalierbar sein.
3.4. Sicherheit
Authentifizierung und Autorisierung können die Performance beeinflussen, da erst einmal
geprüft werden muss, ob die Person / der Dienst überhaupt berechtigt ist auf diese Daten
zuzugreifen.
Da Dynamo aber nur von Amazons eigenen Diensten genutzt wird und somit in einer
„freundlichen“ Umgebung läuft, die nach außen abgeschirmt ist, gibt es keine Anforderungen in
diese Richtung.
Jana Stehmann
Thema 3.2 Dynamo
Seite 6
4. Aufbau
Der Zugriff, auf die in Dynamo gespeicherten Daten, erfolgt über einfache put() und get()
Operationen. Es muss auch im Falle einer Netzpartionierung immer gewährleistet sein, dass auf
die Daten zugegriffen werden kann. Außerdem muss es Verfahren geben, um mit verschiedenen
Versionen eines Datensatzes umzugehen. Um dieses und die unter 3. genannten Anforderungen
zu erfüllen, werden bei Dynamo verschiedene Verfahren genutzt.
Problem
Partitionierung
Verfahren
Consistent Hashing
Vorteil
Skalierbarkeit
Hochverfügbarkeit für
Schreibzugriffe
Vector Clocks
Versionsanzahl nicht an
Aktualisierungsrate gekoppelt
Behandlung
kurzzeitiger Ausfälle
Sloppy Quorum und
Hinted Handoff
Wiederherstellung nach
dauerhaften Ausfällen
Anti-Entropie durch
Merkle-Bäume
Mitgliedschaft und
Ausfallerkennung
Gossip-basiertes
Protokoll
Stellt hohe Verfügbarkeit und
Dauerhaftigkeit sicher, auch wenn
einige Replikate nicht verfügbar
sind
Synchronisierung von
abweichenden Replikaten im
Hintergrund
Vermeidung einer zentralen
Mitglieder-Datenbank
Tabelle 1: Verfahren
4.1. Consistent Hashing
Amazon nutzt zur Speicherung der Daten eine konsistente Hashfunktion, mit der beim
Hinzufügen oder Entfernen von Servern möglichst wenige Daten verschoben werden müssen.
Da Amazon eine sehr heterogene Serverumgebung nutzt, wird jeder Server in mehrere virtuelle
Knoten aufgeteilt und die auf allen Servern so entstandenen Knoten, sind in einem logischen
Ring angeordnet. Die Anzahl der virtuellen Knoten ergibt sich hierbei aus der Leistungsfähigkeit
des Servers. Jeder Knoten ist für einen bestimmten zufälligen Abschnitt des Ringes
verantwortlich und verwaltet alle Datensätze, die zwischen ihm und seinem Vorgänger liegen.
Diese Abschnitte haben eine zufällige Größe, da jedem Knoten eine zufällige Position
zugewiesen wird. Wenn ein Knoten hinzugefügt oder entfernt wird, betrifft dieses nur seine
direkten Nachbarn. Beim Hinzufügen geben die Nachbarn einige Datensätze an den neuen
Knoten ab, beim Entfernen müssen sie diese Datensätze wieder übernehmen (vgl. [3], Kapitel 4).
Wenn eine inkonsistente Hashfunktion verwendet werden würde, dann müssten bei jedem
Hinzufügen oder Entfernen die Datensätze aller Knoten neu verteilt werden und nicht nur die der
direkten Nachbarn.
Wenn ein Server nicht in virtuelle Knoten aufgeteilt werden würde, gäbe es unter Umständen
eine sehr ungleiche Lastverteilung. Insbesondere wenn ein leistungsfähiger Server wegfallen
würde, würden alle Datensätze von diesem auf einen eventuell nicht so leistungsfähigen Server
übertragen, der dann eine sehr hohe Last hätte. Die virtuellen Knoten werden jedoch zufällig
über den gesamten Ring verteilt, so dass bei einem Ausfall eines Servers die Last auf viele
andere Server aufgeteilt wird.
Jana Stehmann
Thema 3.2 Dynamo
Seite 7
Für jeden Datensatz wird mit Hilfe von MD5 ein Hashwert berechnet, mit dem bestimmt wird,
auf welchem Knoten (Koordinator) der Datensatz gespeichert wird. Zusätzlich wird jeder
Datensatz auf die nachfolgenden Knoten repliziert, wobei die Anzahl der zusätzlichen Knoten
konfigurierbar ist (Parameter N).
Es gibt zusätzlich noch eine Präferenzliste, in der steht, welche Knoten dafür verantwortlich sein
können, einen Datensatz zu speichern. Die Liste ist so aufgebaut, dass sie nicht nur Knoten
enthält die hintereinander auf dem Ring liegen, sondern auch einige Knoten überspringt, um
sicher zu stellen, dass die Daten über mehrere physikalische Server bzw. sogar Datenzentren
verteilt sind (vgl. [2], Seite 210).
Beispiel:
Datensatz K wird auf Knoten B gespeichert, mit N=2 werden die Daten, die auf B
gespeichert sind, auf den Knoten C und D repliziert (siehe Abb. 2)
Abbildung 2: Replikation von Datensätzen (Abb. aus [2])
4.2. Vector Clocks
Durch die verringerten Anforderungen an die Konsistenz kann es zu unterschiedlichen Versionen
eines Datensatz auf verschiedenen Knoten kommen. Amazon hat Untersuchungen gemacht, um
festzustellen, wie oft es zu unterschiedlichen Versionen kommt. Dabei wurde festgestellt, dass
der Einkaufswagen-Dienst in 24 Stunden bei 99,94% der Anfragen, nur ein Version gesehen hat
(vgl. [2], Seite 217). Wenn jedoch verschiedene Versionen zurückgegeben werden, muss
bestimmt werden, welches die aktuelle Version ist, hierzu werden Vector Clocks verwendet, die
vom Prinzip her einfache Versionszähler sind.
Zu jeder Version eines Datensatzes gibt es eine Liste, die Paare der Art [Knoten, Zähler] enthält.
Bei jedem Update des Datensatzes wird der Zähler des koordinierenden Knotens um eins erhöht
(vgl. [8], Seite 3f). Anhand dieser Liste kann bestimmt werden, ob zwei Versionen auf einander
aufbauen oder ob sie unabhängig voneinander entstanden sind. Wenn in Version A alle Zähler
kleiner oder gleich zu denen in Version B sind, dann ist Version B aus Version A entstanden und
Version A kann verworfen werden (vgl. [4], Seite 559f). Wenn dieses nicht der Fall ist, muss
entschieden werden wie mit den beiden Versionen umgegangen werden soll. Sollen die beiden
Versionen zusammengeführt werden oder wird nur die genutzt, die z.B. zuletzt geschrieben
wurde.
Jana Stehmann
Thema 3.2 Dynamo
Seite 8
Durch das Zusammenführen verschiedener Versionen kann es bei dem Einkaufswagen-Dienst
dazu kommen, dass Dinge, die aus einem Einkaufswagen gelöscht wurden, mit einem mal
wieder auftauchen, aber Artikel die in den Einkaufswagen gelegt wurden, werden nie einfach so
verschwinden.
Normalerweise wird ein Datensatz immer von einem der ersten Knoten aus der Präferenzliste
aktualisiert (Koordinator-Knoten). Wenn diese jedoch ausfallen, übernimmt ein anderer Knoten
die Koordination und wird der Versionsliste hinzugefügt, somit kann die Liste wachsen. Dynamo
speichert daher für jedes Paar den Zeitstempel der letzten Änderung. Wenn eine Grenze erreicht
wird, wird das älteste Paar aus der Liste entfernt.
Beispiel (siehe Abb. 3):
- Knoten Sx schreibt einen Datensatz -> ([Sx, 1])
- Knoten Sx aktualisiert diesen Datensatz -> ([Sx, 2])
- Knoten Sx fällt aus
- Knoten Sy aktualisiert diesen Datensatz -> ([Sx, 2], [Sy, 1])
- Gleichzeitig: Knoten Sz aktualisiert diesen Datensatz -> ([Sx, 2], [Sz, 1])
- Knoten Sx ist wieder verfügbar
- Die beiden unterschiedlichen Version werden von einer Anwendung gelesen, von dieser
zusammengeführt und von Knoten Sx aktualisiert -> ([Sx, 3], [Sy, 1], [Sz, 1])
Abbildung 3: Versionsverwaltung (Abb. aus [2])
Jana Stehmann
Thema 3.2 Dynamo
Seite 9
4.3. Sloppy Quorum und Hinted Handoff
Um die Ausfallsicherheit zu erhöhen und gleichzeitig eine gute Performance zu gewährleisten,
wurden zusätzlich zu dem Parameter N, der bestimmt auf wie viele Knoten die Daten repliziert
werden, die Parameter R (Lesen) und W (Schreiben) eingeführt, da eine Operation nur so schnell
sein kann, wie der langsamste Knoten. Diese sind ebenfalls konfigurierbar und bestimmen wie
viele Knoten beteiligt sein müssen, um eine Lese- bzw. Schreiboperation erfolgreich durchführen
zu können. N sind hierbei nicht fest definierte Knoten sondern die ersten Erreichbaren der
Präferenzliste (Sloppy Quorum).
Dabei gilt: R + W > N
Die Standardkonfiguration für das Tupel (N, R, W) ist (3, 2, 2) (vgl. [2], Seite 215). Diese
besagt, dass
- ein Datensatz auf drei Knoten gespeichert wird
- ein Lesezugriff erfolgreich ist, wenn mindestens zwei dieser Knoten Daten liefern
- ein Schreibzugriff erfolgreich ist, wenn mindestens zwei dieser Knoten die neuen Daten
schreiben konnten (vgl. [5])
Wenn ein Knoten ausfällt, werden die Daten an den ersten Knoten der Präferenzliste
weitergegeben (Hinted Handoff), auf dem die Daten noch nicht repliziert sind. An diesem
Knoten werden die Daten in einer separaten Datenbank gespeichert und mit einem Vermerk
gekennzeichnet, in dem steht, auf welchem Knoten die Daten eigentlich gespeichert sein sollen.
Sobald der ausgefallene Knoten wieder verfügbar ist, werden die Daten an diesen zurückgegeben
und aus dem „Zwischenspeicher“ gelöscht.
4.4. Anti-Entropie durch Merkle-Bäume
Wenn ein Knoten, der noch Hinted Handoff Daten bei sich gespeichert hat, ausfällt, kann dieses
dazu führen, dass der Original-Knoten bei einem Neustart möglicherweise veraltete Daten
gespeichert hat. Daher führt jeder Knoten bei einem Neustart einen Abgleich seiner Daten mit
den anderen Knoten, die dieselben Daten repliziert haben, durch.
Um die daraus resultierende Netzwerkbelastung möglichst gering zu halten werden MerkleBäume verwendet. Dieses sind Hash-Bäume die in ihren Blättern die Hashwerte der Datensätze
speichern und in den Vaterknoten den Hashwert über die darunterliegenden Hashwerte. Dieses
setzt sich bis in die Wurzel fort (vgl. [9], Kapitel 3).
Für jeden Schlüsselwertebereich, den ein Knoten verwaltet, gibt es einen separaten Baum. Bei
einem Vergleich der Merkle-Bäume zweier Knoten wird mit der Wurzel begonnen. Wenn die
Wurzeln dieselben Hashwerte haben, dann sind alle Datensätze gleich und der Vergleich kann
beendet werden. Wenn die Wurzeln unterschiedliche Werte haben, werden die Knoten der
nächsten Ebenen verglichen, bis festgestellt wird, in welchem Blatt die unterschiedlichen Daten
liegen. Durch dieses Verfahren werden immer nur Teilbäume miteinander verglichen und nicht
der komplette Datenbestand.
Wenn Knoten dem Ring beitreten oder aus ihm entfernt werden, verändern sich die
Schlüsselbereiche, die ein Knoten verwaltet, und die Bäume müssen komplett neu aufgebaut
werden.
Jana Stehmann
Thema 3.2 Dynamo
Seite 10
4.5. Gossip-basiertes Protokoll
Wenn ein Knoten dem Ring dauerhaft hinzufügt oder daraus entfernt werden soll, wird diese
Änderung von einem Administrator explizit durchgeführt und geschieht nicht automatisch.
Hierzu verbindet sich der Administrator über die Kommandozeile oder eine Web-Oberfläche mit
diesem Knoten und gibt den entsprechenden Befehl ein. Die Änderung und der Zeitpunkt werden
gespeichert. Für jeden Knoten gibt es eine Historie über die Bei- und Austritte.
Die Änderung wird über ein Gossip-basiertes Protokoll an alle anderen Knoten übermittelt. Jeder
Knoten tauscht seine Informationen jede Sekunde mit einem beliebigen anderen Knoten aus, es
gibt keine festgelegte Reihenfolge. Dieses Verfahren führt erst nach einiger Zeit zu einem
konsistenten Ring. In der Zwischenzeit gibt es Knoten die noch nichts darüber wissen, dass ein
Knoten dem Ring beigetreten oder aus ihm entfernt worden ist. Besonders in großen Netzen
kann der Austausch der Informationen sehr lange dauern.
Um dieses zu vermeiden gibt es sogenannte „Seeds“. Seeds sind normale Knoten, die aber in
eine spezielle Konfigurationsdatei eingetragen sind, dadurch sind sie allen anderen Knoten
bekannt und werden bevorzugt abgefragt. Die Änderungen in der Zusammensetzung des Ringes
werden somit deutlich schneller bekannt.
Jana Stehmann
Thema 3.2 Dynamo
Seite 11
5. Optimierungen
Um die Performance zu steigern hat Amazon im Laufe der Zeit einige Veränderungen /
Optimierungen an Dynamo vorgenommen.
5.1. Verbesserte Partitionierung
Die unter 4.1 vorgestellte Verteilung der Daten auf viele Knoten die einen zufälligen Bereich
(Partition) abdecken (vgl. Abb. 4), hat sich im Betrieb als nicht optimal herausgestellt. Immer
dann, wenn ein Knoten hinzugefügt wird, müssen die
Nachbarn ihre eigenen Daten scannen und die entsprechenden
Datensätze an den neuen Knoten übergeben. Das Scannen der
Daten kann unter Umständen bis zu einem Tag dauern, da der
Scanprozess den Produktivbetrieb nicht stören darf und daher
im Hintergrund mit einer niedrigen Priorität laufen muss.
Außerdem müssen bei jedem Hinzufügen oder Entfernen von
Knoten die Merkle Bäume auf vielen Knoten neu aufgebaut
werden, da die Daten nicht nur auf den direkten Nachbarn
liegen, sondern auch über einige Knoten repliziert sind.
Abbildung 4: alte Strategie (Abb. aus [2])
Um diese Situation zu verbessern, hat Amazon die Aufteilung der Daten so verändert, dass die
Partitionen nicht mehr eine zufällige, sondern eine feste Größe haben. Der gesamte Hashbereich
wird dabei in Q gleichgroße Partitionen zerlegt (vgl. Abb. 5). Jedem Knoten werden dann Q/S
Partitionen zugewiesen. S ist hierbei die Anzahl der
Knoten im System.
Wenn jetzt ein Knoten entfernt wird, werden die auf ihm
gespeicherten Partitionen auf alle verbleibenden Knoten
verteilt. Dieses geht deutlich schneller als die alte
Methode, da nun ganze Partitionen am Stück verschoben
werden und nicht einzelnen Datensätze.
Aus Sicherheitsgründen werden alle Daten bei Amazon
regelmäßig gesichert. Dieses Backup geht mit der neuen
Strategie ebenfalls deutlich schneller, da wie beim
Hinzufügen von Knoten, ganze Partitionen gesichert
werden und nicht die einzelnen Datensätze bei den Knoten
angefragt werden müssen.
Abbildung 5: neue Strategie (Abb. aus [2])
5.2. Quorumanpassungen
Das Standardquorum von (3, 2, 2) ist für die meisten Amazon Dienste ausreichend, einige
Dienste stellen aber besondere Anforderungen an die Lese- oder Schreibgeschwindigkeit. Für
diese Dienste gibt es die Möglichkeit die Werte für R und W so zu verändern, dass nur ein
Knoten die Aufgabe erfolgreich zurück melden muss.
Wenn eine Anwendung vor allem Daten liest (z.B. der Produktkatalog) und selten schreibt, kann
die Konfiguration bspw. zu (3, 1, 3) geändert werden. Damit muss bei einem Lesezugriff nur ein
Knoten Daten liefern, wodurch die Daten schneller bereitgestellt werden können und es weniger
Jana Stehmann
Thema 3.2 Dynamo
Seite 12
Probleme macht, wenn mehrere Knoten ausfallen. Bei einem Schreibzugriff müssen aber alle
Knoten die neuen Daten schreiben können.
Auch der umgekehrte Fall, dass die Konfiguration zu (3, 3, 1) geändert wird, ist denkbar.
Dadurch entsteht ein immer schreibbares System, solange mindestens ein Knoten verfügbar ist.
Bei so einem System steigt die Gefahr der inkonsistenten Daten und der Aufwand für die
nachträgliche Synchronisation erhöht sich.
5.3. Anfragesteuerung
Die Anfrage eines Datensatzes kann entweder Server- oder Client-gesteuert sein. Bei der
Servergesteuerten Anfragebearbeitung sendet der Client seine Anfrage zunächst an einen
Lastverteiler (Load Balancer), dieser schickt die Anfrage an einen beliebigen, wenig belasteten,
Knoten weiter.
Jeder Knoten enthält eine Komponente zur Anfragesteuerung. Leseanfragen können von allen
Knoten koordiniert werden, Schreibanfragen jedoch nur von den Knoten, die sich in der
Präferenzliste für diesen Datensatz befinden.
Eine Lese- oder Schreibanfrage besteht aus den folgenden Schritten:
- Die Anfrage wird an die betreffenden Knoten geschickt
- Es wird auf die minimal nötige Anzahl an Antworten gewartet
- Wenn nicht genügend Antworten eintreffen, wird die Anfrage mit einem Fehler zurück
gegeben
- Ansonsten werden aus den gesammelten Informationen die Versionen ausgesucht die
zurück gegeben werden sollen
- Wenn ein Knoten veraltete Informationen gesendet hat, werden die aktuellen Daten an
diesen geschickt
Bei der Clientgesteuerten Anfragebearbeitung hingegen, werden die oben genannten Schritte
nicht auf einem der Knoten, sondern direkt durch den Client ausgeführt. Hierzu fragt der Client,
alle 10 Sekunden, einen beliebigen Knoten, welche Knoten für welche Schlüsselbereiche
verantwortlich sind. Durch dieses Wissen kann der Client seine Anfrage direkt an die
verantwortlichen Knoten schicken und spart sich den extra Schritt über den Lastverteiler.
Aufgrund der gleichmäßigen Verteilung der Datensätze über alle Knoten kommt es auch bei
diesem Ansatz zu einer Art Lastverteilung.
Abbildung 6 zeigt, dass der Clientgesteuerte Ansatz bei der Anfragebearbeitung im Mittel nur
halb so viel Zeit wie der Servergesteuerte benötigt.
Abbildung 6: Anfragesteuerung (Abb. aus [2])
Jana Stehmann
Thema 3.2 Dynamo
Seite 13
6. Zusammenfassung
Amazon ist es durch die Verknüpfung der verschiedenen Verfahren gelungen, ein hoch
verfügbares und leistungsfähiges System zu installieren. Die Speicherung der Daten durch ein
Key-Value-Store Verfahren ist schnell und einfach zu implementieren, da der Zugriff auf die
einzelnen Datensätze immer über den Primärschlüssel erfolgt. Der Nutzer bekommt alle
Informationen schnell dargestellt.
Durch die verwendeten Verfahren zur Skalierung und Replikation ist Amazon in der Lage, auch
hohe Anzahlen von Bestellungen abwickeln zu können, ohne an die Grenzen der
Leistungsfähigkeit der Server zu kommen. Ebenso kann der Ausfall von Hardware oder ganzen
Rechenzentren relativ problemlos überwunden werden.
Jana Stehmann
Thema 3.2 Dynamo
Seite 14
Literaturliste
[1]
Rick Cattell. Scalable sql and nosql data stores. SIGMOD Record, 39(4):12{27, 2010
[2]
Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati,
Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter Vosshall,
and Werner Vogels. Dynamo: amazon's highly available key-value store. SIGOPS
Oper. Syst. Rev., 41(6):205{220, October 2007
[3]
David R. Karger, Eric Lehman, Frank Thomson Leighton, Rina Panigrahy,
Matthew S. Levine, and Daniel Lewin. Consistent hashing and random trees: Distributed
caching protocols for relieving hot spots on the world wide web. In STOC,
pages 654{663, 1997
[4]
Leslie Lamport. Time, clocks, and the ordering of events in a distributed system.
Commun. ACM, 21(7):558{565, 1978.
[5]
Amazon Dynamo, http://de.wikipedia.org/wiki/Amazon_Dynamo, Stand 27.03.2013
[6]
Amazon Pressemitteilung, http://www.amazon.de/gp/press/pr/20091226, 26.12.2009
[7]
Amazon.co.uk Overview, http://phx.corporate-ir.net/phoenix.zhtml?c=251199&p=irolmediaOverview, Stand 07.06.2013
[8]
Roberto Baldoni and Michel Raynal. Fundamentals of distributed computing: A practical
tour of vector clock systems. IEEE Distributed Systems Online, 3(2), 2002.
[9]
Mykletun, E., Narasimha, M., and Tsudik, G., "Providing Authentication and Integrity in
Outsourced Databases UsingMerkle Hash Trees", UCI-SCONCETechnical Report, 2003
FernUniversität in Hagen
–
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 3.3
Bigtable
Referent: Felix Siegrist Michael
Inhaltsverzeichnis
Inhaltsverzeichnis
1 Motivation und Ziele
1.1 Ausgangslage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Ziele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
4
4
2 Bigtable – eine erste Annäherung
4
3 Datenmodell
3.1 Zeilen . . . . . . . . . . .
3.1.1 Tablets . . . . . .
3.2 Spaltenfamilien / Spalten
3.3 Timestamps . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
6
7
4 API
4.1
4.2
4.3
4.4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
7
8
8
.
.
.
.
9
9
9
10
10
Metadaten . . . . . . .
Schreibzugriffe . . . .
Lesezugriffe – Scanner
MapReduce . . . . . .
.
.
.
.
.
.
.
.
5 Bausteine
5.1 GFS . . . . . . . . . . . .
5.2 SSTable . . . . . . . . . .
5.3 Lockservice – Chubby . .
5.4 Clusterverwaltungssystem
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6 Organisation eines Bigtable Clusters
10
6.1 Masterserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
6.2 Tabletserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
7 Tablets
7.1 Lokalisierung von Tablets .
7.2 Zuweisung zu Tabletservern
7.3 Interne Organisation . . . .
7.3.1 Migration . . . . . .
7.3.2 Verdichtung . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
13
13
14
14
8 Maßnahmen zur Effizienzsteigerung
8.1 Lokalitätsgruppen . . . . . . . .
8.2 Komprimierung . . . . . . . . .
8.3 Bloomfilter . . . . . . . . . . .
8.4 Gemeinsame Logdateien . . . .
8.4.1 Recovery . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15
15
15
16
16
16
. . . . .
. . . . .
. . . . .
. . . . .
Engine .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
17
17
17
17
18
18
9 Bigtable im Einsatz
9.1 Crawler (Web-Suche)
9.2 Google Analytics . .
9.3 Google Earth . . . .
9.4 Personalisierte Suche
9.5 Datastore der Google
. . .
. . .
. . .
. . .
App
.
.
.
.
.
3
2 Bigtable – eine erste Annäherung
1 Motivation und Ziele
Gegen Ende des Jahres 2003 startete Google mit dem Design und der Implementierung von Bigtable,
einem eigenen verteilten Speichersystem für diverse interne Projekte.
1.1 Ausgangslage
Die Projekte bei Google zeichnen sich typischerweise durch enorme zu verwaltende Mengen von
mäßig strukturierten Daten aus, wie zum Beispiel:
• Milliarden von URLs1 und zugehörigem Seiteninhalt in jeweils mehreren Versionen, sowie
diverse durch den Crawler gesammelte Metadaten;
• über 100 Millionen Benutzer, die über 1000 Suchabfragen pro Sekunde tätigen1 und weitere
benutzerspezifische Daten hinterlegen;
• 100 TB Satellitenbilddaten1 und zugehörige geographische Daten, sowie zusätzliche Informationen wie z.B. durch Benutzer hinzugefügte Annotationen.
1.2 Ziele
Einige der Hauptanforderungen von Google an ein Speichersystem waren:
• zuverlässige Skalierbarkeit auf Petabytes von Daten und tausende verteilter Rechner;
• breites Anwendungsspektrum, d.h. die Speicherlösung sollte allgemein genug gehalten sein,
um von möglichst vielen Projekten genutzt werden zu können, egal ob diese besonders hohen
Datendurchsatz (z.B. Webcrawler bei der Arbeit) oder besonders rasche Antwortzeiten (z.B.
Benutzer, die eine Suchabfrage starten) benötigen;
• hohe Performance, d.h. Unterstützung hoher Schreib- und Leseraten (Millionen von Operationen pro Sekunde), möglichst kurze Antwortzeiten, effiziente Scans über interessante Bereiche;
• hohe Verfügbarkeit, d.h. ein Zugriff auf die aktuellen Daten soll zu jeder Zeit möglich sein
trotz immer wieder auftretender Fehler auf den Speicherplatten, auf den Servern oder generell
im Netzwerk;
• asynchrone Prozesse müssen laufend unterschiedliche Bereiche der Daten aktualisieren können;
• die Entwicklung der Daten über die Zeit soll berücksichtigt werden.
Google entschied sich, ein eigenes Speichersystem zu implementieren, denn die Kosten, um die
oben genannten Datenmengen mit einer kommerziellen Datenbank effizient zu bearbeiten, wären
enorm. Bei einem eigenen System können Low-Level Speicheroptimierungen, die die Performance
oft entscheidend verbessern, viel unkomplizierter vorgenommen werden. Außerdem kann ein eigenes
Produkt in vielen Projekten eingesetzt werden, ohne jedes Mal Lizenzkosten nach sich zu ziehen.
2 Bigtable – eine erste Annäherung
Der Name „Bigtable“, sowie die im Datenmodel verwendeten Begriffe „Zeile“ und „Spalte“ sind leider
etwas irreführend. Bigtable liegt kein relationales Datenmodell zugrunde, wie man es von relationalen Datenbanken her kennt. Eine Tabelle in Bigtable wird von Google denn auch beschrieben
als „sparse, distributed, persistent, multidimensional, sorted map“ [Cha+08], also frei übersetzt als
dünnbesetzte, verteilte, persistente, mehrdimensionale, sortierte Map. Wir schauen uns die Bedeutung dieser Begriffe etwas genauer an2 .
1
2
4
Dea05.
Krz11.
Map Eine Tabelle in Bigtable entspricht also eigentlich der Datenstruktur „Map“ (auch Dictionary
oder assoziatives Array genannt), in der die Daten über einen Schlüssel abgerufen werden. Die
Informationen werden in Schlüssel-Wert-Paaren abgelegt. In Bigtable besteht der Schlüssel aus
einem (Zeile, Spalte, Timestamp)-Tripel (vgl. Abschnitt 3).
Verteilt Bigtable verteilt seine Daten auf viele (oft tausende) Rechner. Getrennt wird zwischen
zwei Zeilen. Eine einzelne Zeile wird also nie auf verschiedene Maschinen verteilt. Eine Maschine
verwaltet mehrere Gruppen von aufeinanderfolgenden Zeilen.
Sortiert Maps sind typischerweise nicht sortiert. Die Position der Werteinträge in der Map wird
stattdessen meist über einen Hashwert der zugehörigen Schlüssel bestimmt. In Bigtable werden
die Daten nach den Zeilennamen sortiert gespeichert. Dadurch kann durch geschickte Wahl der
Zeilennamen erreicht werden, dass zusammengehörende Daten auch nahe beieinander (oft auf der
selben Maschine – Stichwort Lokalität) gespeichert werden.
Mehrdimensional Eine Tabelle besteht aus Zeilen. Jede Zeile enthält eine bis viele Spaltenfamilien, von denen jede wiederum eine bis viele Spalten enthalten kann. Außerdem kann jede Spalte
mehrere Versionen ihres Werteintrages enthalten, die jeweils über einen zugehörigen Timestamp
identifiziert werden. Zeile, Spaltenfamilie, Spalte und Timestamp bilden somit einen mehrdimensionalen Zugriffspfad auf die Daten.
Dünnbesetzt Die einzelnen Zeilen einer Tabelle in Bigtable können ganz unterschiedliche Spalten
benutzen. Oder anders ausgedrückt: wenn man sich die Daten tabellarisch angeordnet vorstellt und
somit alle Zeilen die gleichen Spalten haben, so sind in jeder Zeile typischerweise nur sehr wenige
Spalten tatsächlich belegt. Die meisten Spalten bleiben leer.
Persistent Die Daten werden selbstverständlich persistent auf Platten gespeichert.
3 Datenmodell
Die Daten sind in Bigtable in drei Dimensionen organisiert: Zeilen, Spalten und Timestamps. Im
Schnittpunkt von Zeilen und Spalten liegen die Zellen. Jede Zelle kann ihren Speicherwert in mehreren Versionen enthalten. Die einzelnen Versionen entsprechen dem Wert zu einem bestimmten
Zeitpunkt. Die Werte selbst sind einfache Zeichenketten. Es bleibt dem Anwendungssystem überlassen, diese falls nötig geeignet zu strukturieren. Auch Zeilen- und Spaltenbezeichnungen sind
einfache Zeichenketten. Die Timestamps werden als 64-Bit Ganzzahlwerte dargestellt. Insgesamt
kann die Map also wie folgt definiert werden:
(row:string, column:string, time:int64) → string
Als erläuterndes Beispiel dient im Folgenden eine Tabelle, die Informationen zu Webseiten speichert. Als Zeilenschlüssel werden die URLs verwendet. Verschiedene Aspekte der Webseiten dienen
als Spaltenschlüssel. So wird zum Beispiel der HTML-Quelltext der Seite in der Spalte content:
hinterlegt. Dies jeweils in mehreren Versionen, welche den jeweiligen Zustand der Seite zum Zeitpunkt, als die Seite vom Google-Crawler besucht und eingelesen wurde, darstellen und über die
dritte Schlüsselkomponente, den Timestamp indiziert werden (vgl. Abb. 1).
3.1 Zeilen
Bigtable speichert die Daten lexikographisch sortiert nach Zeilenschlüssel, die Zeilennamen bilden
also das Sortierkriterium. Zeilennamen sind beliebig wählbare Zeichenketten. Es bleibt dem Anwendungssystem überlassen, diese geeignet zu wählen, sodass semantisch zusammengehörende Daten
5
3 Datenmodell
Abbildung 1: Ausschnitt aus einer Bigtable Tabelle, die Informationen zu Webseiten speichert
auch nahe beieinander gespeichert werden. Im Beispiel aus Abbildung 1 werden die URLs der Seiten
in umgekehrter Schreibweise, also beginnend mit der Toplevel-Domain gewählt. Dadurch werden Seiten aus der selben Domain benachbart abgespeichert, was entsprechende Abfragen effizienter macht
(z.B. Suchoptionen „nur Seiten aus Deutschland anzeigen“ oder „site: fernuni-hagen.de“).
Zeilen bilden in Bigtable die Einheiten transaktionaler Konsistenz, d.h. der Zugriff auf die Daten
einer Zeile ist atomar, egal wieviele Spalten darin enthalten sind. Transaktionaler Zugriff auf Daten
mehrerer Zeilen wird jedoch nicht unterstützt.
Die Erzeugung einer neuen Zeile geschieht implizit beim Schreiben eines Wertes unter einem
(Zeile, Spalte, Timestamp)-Schlüssel mit einem Zeilennamen, der bisher noch nicht in der Tabelle
enthalten war.
3.1.1 Tablets
In Sortierreihenfolge benachbarte Zeilen werden in sogenannten Tablets gruppiert. Diese enthalten
also jeweils einen zusammenhängenden Bereich von Zeilen. Tablets werden als Ganzes auf Tabletserver (vgl. Abschnitt 6.2) verteilt. Dadurch werden Abfragen, die nur kleine zusammenhängende
Zeilenschlüsselbereiche betreffen, sehr effizient, da nur wenige Tabletserver mit einbezogen werden
müssen. Auf Tablets wird in Abschnitt 7 noch genauer eingegangen.
3.2 Spaltenfamilien / Spalten
Eine Tabelle kann unbegrenzt viele Spalten enthalten. Diese werden in Spaltenfamilien gruppiert,
von denen es optimalerweise nicht mehr als ein paar hundert pro Tabelle gibt. Die Daten innerhalb
einer Spaltenfamilie sind üblicherweise vom selben semantischen Typ.
Spaltenfamilien gehören zum Schema der Tabelle und müssen explizit definiert und erzeugt werden, bevor Daten darin abgelegt werden können. Ebenso können auch ganze Spaltenfamilien wieder
gelöscht werden, indem das Schema entsprechend verändert wird. Spaltenfamilien bilden so eine Art
Zugriffskontrolleinheit der Tabelle.
Spaltenschlüssel folgen der Syntax Spaltenfamilie:Qualifier, wobei der Qualifier auch entfallen
kann. Im Beispiel aus Abbildung 1 haben die Spaltenfamilien contents und lang jeweils keinen
Qualifier. Erstere enthält den Webseitenquelltext, letztere den Sprachcode der Seite. Die Spaltenfamilie anchor speichert Verweise (Links) auf die jeweilige Seite. Der Qualifier bezeichnet dabei die
URL der verweisenden Seite, während der Wert in der Zelle dem dort angezeigten Linktext entspricht. Auf der Seite de.wikipedia.org/wiki/... gibt es also einen Verweis auf die Seite
www.fernuni-hagen.de. Der Text des Verweises lautet „Fernuniversität in Hagen“.
Nun wird auch die Bedeutung des Begriffs „dünnbesetzt“ (sparse) aus Abschnitt 2 deutlich, wenn
6
3.3 Timestamps
man sich vorstellt, dass es auf die Homepage der Fernuniversität in Hagen wahrscheinlich tausende
Verweise gibt, deren Spaltenschlüssel aber nicht übereinstimmen mit entsprechenden Spaltenschlüsseln einer anderen Zeile und somit die meisten Spalten einer Zeile leer bleiben. Doch wie gesagt
handelt es sich bei der Datenstruktur ja nicht eigentlich um eine Tabelle, sondern um eine Map, in
der es dann auch keine leeren Zellen gibt.
3.3 Timestamps
Um unterschiedliche Versionen bestimmter Daten in den Zellen speichern und verwalten zu können,
werden die Schlüsselbestandteile „Zeile“ und „Spalte“ um eine dritte, zeitliche Komponente ergänzt:
die Timestamps. Wird beim Schreiben neuer Daten kein Timestamp mitgegeben, so wird implizit
die aktuelle Zeit gesetzt. Benutzeranwendungen können aber auch explizit eine bestimmte Zeit
mitgeben. Die einzelnen Versionen werden in bzgl. Timestamp absteigender Reihenfolge in den
Zellen gespeichert, sodass die aktuellste Version jeweils zuerst gelesen werden kann. Beim lesenden
Zugriff bestehen Suchoptionen wie z.B. „gib die k letzten Einträge“ oder „gib alle Einträge innerhalb
des Zeitintervalls [tstart . . . tende ]“.
Im Beispiel aus Abbildung 1 enthalten einzelne Zellen der Spalte contents: jeweils mehrere Versionen der Webseiten, also den Stand der Webseiten zu den Zeitpunkten, an denen sie vom Crawler
besucht und eingelesen wurden.
Timestamps können außerdem für eine Art „Garbage Collection“ benutzt werden, indem bei der
Definition von Spaltenfamilien auf diesen entsprechende Attribute wie „behalte jeweils nur die k
letzten Einträge einer Zelle“ oder „behalte Zelleinträge nur solange, bis sie älter als n Tage sind“
gesetzt werden.
4 API
Der Zugriff auf Bigtable wird Anwendungsprogrammen durch eine entsprechende Benutzerbibliothek ermöglicht. Da es sich bei Bigtable um ein Google-internes, proprietäres Produkt handelt,
findet man kaum Beschreibungen der Programmierschnittstelle (API). Die folgenden Beispiele für
Schreib- und Lesezugriffe sind direkt dem dieser Arbeit zugrunde liegenden White-Paper3 von Google entnommen.
4.1 Metadaten
Zu den die Metadaten manipulierenden Operationen, die durch das API angeboten werden, gehören
unter anderem solche zum Erzeugen, Löschen und Modifizieren von Tabellen, sowie zum Anlegen,
Entfernen und Ändern von Spaltenfamilien. Auch Zugriffsrechte können verwaltet werden.
4.2 Schreibzugriffe
Schreibzugriffe betreffen jeweils einzelne Zeilen und sind atomar. Dazu gehören:
• Set() – Schreiben von Zellen in eine Zeile
• Delete() – Löschen von Zellen einer Zeile, oder Löschen aller Zellinhalte einer Zeile, die zu
einem bestimmten Timestamp-Intervall gehören
• DeleteRow() – Löschen aller Zellen einer Zeile, also Löschen der ganzen Zeile
Listing 1 zeigt ein Beispiel, wie über ein RowMutation Objekt mehrere Operationen, die eine Zeile
verändern, gesammelt und dann beim Aufruf von Apply atomar ausgeführt werden.
3
Cha+08.
7
4 API
Listing 1: Beispiel in C++ für schreibenden Zugriff
Table *T = OpenOrDie("/bigtable/web/webtable");
RowMutation rm(T, "de.fernuni-hagen.www");
rm.Set("anchor:www.fernuni-hagen.de/mathinf/", "Home");
rm.Delete("anchor:de.wikipedia.org");
Operation op;
Apply(&op, &rm);
4.3 Lesezugriffe – Scanner
Lesezugriffe werden über das Konzept einen Scanners umgesetzt. Dieser ermöglicht Zugriff auf beliebige Zellen der Tabelle. Wird eine einzelne Zeile gelesen, erfogt dieser Zugriff wieder atomar.
Die Anfrage kann die zurückgegebenen Zeilen auf einen bestimmten Bereich einschränken oder alle
Zeilen anfordern. Von einer Zeile können jeweils die Daten aller Spalten oder nur bestimmter Spaltenfamilien bzw. bestimmter Spalten zurückgegeben werden. Ebenso kann auch der zeitliche Bereich
der zurückgelieferten Daten über entsprechende Timestamp-Angaben eingeschränkt werden. In den
Suchkriterien der Abfragen werden Wildcards und Reguläre Ausdrücke unterstützt.
Listing 2 zeigt ein Beispiel, wie mit Hilfe des Scanners über alle Spalten der Spaltenfamilie
anchor: einer Zeile iteriert werden kann.
Listing 2: Beispiel in C++ für lesenden Zugriff
Scanner scanner(T);
ScanStream *stream;
stream = scanner.FetchColumnFamily("anchor");
stream->SetReturnAllVersions();
scanner.Lookup("de.fernuni-hagen.www");
for (; !stream->Done(); stream->Next()) {
printf("%s %s %11d %s\n",
scanner.RowName(),
stream->ColumnName(),
stream->MicroTimestamp(),
stream->Value());
}
Joins über die Daten mehrerer Spalten aus verschiedenen Zeilen werden nicht direkt unterstützt.
Dazu muss man den Scanner parallel verschiedene Abfragen ausführen lassen und diese „manuell“
auf dem Client joinen. Ähnlich wie bei der Stromverarbeitung von Operatoren traditioneller relationaler Datenbanken muss auch hier nicht die vollständige Ausgabe des Scanners abgewartet werden,
sondern für jede zurückgelieferte Zeile kann ein weiterer Scanner mittels Direktabfragen zusätzliche
Daten holen, die dann wieder „manuell“ auf dem Client mit den Daten der aktuellen Zeile des ersten
Scanners gejoint werden.
4.4 MapReduce
MapReduce [DG08] gehört nicht eigentlich zum API von Bigtable, harmoniert aber insofern sehr gut
mit Bigtable, als dass einerseits Bigtable Ausgaben als Eingabe von MapReduce-Prozessen verwendet und mit MapReduce-Techniken weiterverarbeitet werden können und andererseits Ergebnisse
8
von MapReduce-Verarbeitungen Eingabe von Bigtable-Schreibbefehlen sein können. Eigens zu diesem Zweck wurden entsprechende Wrapper-Klassen geschrieben. Die automatische und effiziente
Parallelisierung und Verteilung, die durch den Einsatz von MapReduce erreicht wird, passt sehr gut
zur parallelen und verteilten Arbeitsweise von Bigtable.
5 Bausteine
Bigtable bedient sich diverser bei Google bereits bestehender Technologien und Infrastrukturbausteine. In den folgenden Abschnitten sollen die wichtigsten davon kurz umrissen werden.
5.1 GFS
Zur physischen Speicherung der Daten benutzt Bigtable das Google File System (GFS) [GGL03].
GFS ist ein verteiltes Filesystem. Die Dateien werden in sogenannten Chunks von jeweils 64 MB
Größe auf Chunkservern gespeichert. Zu jedem Chunk gibt es mehrere (üblicherweise drei) Kopien
auf unterschiedlichen Chunkservern. Dadurch wird die Ausfallsicherheit und Verfügbarkeit erhöht.
Ein Masterserver kennt die Adressen der Chunkserver und weiß, welche Chunks auf den jeweiligen
Chunkservern gespeichert sind. Clients, die ein bestimmtes Chunk lesen wollen, fragen den Master
nach der Adresse des zuständigen Chunkservers. Für den eigentlichen Datenaustausch kommuniziert
der Client dann aber direkt mit dem Chunkserver (vgl. Abb. 2).
Abbildung 2: Google File System – Übersicht
5.2 SSTable
Das Fileformat, welches von Bigtable zur Speicherung der Daten verwendet, ist ebenfalls eine Eigenentwicklung von Google namens SSTable.
Logisch gesehen ist eine SSTable eine sortierte, unveränderbare Map von Schlüssel/Wert-Paaren,
wobei sowohl Schlüssel als auch Werte beliebige Zeichenketten (strings) sind (daher auch der Name:
S(orted)-S(tring)-Table). Einmal erstellt, wird eine SSTable nie mehr verändert. Sollen zusätzliche
Daten gespeichert werden, so wird eine neue SSTable erstellt und die alte bei Gelegenheit gelöscht.
Physisch besteht eine SSTable aus einer Sequenz von Blöcken (Defaultgröße 64 KB) und einem
Blockindex am Ende der SSTable (vgl. Abb. 3).
Der Blockindex wird vollständig in den Hauptspeicher geladen, sobald die SSTable geöffnet wird.
Dadurch benötigt das Einlesen eines bestimmten Blocks nur einen Plattenzugriff. Die Adresse des
richtigen Blocks wird zuvor mittels binärer Suche im Blockindex im Hauptspeicher ermittelt. Optional kann eine SSTable auch vollständig in den Hauptspeicher geladen werden.
9
6 Organisation eines Bigtable Clusters
Abbildung 3: Aufbau einer SSTable
5.3 Lockservice – Chubby
Die Synchronisation der Zugriffe auf die verteilten Ressourcen wird über einen Lockservice namens
Chubby [Bur06] gelöst. Chubby bietet eine Schnittstelle ähnlich der eines Filesystems an. So werden
Verzeichnisse und Dateien als Locks verwendet. Clients halten einen Lock, wenn sie den entsprechenden Dateihandle besitzen. Lese- und Schreibzugriffe für ganze Dateien sind atomar.
Neben der Synchronisation von Zugriffen auf Ressourcen über die Locks wird Chubby von Bigtable auch dazu verwendet, um Lese- und Schreibberechtigungen über Zugangskontrolllisten zu
verwalten, sowie Schemadaten und einen zentralen Einstiegspunkt für Datenzugriffe zu speichern
(vgl. Abschnitt 7.1).
5.4 Clusterverwaltungssystem
Bigtable ist ein verteiltes Speichersystem und läuft typischerweise auf hunderten bis tausenden von
Maschinen. Auf diesen Maschinen sind neben Bigtable aber meist auch noch diverse andere Dienste
und Prozesse wie GFS-Server, Applikationsserver, MapReduce-Worker, Chubby-Client u.a. aktiv.
Um all diese Dienste zu koordinieren, ist auf jeder Maschine ein Clusterverwaltungssystem installiert, welches Job-Scheduling-Aufgaben übernimmt, Ressourcen zuteilt, den Zustand der jeweiligen
Maschine überwacht und im Falle eines Fehlverhaltens entsprechend einschreitet.
6 Organisation eines Bigtable Clusters
Bigtable besteht aus drei Kernkomponenten: Einem Masterserver, vielen Tabletservern und einer
Client-Library, die in die Benutzeranwendungen eingebunden wird und diesen den Zugriff auf Bigtable ermöglicht. Daneben laufen meist auf den selben Maschinen die weiteren in Abschnitt 5 beschriebenen Dienste, auf denen Bigtable aufbaut (vgl. Abb. 4).
Bevor die Benutzeranwendung auf Tabellen eines Bigtable-Clusters zugreifen kann, muss sie diesen
öffnen. Dazu sendet die Client-Library dem Lockservice (Chubby) im Cluster einen entsprechenden
Open-Befehl und erfährt, welche Tabellen im Cluster zur Verfügung stehen. Operationen, die das
Schema einzelner Tabellen betreffen, werden durch den Masterserver ausgeführt. Lese- und Schreiboperationen werden direkt an die zuständigen Tabletserver geschickt. Die Kommunikationspfade der
einzelnen Komponenten untereinander sind in Abbildung 4 nicht eingezeichnet.
6.1 Masterserver
Es gibt eigentlich mehrere Instanzen des Masterservers, aber immer nur eine davon ist die aktuell
aktive Instanz. Bei einem Ausfall des Masters wird einfach eine der übrigen Instanzen zum aktiven
10
6.1 Masterserver
Abbildung 4: Bigtable Cluster
11
7 Tablets
Master gewählt. Der aktive Master hält einen einmaligen Masterlock auf dem Lockserver, wodurch
sichergestellt wird, dass nicht zwei Server gleichzeitig zum Master werden können.
Der Master überwacht die Menge der aktiven Tabletserver und weist diesen die einzelnen Tablets
zu. Er ist außerdem zuständig für das Loadbalancing der Tabletserver (vgl. Abschnitt 6.2). Und
schließlich werden Schemaänderungen wie das Erzeugen einer Tabelle oder einer Spaltenfamilie
durch den Master ausgeführt.
6.2 Tabletserver
Ein Tabletserver ist gewöhnlich für ungefähr 100 Tablets zuständig. Ein Tablet enthält typischerweise 100 – 200 MB Daten. Die Zahl der Tablets wächst, wenn ein Tablet zu groß wird und durch
den Tabletserver in zwei kleinere Tablets aufgeteilt wird (splitting). Geteilt wird immer an einer
Zeilengrenze, also nie mitten in einer Zeile.
Die Tablets werden zufällig auf die Tabletserver verteilt, müssen also keinen zusammenhängenden Zeilenbereich darstellen (zur Erinnerung: innerhalb eines Tablets bilden die Zeilen einen zusammenhängenden Bereich). Dadurch wird ein sehr feingranulares Loadbalancing ermöglicht. Stellt
der Masterserver nämlich fest, dass die Last auf einem gewissen Tabletserver A übermäßig steigt,
so kann er Tablets dieses Servers einem anderen, weniger stark belasteten Server B zuweisen und
dadurch die Last auf Server A reduzieren.
Fällt ein Tabletserver aus, wird durch diese Architektur außerdem ein sehr rasches Recovery ermöglicht. Die Tablets des ausgefallenen Servers werden einfach von anderen Tabletservern übernommen, und zwar von so vielen, dass jeder jeweils nur wenige Tablets oder gar nur eines übernehmen
muss.
Tabletserver helfen außerdem mit, Leseabfragen zu beschleunigen, indem sie die von den SSTables
gelieferten Daten für zukünftige Abfragen cachen.
7 Tablets
Die Zeilen einer Bigtable Tabelle werden in Tablets gruppiert. Ein Tablet enthält also alle Daten
einer Folge von benachbarten Zeilen.
7.1 Lokalisierung von Tablets
Da Tablets, wie wir gesehen haben, nicht vollkommen statisch einem Tabletserver zugeordnet sind,
sondern im Laufe der Zeit von verschiedenen Tabletservern verwaltet werden können, stellt sich
die Frage, wie ein bestimmtes Tablet für einen Lese- oder Schreibzugriff gefunden werden kann.
Ein naiver Ansatz wäre, jedesmal den Masterserver zu fragen, der die Tablets den Tabletservern ja
zuweist und daher von jedem Tablet weiß, wo es sich befindet. Bei der großen Zahl von Anfragen
würde der Masterserver dadurch sehr schnell zum Flaschenhals.
Um Tablets trotz ihrer großen Zahl effizient aufzufinden, wird daher eine Struktur ähnlich der
eines B+ -Baumes verwendet, in der die Standortinformationen hinterlegt sind (vgl. Abb. 5).
Eine Datei in Chubby enthält einen Verweis auf das Wurzel-Tablet. Dieses Tablet wird niemals
geteilt. Dadurch wird sichergestellt, dass die hierarchische Struktur zur Lokalisierung von Tablets
nie mehr als drei Ebenen zählt.
Jede Zeile des Wurzel-Tablets zeigt auf ein Tablet der Metadaten-Tabelle. Jede Zeile der MetadatenTabelle wiederum enthält die Lokationsdaten der eigentlichen Daten-Tablets in den von den Benutzeranwendungen verwendeten Tabellen.
Zu den Lokationsdaten eines Tablets gehören IP-Adresse und Port des entsprechenden Tabletservers, der Name der Tabelle, zu welcher das Tablet gehört, sowie der Schlüssel der letzten im Tablet
enthaltenen Zeile.
12
7.2 Zuweisung zu Tabletservern
Abbildung 5: Tablet-Lokalisierung
Gehen wir davon aus, dass eine Tabletzeile 1 KB groß ist und ein Tablet 128 MB Daten enthält,
so enthält ein Tablet 217 Zeilen. Bei maximaler Auslastung zeigt das Wurzel-Tablet also auf 217
Tablets in der Metadaten-Tabelle. Diese enthält dann insgesamt 234 Zeilen, was einer maximal
adressierbaren Zahl von 234 Tablets in den Benutzertabellen entspricht. Wenn auch diese Tablets
durchschnittlich 128 MB Daten (= 27 · 210 · 210 = 227 Bytes) enthalten, so gibt es in diesem Bigtable
Cluster Platz für 261 Bytes Nutzdaten.
Um die Anzahl der notwendigen Zugriffe auf diese hierarchische Struktur möglichst gering zu
halten, werden einmal gelesene Lokationsdaten in der Client-Library gecacht. Außerdem werden bei
einem Lesezugriff nicht nur die Lokationsdaten eines einzelnen Tablets übertragen, sondern immer
gleich die mehrerer Tablets (prefetching).
7.2 Zuweisung zu Tabletservern
Jeder Tabletserver erzeugt beim Start in einem bestimmten Verzeichnis von Chubby (Serververzeichnis) eine eindeutige Datei und hält einen exklusiven Lock darauf. Der Masterserver überwacht
dieses Serververzeichnis und wird so über hinzukommende oder wegfallende Tabletserver in Kenntnis gesetzt. Um möglichst rasch über den Ausfall eines Tabletserver informiert zu werden, fragt der
Master zusätzlich periodisch bei jedem Tabletserver dessen Status ab. Erreicht er einen Tabletserver
nicht, versucht er, den Lock auf dessen Datei im Serververzeichnis zu erhalten. Gelingt dies, so weiß
der Masterserver, dass der Tabletserver den Lock verloren hat. Er löscht die Datei, um sicherzustellen, dass der Tabletserver den Lock nie mehr erhalten kann. Dann weist er andere Tabletserver
an, die Tablets des ausgefallenen Tabletservers zu übernehmen (recovery). Jeder dieser Tabletserver
übernimmt nur ein paar wenige Tablets. Dieser Vorgang läuft parallel auf allen übernehmenden
Tabletservern ab. Die veränderte Situation wird in der Metadaten-Tabelle nachgeführt.
7.3 Interne Organisation
Physisch werden Tablets in mehreren SSTables abgespeichert. Eine SSTable entspricht dabei einer
GFS-Datei. Eine solche wird von GFS in Chunks unterteilt und gespeichert. Von jedem Chunk gibt
es jeweils drei Kopien auf unterschiedlichen Chunkservern.
13
7 Tablets
Änderungen durch Schreiboperationen werden allerdings zunächst in einem Commit-Log festgehalten und im Hauptspeicher in eine sogenannte Memtable geschrieben. Das Commit-Log ist eine
weitere GFS-Datei und enthält die Redo-Einträge für die eingegangenen Änderungen. Neue Einträge
werden jeweils hinten an das Commit-Log angehängt. Die Memtable ist nach Zeilenschlüssel sortiert. Der Inhalt der Memtable wird erst in eine SSTable geschrieben, wenn die Größe der Memtable
eine bestimmte Grenze überschreitet (vgl. Abschnitt 7.3.2).
Lesende Zugriffe müssen sowohl den Inhalt der Memtable, sowie den aller SSTables berücksichtigen. Dazu werden diese Inhalte zu einer einzigen Sicht verschmolzen (vgl. Abb. 6). Da sowohl
Memtable als auch SSTables bereits nach Zeilenschlüsseln sortiert vorliegen, ist das Erzeugen einer
solchen Sicht sehr effizient (vergleichbar mit einem Mergesort).
Abbildung 6: Interne Organisation eines Tablets
7.3.1 Migration
Wenn ein Tabletserver vom Master den Auftrag bekommt, ein Tablet von einem anderen Tabletserver zu übernehmen (Recovery, Loadbalancing), so liest er in der Metadaten-Tabelle die Metadaten
des Tablets. Diese enthalten neben den Lokationsdaten auch eine Liste aller SSTables, die Daten
des Tablets enthalten, sowie Verweise auf Commit-Logs, in denen Redo-Einträge für das Tablet
gespeichert sind. Der Tabletserver kann dann die Memtable des Tablets auf Basis der SSTables und
der Redo-Einträge rekonstruieren.
7.3.2 Verdichtung
Wenn die Memtable eine bestimmte Größe überschreitet, wird eine sogenannte kleine Verdichtung
(minor compaction) durchgeführt. Dabei wird der Inhalt der Memtable in eine neu angelegte SSTable
geschrieben und eine neue, leere Memtable erzeugt. Dadurch wird einerseits Hauptspeicher auf dem
Tabletserver freigegeben und andererseits müssen bei einer Übernahme des Tablets durch einen
anderen Tabletserver weniger Redo-Einträge des Commit-Logs ausgeführt werden.
Neben dieser kleinen Verdichtung kennt Bigtable auch noch eine große Verdichtung (major compaction). Diese verhindert, dass die Zahl der SSTables, die durch die kleine Verdichtung erzeugt
werden, immer weiter wächst. Während der großen Verdichtung werden nämlich alle SSTables eines
Tablets zu einer SSTable zusammengefasst. Die bisherigen SSTables können anschließend gelöscht
14
werden. Während der großen Verdichtung findet auch die „Garbage Collection“ statt, welche Einträge löscht, die z.B. aufgrund ihrer Timestamp-Attribute auf der Spaltenfamilie nicht mehr benötigt
werden (vgl. Abschnitt 3.3).
8 Maßnahmen zur Effizienzsteigerung
Um die Performance von Zugriffen auf Bigtable zu steigern, wurden diverse Anpassungen an den bisher beschriebenen Strukturen und Abläufen vorgenommen. Ein paar davon sollen hier exemplarisch
erwähnt werden.
8.1 Lokalitätsgruppen
Hierbei handelt es sich um eine Möglichkeit für die Benutzer, Einfluss darauf zu nehmen, wie die
Daten physisch gespeichert werden, um so bestimmte Abfragen performanter werden zu lassen. Jede
Spaltenfamilie kann einer Lokalitätsgruppe zugeordnet werden. Die Lokalitätsgruppen selber werden durch den Benutzer definiert. Für jede Lokalitätsgruppe wird während der Tabletverdichtung
(vgl. Abschnitt 7.3.2) eine eigene SSTable erzeugt. Dadurch werden Daten aus verschiedenen Lokalitätsgruppen in unterschiedlichen GFS-Dateien gespeichert, wodurch das Lesen dieser Daten viel
effizienter wird. Es macht also Sinn, Spalten, welche selten gemeinsam gelesen werden, unterschiedlichen Lokalitätsgruppen zuzuordnen.
Zum Beispiel ist es für die Tabelle aus Abbildung 1, die Informationen zu Webseiten speichert,
wahrscheinlich sinnvoll, die Spalte contents: einer eigenen Lokalitätsgruppe zuzuweisen und von
einer Lokalitätsgruppe, welche Spalten mit Metadaten zu der jeweiligen Webseite enthält (wie z.B.
die Sprache oder das Ranking der Seite) zu trennen. Werden dann nur Metadaten benötigt, muss
eine entsprechende Abfrage viel weniger Daten lesen, als wenn in den zu durchsuchenden SSTables
auch noch der ganze Seiten-Content enthalten wäre.
Lokalitätsgruppen bieten aber auch noch weitere Tuning-Möglichkeiten. So kann eine Lokalitätsgruppe zum Beispiel als in-memory definiert werden. Dadurch werden die entsprechenden SSTables
auf dem Tabletserver in den Hauptspeicher geladen, wodurch Anfragen auf diese Daten natürlich
sehr viel direkter und schneller bearbeitet werden können. Dies ist vor allem für kleinere Lokalitätsgruppen, welche häufig benötigte Daten enthalten, sehr sinnvoll. Durch die Unveränderlichkeit
der SSTables muss kein Aufwand betrieben werden, um den Zustand im Hauptspeicher mit der
GFS-Datei konsistent zu halten.
8.2 Komprimierung
Neben der Verdichtung, bei der Hauptspeicherplatz freigegeben wird bzw. SSTables zusammengefasst und von nicht mehr benötigten Daten befreit werden, spielen in Bigtable auch Komprimierungsalgorithmen eine wichtige Rolle bei der Aufgabe, Leseoperationen zu beschleunigen. Wird bei
den Lesezugriffen die Menge der zu lesenden Daten verringert, bedeutet dies direkt eine entsprechende Verringerung der I/O-Zugriffe und damit eine große Zeitersparnis. Bedingung dafür ist natürlich,
dass Algorithmen verwendet werden, die das Dekomprimieren schnell erledigen. Ansonsten würde
der Zeitgewinn wieder zunichte gemacht. So wurde bei der Wahl der Algorithmen also besonders
auf die Geschwindigkeit geachtet und erst in zweiter Linie auf die Platzersparnis.
Ob SSTables aber überhaupt komprimiert werden sollen oder nicht, kann der Benutzer pro Lokalitätsgruppe, zu der die SSTables gehören, gesondert konfigurieren. Der Komprimierungsvorgang
wird dann auf jeden Block einer SSTable getrennt angewendet. Dadurch wird es möglich, beim
Lesen der SSTable auch wieder nur einzelne Blöck zu berücksichtigen und nicht die ganze Datei
dekomprimieren zu müssen.
Das Datenmodell von Bigtable bietet diverse Angriffspunkte für Komprimierungsalgorithmen.
Besonders gute Komprimierungsergebnisse liefern ähnliche Werte innerhalb der gleichen Zelle bei
15
8 Maßnahmen zur Effizienzsteigerung
unterschiedlichen Timestamps, ähnliche Werte in den verschiedenen Spalten einer Spaltenfamilie,
oder ähnliche Werte über nahe beieinanderliegende Zeilen hinweg. Auf die Tabelle aus Abbildung 1
angewendet bedeutet dies, dass die Inhalte einer Webseite zu unterschiedlichen Zeitpunkten oft nur
wenig voneinander abweichen, dass die Werte in den Spalten der anchor Spaltenfamilie meist sehr
ähnlich sind (der Linktext auf die Seite www.fernuni-hagen.de wird in vielen Fällen „Fernuniversität in Hagen“ lauten), oder dass durch die spezielle Wahl der umgekehrten Schreibweise der
Hosts in den Zeilenschlüsseln alle Zeilen mit Seiten der Fernuniversität benachbart sind und dadurch ähnlicher Seiteninhalt nahe beieinander liegt (wie z.B. Navigationsbereiche, die auf vielen
Seiten vorkommen).
8.3 Bloomfilter
Betreffen Leseoperationen sehr viele SSTables, die nicht in-memory gehalten werden (vgl. Abschnitt 8.1), so führt dies zu zahlreichen Plattenzugriffen. Dieses Problem wird entschärft, indem
Bloomfilter [Blo70] eingesetzt werden. Dabei handelt es sich um Datenstrukturen im Hauptspeicher,
mit deren Hilfe sehr rasch entschieden werden kann, ob ein bestimmter Datenwert in einer SSTable
enthalten ist, oder nicht, wobei ein positiver Entscheid nur mit einer bestimmten Wahrscheinlichkeit zutrifft, während ein negativer Entscheid mit Sicherheit korrekt ist. Bei einer positiven Antwort
kann es also sein, dass der gesuchte Wert doch nicht in der SSTable gefunden wird. Bei einer negativen Antwort muss die SSTable aber nicht durchsucht werden. Dadurch lässt sich die Zahl der
benötigten I/O-Zugriffe auf SSTable-Dateien bei einer Suchabfrage entscheidend reduzieren.
8.4 Gemeinsame Logdateien
Wie in Abschnitt 7.3 beschrieben, werden eingehende Daten zunächst in die Memtable im Hauptspeicher geschrieben, nachdem entsprechende Redo-Einträge in ein Commit-Log geschrieben wurden.
Hätte jedes Tablet sein eigenes Commit-Log, würde dies bei Millionen von Tablets (für diese Mengen
wurde Bigtable ausgelegt) einer Unmenge von GFS-Dateien gleichkommen, die parallel beschrieben
werden müssten. Um dieses Problem zu entschärfen, existiert nur ein Commit-Log pro Tabletserver,
welches Log-Einträge für alle Tablets auf diesem Server in der Reihenfolge, in der sie eintrafen, enthält – also zufällig durchmischt. Wenn ein Tabletserver für jeweils gegen Tausend Tablets zuständig
ist, müssen auf diese Weise tausendmal weniger Log-Dateien geschrieben werden.
8.4.1 Recovery
Diese Optimierung hat jedoch ein etwas aufwändigeres Vorgehen beim Tablet-Recovery (vgl. Abschnitt 7.2) zur Folge. Wenn ein Tabletserver ein Tablet eines ausgefallenen Servers übernimmt,
muss er dieses anhand der SSTables und des Commit-Logs rekonstruieren. Wenn nun etwa hundert
Tabletserver die Tablets des ausgefallenen Servers übernehmen und dazu jeder dieser Tabletserver
das eine Commit-Log nach Einträgen für das zu übernehmende Tablet durchsuchen müsste, würde
ein und dasselbe Commit-Log hundertmal gelesen. Deshalb wird hier ein anderes Vorgehen gewählt.
Das Commit-Log wird zunächst sortiert (und zwar nach Tablet und innerhalb des Tablets nach
dem Zeilenschlüssel). Dadurch kommen alle Einträge für ein bestimmtes Tablet nebeneinander zu
liegen. Für das Auslesen wird vom jeweiligen Tabletserver also nur eine Suche auf der Festplatte
benötigt, gefolgt von sequentiellem Lesen der Einträge für das Tablet. Außerdem müssen durch
dieses Vorgehen die Einträge des Commit-Logs nur einmal „entwirrt“ werden.
Um den Sortiervorgang zu beschleunigen, wird das Commit-Log in Partitionen von 64 MB Größe
unterteilt. Diese werden vom Masterserver dann verschiedenen Tabletservern zur Sortierung zugeteilt. Das Sortieren läuft dadurch parallelisiert ab.
16
9 Bigtable im Einsatz
Zum Schluss sollen noch einige bekannte Anwendungen erwähnt werden, die Bigtable zur Speicherung ihrer Daten verwenden. Es handelt sich dabei um Anwendungen mit ganz unterschiedlichen
Bedürfnissen und Schwerpunkten. Das Ziel, dass Bigtable für Projekte aus einem breiten Anwendungsspektrum nutzbar sein soll (vgl. Abschnitt 1.2), wurde also durchaus erreicht.
9.1 Crawler (Web-Suche)
Die bekannteste Anwendung ist natürlich die Web-Suche, bzw. der Crawler, der die Webseiten besucht und die unvorstellbaren Mengen von Daten sammelt, die dann über die Web-Suche auffindbar
gemacht werden.
Die Bigtable Tabelle, die zur Speicherung dieser Daten verwendet wird, enthielt im Jahr 2006,
als das White-Paper4 , das dieser Arbeit zugrunde liegt geschrieben wurde, 800 TB Daten, verteilt
auf 1000 Milliarden Zellen. Die Kompressionsrate beträgt 11%, d.h. durch Komprimierung (vgl.
Abschnitt 8.2) kann die zu speichernde Datenmenge auf 11% ihrer ursprünglichen Größe reduziert
werden.
9.2 Google Analytics
Google Analytics (analytics.google.com) sammelt für angemeldete Webseite statistische Zugriffsdaten wie die Anzahl Seitenaufrufe für eine URL pro Tag oder die Anzahl unterschiedlicher
Besucher pro Tag aufgeschlüsselt nach den Ländern/Regionen, aus denen die Anfragen abgeschickt
wurden. Es können aber auch Abfragen zum Verhalten der Besucher auf der Webseite gemacht
werden, wie z.B. „Wieviel Prozent der Besucher kaufen ein Produkt, nachdem sie eine bestimmte
Seite besucht haben?“, oder „Welcher Anteil der Besucher bricht den Kaufprozess in welchem Schritt
ab?“.
Google Analytics verwendet eine Bigtable Tabelle zur Speicherung der Rohdaten (Klicks), welche
pro Benutzer-Session eine Zeile enthält. Der Zeilenname (Zeilenschlüssel) enthält den Namen der
Website, für die die Daten gesammelt werden, sowie den Zeitpunkt der Erzeugung der BenutzerSession. Auf diese Weise wird erreicht, dass Benutzer-Sessions, die die selbe Webseite besuchen, in
benachbarten Zeilen zu liegen kommen und innerhalb dieses Zeilenranges chronologisch sortiert sind.
Diese Tabelle enthielt im Jahr 2006 200 TB Daten in 80 Milliarden Zellen. Die Kompressionsrate
beträgt 14%.
Eine zweite Tabelle enthält verschiedene vordefinierte Analysen und Berichte für jede einzelne
angemeldete Webseite. Der Inhalt dieser Tabelle wird durch MapReduce-Jobs periodisch aus dem
Inhalt der Tabelle mit den Rohdaten generiert. Im Jahr 2006 war diese Tabelle 20 TB groß.
9.3 Google Earth
Google Earth (earth.google.com) und Google Maps (maps.google.com) basieren auf den
selben Satellitenbilddaten. Die Rohdaten werden mittels MapReduce-Jobs vorverarbeitet und auf
einer Bigtable Tabelle von 70 TB Größe gespeichert. Da die Bilddaten schon komprimiert sind, ist
die Komprimierung auf dieser Tabelle abgeschaltet.
Jede Zeile in dieser Tabelle entspricht einem geographischen Segment. Die Zeilen werden so benannt, dass geographisch benachbarte Segmente auch in der Tabelle in benachbarten Zeilen zu liegen
kommen.
Eine zweite, relative kleine Bigtable Tabelle (500 GB) wird als Index auf die GFS-Dateien verwendet. Diese Tabelle muss Zehntausende Abfragen pro Sekunde effizient beantworten können und
4
Cha+08.
17
9 Bigtable im Einsatz
wird deshalb auf hunderte von Tabletservern verteilt. Außerdem enthält diese Tabelle Spaltenfamilien, die im Hauptspeicher vorgehalten werden. Insgesamt befinden sich etwa 33% der Daten im
Hauptspeicher.
9.4 Personalisierte Suche
Hat man den Dienst „Personalisierte Suche“ (www.google.com/psearch) aktiviert, so werden
Suchbegriffe und Klicks während der Websuche oder Bildersuche aufgezeichnet und analysiert. Dadurch kann man seine alten Suchabfragen später nochmals ansehen bzw. wiederholen und erhält bei
der Suche personalisierte Vorschläge während der Eingabe, sowie gemäß analysiertem Nutzungsmuster personalisierte Suchergebnisse.
Für jeden Benutzer, der diesen Dienst aktiviert hat, wird in der dahinter liegenden Bigtable
Tabelle eine Zeile angelegt. Für jede Art von Benutzeraktion gibt es eine eigene Spaltenfamilie und
zu jedem Datenelement wird als Timestamp der Zeitpunkt, zu dem die Benutzeraktion ausgeführt
wurde, hinterlegt.
Benutzerprofile werden mittels MapReduce-Jobs über den gespeicherten Daten erstellt. Um die
Verfügbarkeit der Daten zu erhöhen, werden diese auf mehrere Bigtable-Cluster repliziert. Außerdem
können die Daten so von einem geographisch weniger weit vom Benutzer entfernt liegenden Server
ausgeliefert werden, was die Latenzzeit verkürzt.
9.5 Datastore der Google App Engine
Bigtable selbst wird von Google nicht zur direkten Nutzung durch die Öffentlichkeit angeboten.
Mit der Google App Engine (cloud.google.com/appengine) steht aber eine Plattform zur
Verfügung (PaaS – Platform as a Service), die es ermöglicht, eigene Webanwendungen auf Googles
Infrastruktur laufen zu lassen und diverse Dienste von Google zu nutzen. Zur Speicherung der
Daten wird dort ein sogenannter Datastore angeboten, der auf Bigtable aufbaut. Nutzt man diesen
Datastore, so nutzt man also indirekt Bigtable. Es stehen APIs in Java und Python zur Verfügung.
18
Literatur
Literatur
[Blo70]
Burton H. Bloom. „Space/time trade-offs in hash coding with allowable errors“. In:
Commun. ACM 13.7 (1970), S. 422–426. issn: 0001-0782.
[Bur06]
Mike Burrows. „The Chubby lock service for loosely-coupled distributed systems“. In:
Proceedings of the 7th symposium on Operating systems design and implementation.
OSDI ’06. Seattle, Washington: USENIX Association, 2006, S. 335–350. isbn: 1-93197147-1.
[Cha+08]
Fay Chang, Jeffrey Dean, Sanjay Ghemawat u. a. „Bigtable: A Distributed Storage System for Structured Data“. In: ACM Trans. Comput. Syst. 26.2 (2008), 4:1–4:26. issn:
0734-2071.
[DG08]
Jeffrey Dean und Sanjay Ghemawat. „MapReduce: simplified data processing on large
clusters“. In: Commun. ACM 51.1 (2008), S. 107–113. issn: 0001-0782.
[GGL03]
Sanjay Ghemawat, Howard Gobioff und Shun-Tak Leung. „The Google file system“. In:
SIGOPS Oper. Syst. Rev. 37.5 (2003), S. 29–43. issn: 0163-5980.
Webseiten
[Dea05]
Jeffrey Dean. BigTable: A Distributed Structured Storage System (Colloquium Video,
University of Washington). 2005. url: http://norfolk.cs.washington.edu/
htbin-post/unrestricted/colloq/archive.cgi?id=437.
[Krz11]
Paul Krzyzanowski. BigTable - A NoSQL massively parallel table. 2011. url: http:
//www.cs.rutgers.edu/~pxk/417/notes/content/bigtable.html.
19
Seminar 01912
Big Data Management
im Sommersememster 2013
Ein dokumentenorientiertes Datenbanksystem
Referentin: Kristina Steiger
CouchDB
Inhaltsverzeichnis
1
2
3
Einleitung
2
Die Entwicklungsgeschichte von CouchDB
4
2.1
Entwicklung durch Damien Katz
. . . . . . . . . . . . . . . . . . . . . . .
4
2.2
CouchDB bei IBM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.3
CouchDB als OpenSource Projekt bei Apache . . . . . . . . . . . . . . . .
5
Abfragen und Speichern von Daten in der CouchDB
5
3.1
Datenabfragen über HTTP mit einer RESTful API . . . . . . . . . . . . .
5
3.2
Speichern im JSON Dokumentenformat
6
. . . . . . . . . . . . . . . . . . .
4
Implementierung des MapReduce-Patterns mit Views
7
5
Modellierung der Datenbank mit Design-Dokumenten
7
5.1
Show-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
5.2
List-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
5.3
Validierungs-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
6
7
Letztendliche Konsistenz
11
6.1
Multiversion-Concurrency-Control
. . . . . . . . . . . . . . . . . . . . . .
11
6.2
Koniktmanagement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
6.3
Replikation für Redundanz und parallele Prozesse . . . . . . . . . . . . . .
12
Aktueller Stand und Aussichten
13
1
CouchDB
1 Einleitung
Das Datenbanksystem CouchDB gehört zu den sogenannten NoSQL-Datenbanken. Der
Begri NoSQL impliziert zunächst, daÿ es sich hierbei um kein SQL handelt. Er wurde erstmalig 2009 von Eric Evans für ein Event in San Francisco verwendet. Vorrangig
sollte er als provokative Phrase aufgefasst werden, als Abgrenzung zu der strukturierten
Abfragesprache SQL. NoSQL hat zum Ziel, Alternativen zum allgegenwärtigen relationalen Datenbankmodell und üblichen Datenbanktechnologien aufzuzeigen, die für bestimmte
Anwendungsfälle besser geeignet sind. Mit dem Web2.0 und dem damit einhergehenden
Bedarf nach der Verarbeitung groÿer Datenmengen erfuhren NoSQL-Datenbanken ein sehr
schnelles Wachstum. Die Vereinigung fast aller nicht relationaler Datenbanken unter dem
Begri NoSQL zeigte eine ernstzunehmende Alternative zu SQL-Datenbanken auf. Mittlerweile wird der Begri von groÿen Teilen der Community als Not-only-SQL aufgefasst, um
somit die strikte Abgrenzung wieder aufzuweichen. Es gibt auch viele Hybrid-Lösungen.
Je nach Anwendung gilt es die richtige Datenbank auszuwählen und in vielen (vor allem sicherheitskritischen und komplexen) Fällen ist eine relationale Datenbank auch nach
wie vor die richtige Lösung. Im NoSQL-Archiv von Dr. Prof. Stefan Edlich
[Edl13] sind
alle NoSQL-Datenbanken aufgeführt, aktuell 150. Die NoSQL-Bewegung setzt sich grundsätzlich für eine freie Datenbankauswahl ein und schärft das Bewuÿtsein für das groÿe
+
Spektrum an Datenbanken, das zur Verfügung steht. [EFH 11, S. 10]
Um groÿe Datenmengen ezient zu verarbeiten wurden neue Verfahren entwickelt. Das
sogenannten MapReduce-Verfahren spielt hierbei eine entscheidende Rolle und wird auch
in der CouchDB eingesetzt. Das MapReduce-Framework wurde 2004 bei Google Inc. entwickelt und 2010 erhielt Google das Patent darauf.
+
[EFH 11, S. 12] Während in SQL
mit Hilfe von JOIN-Abfragen auf Daten unterschiedlicher Tabellen zugegrien wird, werden bei dem Map/Reduce Verfahren parallele Berechnungen über groÿe Datenmengen
durchgeführt. Die beiden Phasen Map und Reduce haben ihren Ursprung in funktionalen
Programmiersprachen. Beide Funktionen dürfen keine Nebeneekte haben, d.h. sie dürfen
nicht auf Objekte auÿerhalb ihres aktuellen Scopes zugreifen. Dadurch wird Parallelität
und Skalierbarkeit ermöglicht.
[WK11, S. 37] Zudem arbeiten funktionale Operationen
immer auf Kopien der Daten, wodurch sich unterschiedliche Operationen auf dem gleichen
Datensatz nicht gegenseitig beeinussen. (Abb. 1)
Vielen NoSQL-Projekten und auch der CouchDB liegt das CAP-Theorem zugrunde.
Dieses wurde 2000 erstmals von Dr. Eric Brewer in einem Vortrag erwähnt. Dabei sprach
er über Vor- und Nachteile von ACID (Atomicity, Consistency, Isolation, Durability) und
BASE (Basically Available Soft-state Eventual consistency) und befand, daÿ sich diese
nicht gegenseitig ausschlieÿen, sondern als Bausteine dienen können, die sich der Entwickler beliebig zusammenstellen kann. Diese Richtlinie nannte er CAP und sie wurde 2002 in
einem axiomatischen Beweis bestätigt. CAP besteht aus Consistency, Availability und Partition Tolerance, wobei Partition Tolerance bedeutet, daÿ die Datenbank auf verschiedene
Server verteilt werden kann. Das CAP-Theorem besagt, daÿ ein echtes verteiltes System
2
CouchDB
Abbildung 1: Das MapReduce-Verfahren
nur zwei dieser drei Bausteine garantieren kann. (Abb. 2)
In der CouchDB steht das C nur für eventual consistency. Irgendwann liefert jeder
Lesevorgang das gleiche Ergebnis, da aber im verteilten Kontext gearbeitet wird, kann es
sein, daÿ zunächst unterschiedliche Ergebnisse geliefert werden, bis das Ergebnis wieder
überall konsistent ist. Verfügbarkeit wird über ein Master-Slave-Prinzip hergestellt, indem
schreibend auf einen CouchDB-Server zugegrien wird während im Hintergrund ein zweiter repliziert. Da CouchDB bisher per Denition noch kein echtes verteiltes System ist, ist
Partition Tolerance nur bedingt relevant. [WK11, S. 48]
Abbildung 2: Das CAP-Theorem
3
CouchDB
In folgender Arbeit wird zunächst auf die Entwicklungsgeschichte der CouchDB eingegangen, wie sie mit ihrem Ernder Damien Katz bei IBM und schlieÿlich als Open Source
Projekt bei Apache gelandet ist. Im nächsten Kapitel wird vorgestellt, mit welchen Mechanismen das Abfragen und Speichern von Daten in der CouchDB funktioniert und was
die Besonderheiten dabei sind. Als nächstes wird erklärt wie die Implementierung des
Map/Reduce-Verfahrens in der CouchDB mit sogenannten Views funktioniert. Danach
wird auf das Modellieren einer CouchDB-Datenbank mit Design-Dokumenten eingegangen. Abschlieÿend wird die letztendliche Konsistenz, sowie das Koniktmanagement und
die Replikation in der CouchDB näher erläutert.
2 Die Entwicklungsgeschichte von CouchDB
2.1 Entwicklung durch Damien Katz
Der Ernder von CouchDB ist Damien Katz (*1973). (Abb. 3) Seit seinem Abschluss in
Computer Science 1995 hat er bei Iris Associated, Lotus, MySQL und IBM gearbeitet.
Bei Iris wurde die Software Notes entwickelt, die dann bei Lotus vertrieben wurde und
bis heute unter dem Namen Lotus Notes bekannt ist. Dies bildete auch die Grundlage
für die Entwicklung von CouchDB. 1994 wurde Iris von Lotus und Lotus dann 1995 von
IBM gekauft. Damien arbeitete bei IBM bis 2002 an allen Teilen von Notes weiter und
verlieÿ dann das Unternehmen, um etwas Eigenes zu machen. Er besann sich auf die Notes
Storage Engine von Domino, die bidirektional synchronisieren kann und entwickelte daraus
die Idee einer dokumentenbasierten Datenbank mit mehr Features als Notes. Da er auch
parallelisierten Zugri auf Dokumente integrieren wollte und sich das mit C++ nicht so
einfach umsetzen lieÿ, landete er schlieÿlich bei der Programmiersprache Erlang, die neben
ihrem mythenbildenden Charakter für weniger Fehler bei verteilten und parallel laufenden
Tasks sorgt
[Fro09]. 2005 war eine erste Version von CouchDB fertig, bisher noch mit
einer XML-basierten Storage Engine und einer Query Engine mit SQL-artiger Syntax. Da
sich aber bisher keine nanziellen Erfolge einstellten, nahm er eine Anstellung bei MySQL
an und entwickelte CouchDB privat weiter. Er ersetzte das XML-Format durch JSON und
die Query Engine durch JavaScript und MapReduce. [WK11, S. 44]
Abbildung 3: Der CouchDB-Ernder Damien Katz
4
CouchDB
2.2 CouchDB bei IBM
Damien wurde bereits seit einer Weile von IBM umworben wieder bei ihnen zu arbeiten.
Er wollte dies jedoch nur tun, wenn er CouchDB weiter als OpenSource-Projekt betreiben
durfte. Man einigte sich schlieÿlich auf eine Research-Position und darauf daÿ CouchDB
zu einem Apache-Open-Source-Projekt werden sollte. IBM verfügte somit über die Fachkompetenz von Damien Katz im Bereich CouchDB und sponsorte zum anderen aber auch
die Weiterentwicklung der Datenbank. [WK11, S. 46]
2.3 CouchDB als OpenSource Projekt bei Apache
Alan Bell war der erste Entwickler der neben Damien Katz an CouchDB mitgearbeitet
hat. Er hat die erste Webseite und ein in JavaScript geschriebenes Web-Frontend entwickelt. 2006 stieÿ Jan Lehnhardt zum Team und übernahm viele Aufgaben im CommunityBereich. Er führte die Versionsverwaltung SVN und Google Code für den Source-Code
ein und erstellte ein Wiki. Über die sehr aktive CouchDB-Google-Group kamen die ersten
Entwickler mit Commit-Rechten hinzu, unter anderem Noah Slater, J. Chris Anderson
und Christopher Lenz. 2008 wurde CouchDB, bereits mit einer beachtlichen Community
und groÿem Interesse in der IT-Welt, ein vollwertiges Open-Source-Projekt der Apache
Software Foundation. CouchDB erfreut sich von je her einer groÿen und aktiven Community, die sehr hilfbereit und enthusiastisch ist. Nicht zuletzt sind auch die Entwickler
fast täglich im IRC-Channel oder auf der Mailingliste anzutreen.
[WK11, S. 42] Ende
2009 gründeten Damien Katz und einige Mitstreiter eine Start-up-Firma namens Relaxed
Inc. Unter ihrem Dach erfolgt die Weiterentwicklung von CouchDB. Der Name CouchDB
ist übrigens die Abkürzung für "Cluster of unreliable commodity hardware Data Base".
[Jan10]
3 Abfragen und Speichern von Daten in der CouchDB
3.1 Datenabfragen über HTTP mit einer RESTful API
CouchDB ist vor allem für Webapplikationen geschrieben worden. Deshalb benutzt sie
auch eine REST Api und komminiziert über HTTP. Grundsätzlich beschreibt RESTful
wie über das HTTP-Protokoll auf Ressourcen zugegrien wird. REST bedeutet
tational State Transfer
Represen-
und wurde von Roy Thomas Fielding 2000 als Begri eingeführt.
Es hat mehrere grundlegende Prinzipien. Eine Ressource muss immer eindeutig durch eine URI (Uniform Resource Identier) adressierbar sein, der Zugri auf eine Ressource ist
zustandslos und was mit der Ressource geschehen soll, wird immer durch eine HTTPMethode (Get, Put usw.) beschrieben. [WK11, S.31] Die Operationen an einer CouchDB
erfolgen ausschlieÿlich über das HTTP-Protokoll und die HTTP-Methoden, jedes Dokument hat eine eindeutige ID und der Zugri ist zustandslos.
Nachdem die CouchDB auf dem System gestartet ist, können im Browser bereits nach
Eingabe der Server-Adresse und dem Standard-Port 5984 erste Informationen über die
Datenbank, wie die Version, die Art des Inhalts und das Encoding abgefragt werden. Die
5
CouchDB
Antwort der CouchDB wird als JSON-Objetkt im Browser angezeigt. (Abb. 4) Der Header
läÿt sich über die Chrome-Dev-Tools oder über den Firebug in Firefox auslesen.
Abbildung 4: Ausgabe im Browser nach Aufruf der CouchDB-Adresse
Die Standard-HTTP-Befehle GET, PUT und DELETE stehen auch für das Aufrufen,
Erstellen und Löschen von Datenbanken zur Verfügung. Mit POST können Kongurationseinstellungen vorgenommen werden. Mit der CouchDB-eigenen Methode COPY können
auÿerdem einzelne oder mehrere Dokumente kopiert werden.
3.2 Speichern im JSON Dokumentenformat
Die zentrale Datenstruktur von CouchDB sind Dokumente. Dokumente bieten die Möglichkeit Daten zu strukturieren und zu gruppieren. Sie unterscheiden sich von anderen
Objekten, da sie immer über die CRUD-Methoden verfügen (create, read, update, delete).
[ALS10, S.119]. Jedes Dokument in der CouchDB hat eine eindeutige ID. Diese ID ist pro
Datenbank eindeutig. Es empehlt sich dafür einen UUID (Universally Unique IDentier)
zu verwenden. Dieser kann automatisch von CouchDB erzeugt werden. Auÿerdem hat jedes Dokument eine Revision-ID. Bei jeder Änderung an dem Dokument wird eine neue
Revision-ID erzeugt. [ALS10, S.38]
Das grundlegende CouchDB-Speicherprinzip ist die Speicherung von Key-Value-Paaren.
Die interne Speicherung erfolgt in B-Bäumen. CouchDB speichert alle Daten im JSONFormat. Mit Ausnahme von Attachments, diese werden in dem Format gespeichert, das
im HTTP-Header content-type angegeben ist. JSON bedeutet
tion
JavaScript Object Nota-
und ist eine einfach zu lesende Datenstruktur für den Austausch von Daten. Eine
geschweifte Klammer umschlieÿt das Objekt und darin bendet sich eine Key-Value-Liste,
die auch verschachtelt sein kann.
Listing 1: Ausgabe im JSON-Format
{
6
" couchdb ":" Welcome ",
" uuid ":"14300 e2c98285b4290772917244e2dc8 ",
" version ":"1.3.0" ,
" vendor ": {
" version ":"1.3.0" ,
" name ":" The Apache Software Foundation "
}
CouchDB
}
Einer der gröÿten Vorteile von JSON ist der native Zugri auf diese Datenstruktur durch
JavaScript.
[WK11, S.36] CouchDB Dokumente können somit direkt als Objekte beim
Programmieren verwendet werden. Da alle zusammengehörigen Daten in einem Dokument
gespeichert sind kommt es vor, daÿ die gleichen Daten an mehreren Stellen abgespeichert
werden. In einer dokumentenbasierten Datenbank werden die Daten aber ganz bewuÿt
redundant abgespeichert. Das bringt das Konzept mit sich.
4 Implementierung des MapReduce-Patterns mit Views
Normalerweise werden Dokumente in der CouchDB über ihren Key gelesen. Für komplexere Abfragen werden Views benutzt. Views werden in den Design-Dokumenten deniert und
gespeichert und implementieren das MapReduce-Pattern. Sie können zum Beispiel dafür
verwendet werden, um relevante Dokumnte herauszultern, Daten aus den Dokumenten
zu extrahieren, eziente Indices zu bauen, um Beziehungen zwischen den Dokumenten
darzustellen und um Berechnungen auf den Daten durchzuführen. [ALS10, S.53]
Während der Map-Phase werden alle Dokumente in der Datenbank verarbeitet. Normalerweise wird dann das Ergebnis der Map-Phase an die Reduce-Phase weitergegeben,
bei CouchDB ist dieser Schritt jedoch optional.
Views werden in JavaScript geschrieben. CouchDB verwendet dafür die JavaScriptEngine Spidermonkey. Es lassen sich jedoch auch sogenannte View-Server in beliebigen
anderen Sprachen implementieren. Eine aktuelle Liste vorhandener Server gibt es im
CouchDB-Wiki unter [Apa].
Seit Version CouchDB 0.11.0 wurden drei Funktionen für die Reduce-Phase fest miteingebaut: _sum, das Aufaddieren aller Werte vom Typ Number, _count, das Aufaddieren
aller Werte, die nicht zwangsläug vom Typ Number sein müssen und _stats, das verschiedene Statistiken ausgibt, unter anderem auch sum und count, aber auch min, max und
sumsqr der Werte. Diese Reduce-Funktionen werden innerhalb von CouchDB ausgeführt,
da sie in Erlang geschrieben sind. Dies kann je nach Daten deutliche Geschwindigkeitsvorteile haben. [WK11, S.95]
Es lassen sich auch temporäre Views erstellen, die nicht persistent sind und direkt nach
der Ausführung wieder gelöscht werden. Sie sind zwar weniger performant, müssen dafür
aber nicht in den Design-Dokumenten abgelegt werden.
Um das Ergebnis eines View abzufragen wird ein query auf den View ausgeführt. Bei
den Abfragen können auch verschiedene Parameter übergeben werden, um das Ergebnis
weiter einzuschränken, zu sortieren oder zu gruppieren.
5 Modellierung der Datenbank mit Design-Dokumenten
Design-Dokumente sind besondere Dokumente in der CouchDB, die Anwendungs-Code
enthalten. Ein Design-Dokument hat eine ID die mit _design/ beginnt. Es wird wie alle
anderen Dokumente in der CouchDB behandelt. Die CouchDB sucht hier aber nach Views
7
CouchDB
und anderen Anwendungs-Funktionen. Statische HTML-Seiten können dort ebenfalls als
Attachments hinterlegt werden. Ein Design-Dokument hat unter anderem folgende Elemente:
Eindeutige ID:
"_id": "_design/mydesigndocument",
Revisions-Nummer:
"_rev": "3157636749",
Validierungsfunktion:
"validate_doc_update":
"function(newDoc, oldDoc, user(tx)) {}"
Programmiersprache:
"language": "javascript",
Liste der Views:
"views":{}
Liste der Show-Funktionen:
shows": {}
Liste der List-Funktionen:
"lists": {}
Liste von Attachements:
"_attachements":{}
Tabelle 1: Elemente eines Design-Dokuments
[ALS10, S.49] Es können auch noch weitere Einstellungen hier vorgenommen werden.
Im Folgenden werden Show- und List- und Validierungsfunktionen als Elemente eines
Design-Dokuments vorgestellt.
5.1 Show-Funktionen
CouchDB bietet die Möglichkeit, die Daten eines Dokuments und auch die Ergebnisse
eines Views in anderen Formaten als JSON auszugeben, z.B in HTML, CSV oder XML
oder sogar als PNG. Dies kann sehr nützlich sein, da reine Ajax und JavaScript-Seiten
manchmal nicht von allen Browsern richtig dargestellt werden und auch von Suchmaschinen die Inhalte nicht gefunden werden. Hierzu wird meist HTML benötigt. Dies ist mit
einem middle-tier application server wie Ruby on Rails oder Django möglich, aber auch
direkt über CouchDB mit den Show- und List-Funktionen. [ALS10, S.75]
Show-Funktionen werden im Design-Dokument unter dem Eintrag shows angegeben.
Die Funktion selbst ist in JavaScript geschrieben. Der erste erwartete Parameter ist ein
Dokument-Objekt aus der CouchDB, der zweite das Request-Objekt, das Details über
den HTTP-Request beinhaltet. Die Show-Funktion ist nur auf ein bestimmtes Dokument
anwendbar, nicht auf mehrere (wie die List-Funktion).
Listing 2: Beispiel für eine Show-Funktion
" shows ": {
" html_doc ": function ( doc , req ){
if ( doc ) {
return `<h1 > `+ doc . title + `</h1 >`
`< table > <tr ><td > id : </ td > <td > `+ doc . _id +
`</td > </ tr > \
8
CouchDB
<tr ><td > Typ </ td ><td > `+ doc . typ + ` </ td > </ tr >
</ table >
}
}
} else {
return `<h4 > Fuer die angegebene id `+ req . id +
` gibt es kein Ergebnis .`
}
[WK11, S.105]
Die Funktion wird aufgerufen, indem der Name der Funktion und danach die ID des
Dokuments angegeben wird.
GET /mydb/_design/mydesigndocument/_show/html_doc/72d43a93eb74b5f2
Sie erzeugt dann einen HTTP-Response. Die Einsatzmöglichkeiten dieser Funktionen sind
vielfältig. Man kann sie z.B. dazu nutzen, um Detailansichten einzelner Dokumente in
Applikationen anzuzeigen.
5.2 List-Funktionen
Mit List-Funktionen kann das Ergebnis eines Views in anderen Formaten dargestellt werden. Sie unterscheiden sich vor allem dadurch von Show-Funktionen, daÿ sie mehrere
Dokumente behandeln können. Durch die Funktion wird eine Liste generiert, indem einzelne Zeilen in einer while-Schleife in die Ausgabe geschrieben werden. Das Listenformat
ist hierbei frei wählbar, es kann HTML, CSV oder ein cong le sein. CouchDB stellt für
die Verwendung der List-Funktionen einige Methoden bereit: start(), setzt Optionen für
die Ausgabe, z.B. den Header, getRow(), gibt den Inhalt eines Dokuments zurück und
send(), generiert die Ausgabe.
Listing 3: Beispiel für eine List-Funktion
" lists ": {
" html_list ": function ( head , req ){
start ({ ` headers `: { ` content - type `: ` text / html `}});
send ( `< table ><tr >< td > Typ </ td >< td > ID </ td >
<td > Datum </ td >< td > Wert </ td > </ tr > `);
while ( var row = getRow ()) {
send ( `<tr >< td > `+ row . key [0] + `</td >
<td > `+ row . key [1] + ` </td >
<td > `+ row . key [2] + ` </td >
<td > `+ row . value + `</td >
};
send ( ` </ tr > </ table > `);
9
CouchDB
}
}
[WK11, S.108] Der Pfad zu einer List-Funktion setzt sich wie folgt zusammen:
GET /db/_design/mydesigndocument/_lists/html_list/view-name
Durch Parameter läÿt sich die Ausgabe der Liste weiter einschränken.
[ALS10, S.92]
List-Funktionen sind vor allem im Bereich der Statistiken oder tabellarischen Ausgaben
eine sehr nützliche Implementierung. Sie haben ebenso wie Show-Funktionen keinerlei
Seiteneekte, d.h. sie verändern keine Daten in der Datenbank.
5.3 Validierungs-Funktionen
CouchDB kann Dokumente auch auf Korrektheit überprüfen. Diese Validierung ist optional. Die Funktionen zum Prüfen der Daten können ebenfalls in den Design-Dokumenten
abgelegt werden. Die Funktion validate_doc_update wird benutzt, um ungültige oder
unauthorisierte Updates von Dokumenten zu verhindern. Die Validierungsfunktionen in
der CouchDB können ebenso wie die anderen Funktionen keine Seiteneekte haben, da
sie isoliert von der Anfrage laufen.
[ALS10, S.67] Jedesmal wenn Änderungen an einem
Dokument vorgenommen werden, übergibt CouchDB eine Kopie des existierenden Dokuments, eine Kopie des neuen Dokuments und eine Reihe zusätzlicher Informationen, wie
zum Beispiel Benutzerdaten, an die Validierungsfunktion. Die Funktion kann nun der Änderung zustimmen oder sie ablehnen. Damit erspart man sich eine Menge CPU-Leistung,
die ansonsten darauf verwendet werden müÿte Objekt-Graphen aus SQL zu serialisieren,
diese in Domain Objekte umzuwandeln und diese Objekte dann wiederum zu benutzen,
um Validierung auf Applikations-Ebene zu betreiben.
[ALS10, S.16]
Pro Design-Dokument kann eine Funktion angegeben werden. Diese kann beliebig komplex sein. Es können aber auch mehrere Design-Dokumente angelegt werden. Wenn ein
Dokument abgespeichert wird, muss es die Validierung von allen Design-Dokumenten der
Datenbank durchlaufen und erfolgreich bestehen. Eine Validierungs-Funktion kann wie
folgt aussehen:
Listing 4: Beispiel für eine Validierungs-Funktion
function ( newDocument , currentDocument , userContext ){
if (! newDocument . email ) {
throw ({ forbidden :' Email required . '});
}
}
Wenn keine Exception aus der Funktion geworfen wird, geht CouchDB davon aus, daÿ
die Eingaben richtig sind, und speichert das Dokument.
10
[WK11, S.99]
CouchDB
6 Letztendliche Konsistenz
Es wurde bereits darauf hingewiesen, daÿ in der CouchDB das C in CAP nur für eventual consistency steht. CouchDB wurde gebaut, um in verteilten Systemen eingesetzt zu
werden. In solchen kann es passieren, daÿ die einzelnen Komponenten nicht in ständigem
Kontakt miteinander stehen, aufgrund von Netzwerkproblemen. CochchDB hat sich dafür
entschieden den Nutzern ständige Verfügbarkeit zu garantieren, und dafür nur eventual
consistency. Absolute Konsistenz würde voraussetzen, dass der Nutzer auf die Synchronisation aller Daten über das Netzwerk warten muÿ und deshalb nicht ständig auf die
Daten zugreifen kann. Daÿ Änderungen in der CouchDB zunächst nur lokal vorgenommen
werden, macht das System sehr performant. [ALS10, S.11] Was das genau bedeutet, wird
im Folgenden erklärt.
6.1 Multiversion-Concurrency-Control
Daten in einer Datenbank sollten immer in einem konsistenten Zustand vorliegen. Sobald
mehrere Zugrie gleichzeitig erfolgen, muss sichergestellt werden, daÿ die Datenintegrität
erhalten bleibt. Eine Möglichkeit dazu ist es, die Schreibzugrie alle nacheinander abzuarbeiten, indem jeder Benutzer die Daten während eines Schreibzugris blockiert. Das
bringt jedoch eine relativ schlechte Performance mit sich. In der CouchDB ist deshalb
das Multiversion-Concurrency-Control-Verfahren (MVCC) implementiert. (Abb. 5) Dies
ermöglicht es, daÿ viele Benutzer die Daten gleichzeitig lesen können. Ein Benutzer, der
eine Aktualisierung an den Daten vornimmt, arbeitet an einer lokalen neuen Version des
Dokuments. Da alle Dokumente in der CouchDB versioniert sind, bekommt jede Aktualisierung eine neue Revisionsnummer. Erst nach Abschluss des Schreibvorgangs sieht ein
anderer Benutzer die aktuellste Version. So wird sichergestellt, daÿ ein Benutzer immer
einen konsistenten Zustand der Daten sieht. Die Daten sind aber erst eventual consistent, wenn der schreibende Benutzer mit seiner Aktualisierung fertig ist. Am Ende sehen
alle Benutzer den gleichen Zustand des Dokuments, auch wenn kurzzeitig unterschiedliche
Versionen in Umlauf sein können. [WK11, S.34] [ALS10, S.15]
Abbildung 5: Locking-Verfahren vs. MVCC-Verfahren
11
CouchDB
6.2 Koniktmanagement
Bearbeiten zwei Benutzer das gleiche Dokument wird ein Koniktmanagement benötigt.
CouchDB hat eine automatische Konikterkennung und -lösung. Wenn erkannt wird, daÿ
ein Dokument von zwei verschiedenen Benutzern geändert wurde, wird es als in conict
markiert, wie dies in einem Versionskontroll-System der Fall wäre. Eine Version gewinnt
den Konikt und wird als die aktuellste abgepeichert, die andere wird als die Vorgängerversion in der Historie abgelegt. Dies passiert automatisch. Danach bleibt es dem Benutzer
überlassen, wie er mit diesem Konikt umgeht, er kann die vom System als aktuellste Version gespeicherte Lösung verwenden, die vorhergehende benutzen oder versuchen beide zu
einer neuen Version zu mergen. Es gibt jedoch kein automatisches Zusammenführen von
Versionen mit Konikten durch CouchDB, da es häug nicht zu entscheiden ist, welche
Änderung Vorrang vor anderen hat und dies der Anwendung oder dem Benutzer überlassen wird. Dadurch kann es vorkommen, daÿ die vermeintlich falsche Version die aktuellste
ist. [ALS10, S.17]
6.3 Replikation für Redundanz und parallele Prozesse
Replikation ermöglicht es, mehrere Datenbanken miteinander zu synchronisieren. Im Gegensatz zu MySQL können sich bei CouchDB aber auch beide Datenbanken auf der gleichen Instanz benden und es sind sogar Master-Master-Replikationen möglich, um z.B.
Backups zu erstellen.
[WK11, S.120] Um Daten auf verschiedenen Servern konsistent zu
halten, benutzt CouchDB inkrementelle Replikation. Aktualisierte Dokumente werden in
regelmäÿigen Abständen zwischen den Servern hin und her kopiert. Dies passiert asynchron und kann bei vielen Änderungen deshalb auch eine Weile dauern. Dazu wird einem
POST-Befehl eine source und eine target-Datenbank übergeben und diese werden dann
von CouchDB synchronisiert. Wenn das target-Verzeichnis noch nicht existiert, wird es von
CouchDB neu angelegt und alle Daten werden dorthin übertragen. Es läÿt sich auch einstellen, daÿ CouchDB Continuous Replication betreibt, d.h. den changes-Feed beobachet
und fortlaufend Änderungen überträgt. [ALS10, S.16]
12
CouchDB
7 Aktueller Stand und Aussichten
Sowohl NoSQL- als auch relationale Datenbanken haben ihre Stärken, die sie bei passenden Anforderungen ausspielen können. Durch ihre Schemafreiheit eignen sich NoSQLDatenbanken besser für die Ablage von beliebigen Dokumenten. Die exakte Struktur der
Tabelleninhalte, die durch ein Datenbankschema vorgegeben sind, ermöglicht dagegen in
einer relationalen Datenbank (Ad-hoc-)Abfragen auf ungewöhnlichen Spaltenkombinationen. Relationale Datenbanken sind dafür gebaut, auf einem zentralen Server zu laufen.
Im Zeitalter des Cloud Computing werden aber häug viele kleinere Rechner gemeinsam
verwendet, das heiÿt, die Verteilung von Anfragen wird immer wichtiger. [Jan10]
CouchDB bietet weniger Funktionalität im Vergleich zu groÿen relationalen Datenbanken, ist aber dafür wesentlich schlanker, schneller und einfacher zu bedienen. In CouchDB
ieÿen viele Erfahrungen ein, die sich im Laufe der Zeit bei der Entwicklung von WebApplikationen angesammelt haben. Deshalb wird eine RESTApi, JSON als Speicherformat
und eine Spezialisierung auf verteilte Anwendungen bei der Synchronisation verwendet.
Für eine einfache Webanwendung, die mit groÿen Mengen an Daten hantiert, ist CouchDB
deshalb eine sehr gute Lösung. Allerdings wird hierbei auf absolute Konsistenz bewuÿt verzichtet, zugunsten eines Performance-Vorteils. Dies muss im Hinterkopf behalten werden,
wenn man sich für dieses System entscheidet.
13
CouchDB
Literatur
[ALS10]
[Apa]
Anderson, J. Chris, Jan Lehnardt und Noah Slater:
Denitive Guide: Time to Relax.
CouchDB-Wiki. Liste vorhandener Server.
couchdb/View_server#Implementations.
[Edl13]
Edlich, Prof. Dr. Stefan:
CouchDB. The
OReilly, 2010.
http://wiki.apache.org/
NoSQL Archive. http://nosql-database.org,
2013. Zuletzt abgerufen am 26.05.2013.
+
[EFH 11] Edlich, Stefan, Achim Friedland, Jens Hampe, Benjamin Brauer und
Markus Brückner:
Datenbanken.
[Fro09]
NoSQL. Einstieg in die Welt nichtrelationaler Web2.0
Carl Hanser Verlag München, 2011.
Frommel, Oliver:
CouchDB: Neue Datenbank fürs Web.
http://www.linux-
magazin.de/Online-Artikel/CouchDB, 2009. Zuletzt abgerufen am 09.06.2013.
[Jan10]
Jansen, Rudolf:
ken.
CouchDB - angesagter Vertreter der "NoSQLDatenban-
http://www.heise.de/developer/artikel/CouchDB-angesagter-Vertreter-
der-NoSQL-Datenbanken-929070.html, 2010. Zuletzt abgerufen am 27.05.2013.
[WK11]
14
Wenk, Andreas und Till Klampäckel:
wickler und Administratoren.
CouchDB. Das Praxisbuch für Ent-
Galileo Press, 2011.
Seminar: Big Data Management
MongoDB
Eine Ausarbeitung von Wiebke Wilken
12.06.2013
Inhaltsverzeichnis
1.
Einleitung ............................................................................................................................ 1
2.
Merkmale einer MongoDB-Datenbank als NoSQL-Datenbank .......................................... 2
3.
Datenmodellierung ............................................................................................................. 3
3.1.
3.1.1.
Dokumente ........................................................................................................... 3
3.1.2.
Capped-Collections............................................................................................... 4
3.2.
4.
Modellieren von Beziehungen zwischen Dokumenten ............................................... 4
Mechanismen zur Durchführung von Datenoperationen .................................................. 8
4.1.
Leseoperationen .......................................................................................................... 8
4.2.
Schreiboperationen ..................................................................................................... 9
4.2.1.
Einfügen................................................................................................................ 9
4.2.2.
Aktualisieren....................................................................................................... 10
4.2.3.
Löschen............................................................................................................... 10
4.3.
Aggregatfunktionen ................................................................................................... 11
4.3.1.
Einfache Aggregatfunktionen ............................................................................. 11
4.3.2.
Aggregation-Framework .................................................................................... 11
4.4.
5.
Datentypen .................................................................................................................. 3
GridFS zur Speicherung von großen Dateien............................................................. 12
Definieren von Indizes zur Verbesserung der Performance ............................................. 14
5.1.
Überblick über die verschiedenen Typen von Indizes ............................................... 14
6.
Horizontale Fragmentierung (Sharding) ........................................................................... 16
7.
Einsatzgebiete in der Praxis .............................................................................................. 17
8.
Fazit ................................................................................................................................... 18
9.
Literaturverzeichnis .......................................................................................................... 19
i
MongoDB
1. Einleitung
Durch das Social Computing werden in kürzester Zeit sehr viele Daten erzeugt, die es gilt zu
persistieren. Die relationalen Datenbanken, die ursprünglich für das Speichern von Daten
hinzugezogen wurden, kommen hinsichtlich der Performance und Skalierbarkeit an ihre
Grenzen. So kann es sehr schnell vorkommen, dass das Laden der für die Webanwendung
relevanten Informationen sehr lange dauert. Aus diesem Grund ist das Thema der NoSQLDatenbanken populärer geworden. Mithilfe von NoSQL-Datenbanken besteht die Möglichkeit, die Leistung und Verfügbarkeit von Webanwendungen zu erhöhen, da der Fokus auf
eine große Menge an Daten gesetzt wurde. Eine der bekanntesten und meistverwendeten
NoSQL-Datenbank ist das frei zugängliche Produkt MongoDB der Firma 10gen.
Bei MongoDB handelt es sich um eine dokumentenorientierte Datenbank, mit deren Hilfe
Daten in JavaScript-ähnlichen Objekten abgespeichert werden und die somit in die Sichtweise der objektorientierten Entwicklung hineinpasst.1
Die folgende Ausarbeitung beschäftigt sich neben den Merkmalen einer MongoDB als
NoSQL-Datenbank mit der Datenmodellierung sowie den möglichen Operationen, die auf
den Daten ausgeführt werden können. Anschließend gibt es eine Einführung in die Themen
Indizes und horizontale Fragmentierung innerhalb einer MongoDB. Als letztes Thema werden Einsatzgebiete in der Praxis vorgestellt, in denen der Einsatz einer MongoDB sinnvoll ist.
Die Arbeit wird durch ein entsprechendes Fazit abgeschlossen.
1
Diego Wyllie: Datenbanksysteme für Web-Anwendungen im Vergleich
Wiebke Wilken
Seite 1 von 23
MongoDB
2. Merkmale einer MongoDB-Datenbank als NoSQL-Datenbank
Die MongoDB-Datenbank besitzt einige Merkmale, welche als Charakteristika für eine
NoSQL-Datenbank gelten. Im Folgenden werden diese Merkmale dargestellt.
Dokumentenorientierte Datenbank
Die Objekte, sogenannte Dokumente, lassen sich mit den Objekten einer objektorientierten
Programmiersprache vereinen, so dass zwischen der Datenbank- und der Anwendungsentwicklung nicht zwischen grundsätzlich unterschiedlichen Sprachen gewechselt werden muss.
Durch die Verwendung von Dokumenten in Dokumenten, sogenannten eingebetteten Dokumenten, kann die Verwendung von Join-Algorithmen, wie es von der Abfragesprache SQL
bekannt ist, reduziert werden. Es gibt innerhalb der MongoDB-Datenbank kein fest vorgegebenes Datenbankschema, so dass aufgrund der frei definierbaren Felder eine Flexibilität besteht.
Hohe Performance
Durch die Möglichkeit Objekte zu verschachteln und Dokumente in Dokumenten zu definieren, wird das Lesen und Schreiben von Daten schneller. Es müssen keine Relationen über
mehrere Tabellen hinweg gepflegt werden. Durch das Definieren von Indizes an Feldern von
Dokumenten und eingebetteten Dokumenten wird außerdem der Lese- sowie Schreibvorgang beschleunigt.
Hohe Verfügbarkeit
Eine hohe Verfügbarkeit wird durch die Möglichkeit der Replikation mit einem automatischen Failover-Cluster garantiert.
Einfache Skalierbarkeit
Durch die automatische horizontale Fragmentierung können Daten über mehrere Server
hinweg verteilt werden. Die Daten werden also auf mehrere Server aufgeteilt, so dass im
Falle eines Ausfalls auf die anderen Server ausgewichen werden kann und die Daten wiederhergestellt werden können. Das Kapitel 6 „Horizontale Fragmentierung (Sharding)“ beschäftigt sich im Detail mit diesem Thema.2
2
10gen, MongoDB: Introduction to MongoDB
Wiebke Wilken
Seite 2 von 23
MongoDB
3. Datenmodellierung
3.1. Datentypen
Die MongoDB-Datenbank ist in Collections organisiert. Eine Collection ist eine Ansammlung
von Dokumenten. Ein Dokument wiederum ist eine Sammlung von Key-Value-Pairs. Ein KeyValue-Pair stellt eine Zuordnung eines fachlichen oder technischen Schlüssels zu einem bestimmten fachlichen Wert eines Datums dar. Im Folgenden wird auf die Dokumente und die
sogenannte Capped-Collection genauer eingegangen.
3.1.1. Dokumente
Alle Daten, die in einer MongoDB gespeichert sind, sind Dokumente, welche die standardmäßige Repräsentation der Datenstrukturen darstellen. Ein Dokument in einer MongoDB ist
ein BSON-Objekt. BSON-Objekte sind ähnlich wie die JavaScript-Objekte namens JSON aufgebaut, jedoch mit dem Zusatz, dass die Dokumente in binärer Form repräsentiert werden.
Die BSON-Objekte unterstützen zudem alle verfügbaren BSON-Typen.3 Dazu gehören Datentypen wie Double, String, Object, Array, Boolean, Date, Regular Expression und JavaScript.4
Das bedeutet, dass der Wert eines Key-Value-Pairs innerhalb eines Dokuments unterschiedliche der genannten Typen annehmen kann. Als Typ kann ebenfalls ein weiteres Dokument
oder ein Array von Dokumenten definiert werden. Die Freiheit in der Verwendung der unterschiedlichen Datentypen ist auf das flexible Datenschema zurückzuführen. Dadurch können
Dokumente aller Art innerhalb einer Collection abgelegt werden.5 Einen besonderen BSONTyp stellt die sogenannte ObjectId dar. Die ObjectId ist eine eindeutige, schnell zu generierende Id, die wenig Speicherplatz in Anspruch nimmt. Jedes Dokument, welches in einer Collection abgelegt ist, benötigt eine eindeutige „_id“, die standardmäßig von der MongoDB mit
der ObjectId vorbelegt wird.6
Die meisten Dokumente, die in Collections abgelegt sind, sind Daten, welche die Benutzer
über eine Anwendung erzeugt haben. Diese Dokumente besitzen bestimmte Eigenschaften.
Die maximale Größe eines BSON-Dokuments beträgt 16 Megabytes. Dadurch wird verhindert, dass ein einziges Dokument einen zu großen Anteil des Arbeitsspeichers belegt. Um
Daten größeren Ausmaßes zu speichern, gibt es die sogenannte GridFS-API, zu der im Kapitel
3
10gen, MongoDB: Structure
10gen, MongoDB: BSON types
5
10gen, MongoDB: Structure
6
10gen, MongoDB: ObjectId
4
Wiebke Wilken
Seite 3 von 23
MongoDB
4.4 „GridFS zur Speicherung großer Dokumente“ näher eingegangen wird. Weiterhin gibt es
bei Dokumenten bestimmte Vorgaben hinsichtlich der Feldnamen. Das bereits angesprochene Feld „_id“ ist für die Verwendung als Primärschlüssel reserviert. Der Wert dieses Feldes
muss eindeutig innerhalb einer Collection sein, ist nicht veränderbar und darf jeden Typen
bis auf den des Arrays annehmen. Weiterhin dürfen Feldnamen keine Punkte und DollarZeichen enthalten.7
3.1.2. Capped-Collections
Bei einer Capped-Collection handelt es sich um eine Collection mit einer festen Größe. Sobald eine solche Collection die maximale Größe erreicht hat, werden die ältesten Dokumente mit den neuen überschrieben. Dadurch wird die Ordnung, in der die Dokumente einzufügen sind, eingehalten. Das bedeutet, dass keine Indizes benötigt werden, um Dokumente
innerhalb einer Collection schnell aufzufinden, so dass in diesem Zusammenhang der Overhead von Indizes verringert wird. Weiterhin werden die Dokumente in einer CappedCollection in der natürlichen Reihenfolge angelegt, wie es auf der Festplatte der Fall ist. Diese Reihenfolge darf aufgrund des Einsparens von Indizes nicht verändert werden, da ansonsten die Dokumente nicht mehr so schnell wiedergefunden werden können. Das bedeutet,
dass eine Aktualisierung eines Dokuments dessen Größe nicht verändern darf, da sich ansonsten der Platz in der Collection bzw. auf dem Speicher ändern und dies nicht mehr der
natürlichen Reihenfolge beim Anlegen entsprechen würde.
Capped-Collections werden häufig eingesetzt, um Loginformationen abzuspeichern. In kürzester Zeit werden sehr viele Logeinträge erzeugt, die ohne Index so schnell abgespeichert
werden können, wie direkt im Dateisystem.8
3.2. Modellieren von Beziehungen zwischen Dokumenten
Es gibt verschiedene Arten von Beziehungen, die zwischen Dokumenten bestehen können.
Es wird zwischen der eingebetteten One-to-One-, der eingebetteten One-to-Many- und der
referenzierten One-to-Many-Beziehung unterschieden. Im Folgenden werden die Arten dargestellt und deren Unterschiede herausgearbeitet.
7
8
10gen, MongoDB: Record Documents
10gen, MongoDB: Capped Collections
Wiebke Wilken
Seite 4 von 23
MongoDB
Eingebettete One-to-One-Beziehung
Bei der eingebetteten One-to-One-Beziehung stehen zwei Dokumente in direkter Beziehung
zueinander. Eingebettet bedeutet in diesem Zusammenhang, dass beim Abfragen des einen
Objekts direkt das in Beziehung stehende Objekt mitgeladen wird und nicht eine weitere
Abfrage nötig ist. Bei einer referenzierten Beziehung sind in diesem Fall zwei Abfragen notwendig. Das BSON-Objekt mit einer eingebetteten Beziehung hat den folgenden Aufbau.
{
_id: "joe",
name: "Joe Bookreader",
address: {
street: "123
Fake Street",
city: "Fakton",
state: "MA"
zip: 12345
}
}
9
Die Adresse stellt hier das eingebettete Dokument innerhalb des Personen-Dokuments dar.
Bei einer Abfrage des Dokuments mit der Id „Joe“ wird in diesem Fall direkt das Dokument
mit den Informationen über die Adresse mitgeladen. Bei einer referenzierten One-to-OneBeziehung gibt es zwei getrennte Dokumente, von denen ein Dokument einen Verweis auf
das andere hat. Folgendermaßen ist der Aufbau solcher Dokumente vorstellbar.
{
_id: "joe",
name: "Joe Bookreader"
}
{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA"
zip: 12345
}
10
9
10gen, MongoDB: Model Embedded One-to-One Relationships Between Documents
Ebd.
10
Wiebke Wilken
Seite 5 von 23
MongoDB
Bei dieser Variante sind also zwei Abfragen notwendig, um an alle relevanten Informationen
zu gelangen.11
Eingebettete One-to-Many-Beziehung
Bei einer One-to-Many-Beziehung gibt es ein Dokument, welches zu mehreren anderen Dokumenten in Beziehung stehen kann. Auch hier gibt es gegenüber der referenzierten Variante den Vorteil, dass ebenfalls nur eine Abfrage notwendig ist, um alle Daten, die ein Dokument betreffen, erhalten zu können. Das eingebettete Dokument kann bei einer One-toMany-Beziehung die folgende Struktur annehmen:
{
_id: "joe",
name: "Joe Bookreader",
addresses: [
{
street: "123
Fake Street",
city: "Faketon",
state: "MA",
zip: 12345
},
{
street: "1 Some
Other Street",
city: "Boston",
state: "MA",
zip: 12345
}
]
}
12
Mithilfe eines Arrays werden die vielen mit diesem Dokument in Beziehung stehenden Dokumente des gleichen Typs eingebettet.13
Referenzierte One-to-Many-Beziehung
Die referenzierte Variante der One-to-Many-Beziehung hat im Gegensatz zur eingebetteten
den Vorteil, dass sich die Informationen eines Dokuments, welches zu mehreren anderen
Dokumenten in Beziehung steht, nicht wiederholen und somit nur ein einziges Mal definiert
werden. Das folgende Beispiel zeigt den Aufbau einer referenzierten One-to-ManyBeziehung:
11
10gen, MongoDB: Model Embedded One-to-One Relationships Between Documents
10gen, MongoDB: Model Embedded One-to-Many Relationships Between Documents
13
Ebd.
12
Wiebke Wilken
Seite 6 von 23
MongoDB
{
_id: "oreilly",
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Di
rolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly"
}
{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB
Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher_id: "oreilly"
}
14
Der Unterschied gegenüber der eingebetteten Variante besteht darin, dass das Dokument
mit den Herausgeberinformationen nur einmal definiert und dieses innerhalb mehrerer Dokumente benötigt wird. Somit können Redundanzen vermieden werden.15
14
15
10gen, MongoDB: Model Referenced One-to-Many Relationships Between Documents
Ebd.
Wiebke Wilken
Seite 7 von 23
MongoDB
4. Mechanismen zur Durchführung von Datenoperationen
Für die Durchführung von Operationen auf Daten innerhalb einer MongoDB-Datenbank
werden unterschiedliche Schnittstellen bereitgestellt. Die sogenannten MongoDB-Treiber
unterstützen Sprachen wie C#, JavaScript, PHP, Java und Ruby. Bei der Entwicklung von Applikationen kann der Treiber anhand der genutzten Programmiersprache ausgewählt werden, ohne dass unterschiedliche Sprachen zur Entwicklung der Applikation und zur Durchführung von Datenoperationen verwendet werden müssen.16
In folgenden Codebeispielen findet der MongoDB-JavaScript-Treiber Verwendung.
4.1. Leseoperationen
Bei Leseoperationen werden die Inhalte der jeweiligen Collection innerhalb der Datenbank
abgefragt und als Ergebnisse dargestellt. Es können alle Dokumente einer Collection, aber
auch spezifische Elemente wiedergegeben werden. Zur Einschränkung der Ergebnismenge
können Bedingungen definiert und Vergleichsoperatoren verwendet werden. Mehrere Bedingungen werden mit „Und“- und „Oder“-Operationen verknüpft. Alle Leseoperationen
werden mithilfe der „Find“- und „FindOne“-Methode ermöglicht. Der Unterschied zwischen
diesen beiden Varianten ist, dass „FindOne“ nur ein einziges Dokument zurückliefert und bei
„Find“ eine Liste von Ergebnissen ermittelt wird.
Das folgende Codebeispiel zeigt, wie aus der Collection „Inventory“ alle Dokumente selektiert werden, dessen Feld „type“ dem Wert „food“ entspricht und der Preis kleiner als 9,95
ist. Weiterhin wird die Ergebnismenge mithilfe der Projektion auf zwei Felder beschränkt.
Zusätzlich zu den angegebenen Feldern wird immer das „_id“-Feld ausgegeben.
db.inventory.find( { type: 'food', price: { $lt: 9.95 } },
{ item: 1, qty: 1 } )
17
Bei dem ersten Teil einer solchen Find-Methode handelt es sich um die eigentliche Selektion
und bei dem zweiten Teil nach dem Komma um die Projektion.18
16
10gen, MongoDB: Drivers
10gen, MongoDB: Read Operations
18
Ebd.
17
Wiebke Wilken
Seite 8 von 23
MongoDB
Um Felder eines eingebetteten Dokuments oder um eine bestimmte Position eines Arrays
abzufragen, kann die „Dot-Notation“ verwendet werden. Im Falle eines Arrays kann durch
das Anhängen eines Punkts und des jeweiligen Indexes der Wert an der Position abgefragt
werden. Bei einem eingebetteten Dokument kann durch die Punktnotation das zu selektierende Feld an das Subdokument angehangen werden.19
4.2. Schreiboperationen
Alle Operationen, die der Manipulation von Daten dienen, werden den Schreiboperationen
zugeordnet. Dazu gehört das Anlegen, Ändern und Löschen von Dokumenten. Eine Schreiboperation kann immer nur innerhalb einer Collection ausgeführt werden und betrifft in einem atomaren Zustand nur ein einziges Dokument.20 Das Schreiben von Änderungen an einem Dokument mit mehreren eingebetteten Subdokumenten gilt ebenfalls als atomare Änderungsoperation. Sobald mehrere Dokumente innerhalb einer Transaktion geändert werden, die nicht unmittelbar in einer Beziehung zueinander stehen, kann es zu Verschachtelungen mit anderen Operationen kommen.21 Im Folgenden werden die Einfüge-, Aktualisierund Löschvorgänge näher erläutert.
4.2.1. Einfügen
Das Einfügen von Dokumenten in eine Collection kann auf zwei unterschiedliche Arten erfolgen. Eine Möglichkeit ist, ein definiertes Dokument mittels der „Insert“-Methode einer Collection hinzuzufügen. Handelt es sich bei dem Dokument um das erste in der Collection, wird
die Collection, sofern sie noch nicht existiert, angelegt und anschließend das Element hinzugefügt. Es besteht die Möglichkeit, das notwendige „_id“-Feld eines Dokuments mit einem
eindeutigen Schlüssel zu belegen oder aber das Feld nicht zu setzen, so dass dieses automatisch bei Anlage des Dokuments mit der ObjectId gefüllt wird.22 Werden gleichzeitig mehrere
Dokumente mittels der Insert-Operation zu einer Collection hinzugefügt, wird eine Masseneinfügung, ein sogenanntes „Bulk Insert“, durchgeführt.23
Eine weitere Möglichkeit, Dokumente zu einer Collection hinzuzufügen, bietet das Aktivieren
der „Upsert“-Option. Bei einer Update-Operation kann eine Abfrage definiert werden, die
19
10gen, MongoDB: Dot Notation
10gen, MongoDB: Write Operations
21
10gen, MongoDB: Isolation
22
10gen, MongoDB: Create
23
10gen, MongoDB: Bulk Insert Multiple Documents
20
Wiebke Wilken
Seite 9 von 23
MongoDB
ein bestimmtes Dokument zum Aktualisieren herausfiltert. Wird ein Dokument gefunden,
was der Abfrage entspricht, werden die angegebenen Aktualisierungen vorgenommen. Wird
jedoch das Dokument nicht gefunden, so wird dieses neu angelegt. Mithilfe des folgenden
Codeausschnitts kann dieses realisiert werden.
db.collection.update( <query>,
<update>,
{ upsert: true } )
24
In dem „query“-Abschnitt können die jeweiligen Einschränkungen getroffen werden, um das
Dokument zu finden. Im Update-Bereich werden die anzunehmenden Feldwerte des Dokuments spezifiziert. Über das Attribut „Upsert“ wird schließlich gesagt, dass das spezifizierte
Dokument bei einer leeren Ergebnismenge angelegt werden soll.25
4.2.2. Aktualisieren
Mithilfe der Aktualisieroperationen können die Key-Value-Pairs ein oder mehrerer Dokumente innerhalb einer Collection verändert werden. Beim Update-Befehl besteht die Möglichkeit, die zu ändernden Feldwerte und eine Bedingung zu definieren, für welches Dokument die Änderung gilt. Weiterhin kann die Option „Multi“ gesetzt werden, so dass alle Dokumente aktualisiert werden, die der definierten Bedingung entsprechen.
Innerhalb des Aktualisiervorgangs können außerdem weitere Felder zu Dokumenten hinzugefügt bzw. einzelne Felder entfernt werden. Da es sich innerhalb einer MongoDB um ein
flexibles Schema handelt, müssen durch die Hinzunahme bzw. das Entfernen von Feldern
keine Schemaänderungen vorgenommen werden.
Werden bei der Update-Methode nur Key-Value-Pairs angegeben, wird das vorhandene Dokument, was der angeforderten Selektion entspricht, vollständig durch ein neues Dokument
ersetzt.26
4.2.3. Löschen
Innerhalb einer Collection einer MongoDB-Datenbank können Dokumente entfernt werden.
In der Remove-Anweisung kann eine Bedingung definiert werden, mithilfe dessen die Doku-
24
10gen, MongoDB: Update Operations with the upsert flag
Ebd.
26
10gen, MongoDB: Update
25
Wiebke Wilken
Seite 10 von 23
MongoDB
mente eingeschränkt werden können, die von der Löschung betroffen sind. Werden keine
Bedingungen angegeben, so werden alle Dokumente der Collection gelöscht.27
Als Besonderheit gilt, dass das Löschen von Dokumenten innerhalb einer Capped-Collection
nicht möglich ist.28
4.3. Aggregatfunktionen
Die MongoDB-Datenbank bietet unterschiedliche Möglichkeiten zur Aggregation von Dokumenten. Im Folgenden werden einfache Aggregatfunktionen sowie das AggregationFramework vorgestellt.
4.3.1. Einfache Aggregatfunktionen
Zu den einfachen Aggregatfunktionen der MongoDB zählen Funktionen wie „Count“, für das
Zählen von Dokumenten innerhalb einer Collection.29 Die Funktion „Distinct“ liefert eindeutige Ergebnisse und filtert doppelte Einträge heraus. Dafür können bestimmte Schlüssel angegeben werden, um zu definieren, wann Einträge als doppelte Einträge gelten.30 Als weitere
einfache Aggregatfunktion gibt es „Group“, mithilfe dessen Dokumente zu einer Gruppe zusammengefasst werden. Auch hier gilt es einen Schlüssel zu definieren, der angibt, wann
Dokumente zu einer Gruppe hinzugefügt werden können.31
Das Aggregation-Framework stellt neben den herkömmlichen Aggregatfunktionen eine Möglichkeit dar, zusammengehörige Dokumente zu gruppieren, was im Folgenden vorgestellt
wird.
4.3.2. Aggregation-Framework
Das Aggregation-Framework dient dazu die Ergebnisliste von Dokumenten in Gruppen zusammenzufassen. Die zurückgegebenen Dokumente werden umgestaltet, indem beispielsweise berechnete Spalten oder neue virtuelle Subfelder erzeugt werden. Das Framework
erinnert an den Befehl „Group by“ von SQL.32
Als Beispiel können Dokumente folgenden Aufbaus gesehen werden.
27
10gen, MongoDB: Delete
10gen, MongoDB: Delete Capped Collections
29
10gen, MongoDB: Count
30
10gen, MongoDB: Distinct
31
10gen, MongoDB: Group
32
10gen, MongoDB: Aggregation
28
Wiebke Wilken
Seite 11 von 23
MongoDB
{
"_id": "10280",
"city": "NEW YORK",
"state": "NY",
"pop": 5574,
"loc": [
-74.016323,
40.710537
]
}
33
Eine Aggregation, die alle Staaten mit einer Population, welche größer als 10 Millionen ist,
zusammenfasst, könnte mithilfe der Aggregate-Funktion folgendermaßen erreicht werden.
db.zipcodes.aggregate( { $group :
{ _id : "$state",
totalPop : { $sum : "$pop" } } },
{ $match : {totalPop : { $gte : 10*1000*1000 } }
} )
34
Mithilfe des Group-Operators werden zunächst alle Dokumente gesammelt. Außerdem wird
pro gefundenes Dokument ein neues Dokument für jeden Staat erzeugt. In dem Id-Feld wird
die Bezeichnung des Staates abgespeichert. Weiterhin wird ein neues Feld „totalPop“ erzeugt, welches die Summe aller Populationswerte eines jeden Dokuments mit identischem
Staat enthält. Im zweiten Teil der Aggregate-Funktion wird eine Bedingung angegeben, dass
nur die Staaten ausgegeben werden, bei denen die Summe der Populationswerte 10 Millionen übersteigt.35
4.4. GridFS zur Speicherung von großen Dateien
Die Dokumente des Typs BSON dürfen eine Größe von 16 Megabyte nicht übersteigen. Aufgrund dessen wurde GridFS entwickelt, um größere Dokumente abspeichern und abfragen
zu können.
GridFS speichert eine Datei nicht in einem einzigen Dokument, sondern zerteilt die Datei in
mehrere Teile. Jedes Teil, das standardmäßig eine Größe von 256k nicht übersteigen darf,
wird als separates Dokument abgespeichert. Zur Speicherung verwendet GridFS zwei Collec33
10gen, MongoDB: Aggregation Examples
Ebd.
35
Ebd.
34
Wiebke Wilken
Seite 12 von 23
MongoDB
tions. Die eine dient dazu, die Teile bzw. den Inhalt der Datei zu halten, die andere, um Metadaten der Datei zu speichern.
Werden Dateien aus dem GridFS-Speicher abgefragt, so fügt der jeweilige genutzte Treiber
alle Teile zusammen und gibt die Datei aus. Weiterhin besteht die Möglichkeit in der Mitte
einer Datei einzusteigen, um beispielsweise bei einer Audiodatei bestimmte Phasen überspringen zu können.36
36
10gen, MongoDB: GridFS
Wiebke Wilken
Seite 13 von 23
MongoDB
5. Definieren von Indizes zur Verbesserung der Performance
Indizes können zur Verbesserung der Performance von Leseoperationen benutzt werden. Sie
sind sehr nützlich, wenn die Dokumentengröße den verfügbaren Arbeitsspeicher einnimmt.
Ein Index ist eine Datenstruktur, welche es erlaubt auf schnelle Art und Weise Dokumente
basierend auf Feldwerten zu finden. Die MongoDB-Datenbank unterstützt das Definieren
von Indizes auf jeden Feldern. Ein Index kann über einem Feld, aber auch über mehrere Felder erzeugt werden. In der MongoDB können Indizes nur pro Collection und nicht Collectionübergreifend definiert werden.37
Es gibt unterschiedliche Typen von Indizes, die im Laufe dieses Kapitels dargestellt werden.
5.1. Überblick über die verschiedenen Typen von Indizes
Die verschiedenen Typen von Indizes verfolgen unterschiedliche Ziele. Diese werden nachfolgend erläutert.
_id Index
Standardmäßig wird für jede Collection ein Index auf dem „_id“-Feld erzeugt. Dabei handelt
es sich um einen sogenannten Unique-Index, der dafür zuständig ist, dass das Id-Feld innerhalb einer Collection eindeutige Werte enthält. Da es sich bei dem Id-Feld um den Primärschlüssel eines jeden Dokuments handelt, ist es notwendig für die Eindeutigkeit zu sorgen,
um keine Werte doppelt zu vergeben und somit die Wiederauffindbarkeit von Objekten zu
gewährleisten. Der „_id-Index“ kann nicht entfernt werden.38
Secondary Indexes
Bei allen Indizes in der MongoDB-Datenbank handelt es sich um sogenannte „Secondary
Indexes“. Es können auf allen Feldern innerhalb eines Dokuments oder auf einem eingebetteten Dokument Indizes definiert werden.39
Indizes auf Feldern eingebetteter Dokumente
Innerhalb der MongoDB können außerdem auf Feldern von eingebetteten Dokumenten Indizes definiert werden.40
37
10gen, MongoDB: Indexes: Synopsis
10gen, MongoDB: Index_type_id
39
10gen, MongoDB: Secondary Indexes
38
Wiebke Wilken
Seite 14 von 23
MongoDB
Compound Indexes
Bei „Compound Indexes“ handelt es sich um Indizes, die auf mehreren Feldern eines Dokuments innerhalb einer Collection definiert werden.41
Multikey Indexes
Die MongoDB unterstützt außerdem Indizes auf Arrays. Das bedeutet, dass für jeden Wert
innerhalb des Arrays ein Index erzeugt wird. Werden in dem Array Dokumente gehalten, so
kann auch auf bestimmten Feldern aller Dokumente innerhalb des Arrays ein Index hinzugefügt werden.42
Unique Indexes
Diese Indizes dienen dazu, eine Garantie herzustellen, dass in einem Feld innerhalb eines
Dokuments in einer Collection nur eindeutige Werte gespeichert werden dürfen. Somit kann
es kein Dokument mit gleichen Feldwerten geben.43
Hashed Indexes
Bei „Hashed Indexes“ werden die Werte der indizierten Felder als Hashwert gehalten. Somit
wird nicht für alle Werte ein Index angelegt, sondern es reicht aus, wenn der Hash, der über
die zu indizierenden Feldwerte berechnet wird, erzeugt und abgespeichert wird.44
40
10gen, MongoDB: Indexes on embedded fields
10gen, MongoDB: Compound Indexes
42
10gen, MongoDB: Multikey Indexes
43
10gen, MongoDB: Unique Indexes
44
10gen, MongoDB: Hashed Indexes
41
Wiebke Wilken
Seite 15 von 23
MongoDB
6. Horizontale Fragmentierung (Sharding)
Unter horizontaler Fragmentierung (Sharding) wird das Verteilen einer logisch zusammenhängenden Datenbank auf mehrere Server verstanden. Daraus entsteht ein sogenanntes
Cluster von Maschinen.
Durch das Sharding wird das Skalieren in der Datenbank und das Verteilen der Last ermöglicht. Das bedeutet, dass die gesamte Last nicht nur von einem einzigen Server bewerkstelligt
werden muss, sondern auf mehrere verteilt werden kann. Einzelne Teile einer Collection
werden auf einen anderen Server ausgelagert, so dass das Überlaufen des Speichers vermieden wird. Falls der Speicher für alle Dokumente innerhalb der Collection nicht mehr ausreicht, kann das Cluster durch entsprechende Server erweitert werden. Die MongoDBDatenbank verteilt die Daten der Collections automatisch auf alle vorhandenen Server innerhalb des Clusters.45
45
10gen, MongoDB: Sharded Cluster
Wiebke Wilken
Seite 16 von 23
MongoDB
7. Einsatzgebiete in der Praxis
Es gibt unterschiedliche Möglichkeiten in der Praxis, in denen der Einsatz einer MongoDB
sinnvoll ist. Im diesem Kapitel werden zwei Beispiele dargestellt.
Speichern von Loginformationen
Beim Schreiben von Loginformationen produziert ein Server in kürzester Zeit eine große
Menge an Daten, die häufig in Form von Klartext in Dateien abgelegt werden. Dieses nimmt
sehr viel Speicherplatz in Anspruch. Die Auswertung von Textdateien stellt außerdem ein
Hindernis dar, falls Bedarf besteht, eine Auswertung über bestimmte Logmeldungen zu erhalten. Wird eine MongoDB zur Speicherung von Loginformationen verwendet, können
Auswertungen einfacher realisiert werden, da die Informationen in Objekten abgespeichert
werden und dafür entsprechende Methoden implementiert werden können. Des Weiteren
kann beim Speichern der Logdateien im Dateisystem der Speicher schnell überlaufen. Durch
die Möglichkeit des Sharding kann mithilfe eines Clusters auf mehrere Server ausgewichen
werden.46
Produktkatalog
In Produktkatalogen kann die Datenstruktur der einzelnen Produkte sehr unterschiedlich
sein. Es können unterschiedliche Felder benötigt werden, um Produkte zu spezifizieren. Weiterhin kommen sehr viele Daten zusammen, so dass die Möglichkeit geschaffen werden
muss, in kürzester Zeit Daten abfragen zu können. In diesem Zusammenhang bietet sich die
MongoDB-Datenbank an, da zum einen ein flexibles Schema unterstützt wird, so dass keine
festen Vorgaben bezüglich der Objektstrukturen bestehen. Zum anderen können bei einer
großen Menge an Daten geeignete Indizes Verwendung finden, so dass das Lesen und
Schreiben von Daten beschleunigt wird.47
46
47
10gen, MongoDB: Storing Log Data
10gen, MongoDB: Product Catalog
Wiebke Wilken
Seite 17 von 23
MongoDB
8. Fazit
Eine NoSQL-Datenbank wie MongoDB lässt sich nicht nur, wie eingangs erwähnt, innerhalb
der „Social Computing“-Anwendungen nutzen, sondern bietet beispielsweise genauso Einsatzmöglichkeiten im Bereich des Produktmanagements oder der Verwaltung von Logmeldungen. Bei einer MongoDB-Datenbank können Indizes auf unterschiedliche Art und Weise
definiert werden, um das Abfragen sowie Schreiben von Dokumenten zu beschleunigen.
Weiterhin ist die Möglichkeit die Datenbank in einem Cluster zu organisieren besonders bei
einer großen Anzahl an Daten hilfreich, damit der Zugriff möglichst effizient erfolgt.
Durch die Ablage der Daten bzw. Dateien in Form von Objekten, kann dies sehr leicht in die
Umgebung bzw. in die verwendete Programmiersprache integriert werden. So ist es für den
Entwickler nicht zwangsläufig notwendig, weitere Sprachen für die Kommunikation mit der
Datenbank zu erlernen und zu verwenden. Durch die Vielzahl von Treibern, die das Unternehmen 10gen zur Verfügung stellt, kann die Syntax der jeweiligen Sprache erhalten bleiben.
Es werden lediglich Verwendungsformen der API zu erlernen sein. Weiterhin kann durch das
Speichern der Daten in Objekte auf ein Mapping in Entities verzichtet werden, da die Objekte unmittelbar zur Verfügung stehen.
Da die Datenstruktur einer MongoDB-Datenbank flexibel ist, können Dokumente aller Art
innerhalb einer Collection abgelegt werden. Dieses kann ein Vorteil sein, wenn sehr unterschiedliche Daten in einer Anwendung erzeugt werden, wie es beispielsweise bei dem Produktkatalog der Fall ist. Dadurch müssen die Produkte nicht alle in der gleichen Form präsentiert werden, sondern können individuelle Eigenschaften besitzen. Die flexible Datenstruktur
kann jedoch auch zu einem Nachteil werden, da innerhalb der Anwendung nicht offensichtlich ist, welche Felder ein Objekt aus der Datenbank bereitstellt. Somit muss zunächst immer
eine Identifikation stattfinden, um welche Art von Dokument es sich handelt.
Wiebke Wilken
Seite 18 von 23
MongoDB
9. Literaturverzeichnis
Diego Wyllie (2013): Datenbanksysteme für Web-Anwendungen im Vergleich,
http://www.computerwoche.de/a/datenbanksysteme-fuer-web-anwendungen-imvergleich,2496589, 2013, Einsichtnahme: 08.06.2013
10gen, MongoDB (2013): Introduction to MongoDB,
http://www.mongodb.org/about/introduction/, 2013, Einsichtnahme: 08.06.2013
10gen, MongoDB (2013): Structure,
http://docs.mongodb.org/manual/core/document/#structure, 2013, Einsichtnahme:
08.06.2013
10gen, MongoDB (2013): BSON types,
http://docs.mongodb.org/manual/reference/glossary/#term-bson-types, 2013, Einsichtnahme: 08.06.2013
10gen, MongoDB (2013): ObjectId, http://docs.mongodb.org/manual/reference/object-id/,
2013, Einsichtnahme: 08.06.2013
10gen, MongoDB (2013): Record Documents,
http://docs.mongodb.org/manual/core/document/#record-documents, 2013, Einsichtnahme: 08.06.2013
10gen, MongoDB (2013): Capped Collections,
http://docs.mongodb.org/manual/core/capped-collections/, 2013, Einsichtnahme:
08.06.2013
10gen, MongoDB (2013): Model Embedded One-to-One Relationships Between Documents,
http://docs.mongodb.org/manual/tutorial/model-embedded-one-to-one-relationshipsbetween-documents/, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Model Embedded One-to-Many Relationships Between
Documents, http://docs.mongodb.org/manual/tutorial/model-embedded-one-to-manyrelationships-between-documents/, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Model Referenced One-to-Many Relationships Between
Documents, http://docs.mongodb.org/manual/tutorial/model-referenced-one-to-manyrelationships-between-documents/, 2013, Einsichtnahme: 09.06.2013
Wiebke Wilken
Seite 19 von 23
MongoDB
10gen, MongoDB (2013): Drivers, http://docs.mongodb.org/manual/applications/drivers/,
2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Read Operations, http://docs.mongodb.org/manual/core/readoperations/, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Dot Notations,
http://docs.mongodb.org/manual/reference/glossary/#term-dot-notation, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Write Operations, http://docs.mongodb.org/manual/core/writeoperations/#write-operations, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Isolation, http://docs.mongodb.org/manual/core/writeoperations/#isolation, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Create, http://docs.mongodb.org/manual/core/create/, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Bulk Insert Multiple Documents,
http://docs.mongodb.org/manual/core/create/#bulk-insert-multiple-documents, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Update Operations with the upsert flag,
http://docs.mongodb.org/manual/core/create/#update-operations-with-the-upsert-flag,
2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Update, http://docs.mongodb.org/manual/core/update/, 2013,
Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Delete, http://docs.mongodb.org/manual/core/delete/, 2013, Einsichtnahme: 09.06.2013
10gen, MongoDB (2013): Delete Capped Collections,
http://docs.mongodb.org/manual/core/delete/#capped-collection, 2013, Einsichtnahme:
09.06.2013
Wiebke Wilken
Seite 20 von 23
MongoDB
10gen, MongoDB (2013): Count,
http://docs.mongodb.org/manual/reference/command/count/, 2013, Einsichtnahme:
10.06.2013
10gen, MongoDB (2013): Distinct,
http://docs.mongodb.org/manual/reference/command/distinct/, 2013, Einsichtnahme:
10.06.2013
10gen, MongoDB (2013): Group,
http://docs.mongodb.org/manual/reference/command/group/, 2013, Einsichtnahme:
10.06.2013
10gen, MongoDB (2013): Aggregation, http://docs.mongodb.org/manual/core/aggregation/,
2013, Einsichtnahme: 10.06.2013
10gen, MongoDB (2013): Aggregation Examples,
http://docs.mongodb.org/manual/tutorial/aggregation-examples/, 2013, Einsichtnahme:
10.06.2013
10gen, MongoDB (2013): GridFS, http://docs.mongodb.org/manual/core/gridfs/, 2013, Einsichtnahme: 10.06.2013
10gen, MongoDB (2013): Indexes: Synopsis,
http://docs.mongodb.org/manual/core/indexes/#synopsis, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Indexes: index-type-id,
http://docs.mongodb.org/manual/core/indexes/#index-type-id, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Secondary Indexes,
http://docs.mongodb.org/manual/core/indexes/#secondary-indexes, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Indexes on embedded fields,
http://docs.mongodb.org/manual/core/indexes/#indexes-on-embedded-fields, 2013, Einsichtnahme: 11.06.2013
Wiebke Wilken
Seite 21 von 23
MongoDB
10gen, MongoDB (2013): Compound Indexes,
http://docs.mongodb.org/manual/core/indexes/#compound-indexes, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Multikey Indexes,
http://docs.mongodb.org/manual/core/indexes/#multikey-indexes, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Unique Indexes,
http://docs.mongodb.org/manual/core/indexes/#unique-indexes, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Hashed Indexes,
http://docs.mongodb.org/manual/core/indexes/#hashed-index, 2013, Einsichtnahme:
11.06.2013
10gen, MongoDB (2013): Sharded Clusters, http://docs.mongodb.org/manual/core/shardedclusters/, 2013, Einsichtnahme: 11.06.2013
10gen, MongoDB (2013): Storing Log Data, http://docs.mongodb.org/manual/usecases/storing-log-data/, 2013, Einsichtnahme: 11.06.2013
10gen, MongoDB (2013): Product Catalog, http://docs.mongodb.org/manual/usecases/product-catalog/, 2013, Einsichtnahme: 11.06.2013
Wiebke Wilken
Seite 22 von 23
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 14
Cassandra
Referent: Jan Kristof Nidzwetzki
2
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Inhaltsverzeichnis
1 Einleitung
1.1 Geschichte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Einsatzbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Cassandra
2.1 Datenmodell . . . . . . . . . . . . . . .
2.1.1 Schlüsselräume . . . . . . . . .
2.1.2 Spaltenfamilien . . . . . . . . .
2.1.3 Spalten . . . . . . . . . . . . .
2.1.4 Zeilen . . . . . . . . . . . . . .
2.1.5 Superspalten . . . . . . . . . .
2.2 Architektur von Cassandra . . . . . . .
2.2.1 Partitionierer . . . . . . . . . .
2.2.2 Replikation . . . . . . . . . . .
2.2.3 Snitches . . . . . . . . . . . . .
2.2.4 Peer-to-Peer und Gossip . . . .
2.3 Lesen und Schreiben von Daten . . . .
2.3.1 Tunable Consistency . . . . . .
2.3.2 Hinted Handoff . . . . . . . . .
2.3.3 Anti-Entropy und Read Repair
2.3.4 Persistenz . . . . . . . . . . . .
2.4 Sicherheit . . . . . . . . . . . . . . . .
2.5 Performance . . . . . . . . . . . . . . .
3
3
4
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
6
6
6
6
7
8
8
9
10
11
11
12
13
14
15
16
3 Erweiterungen von Cassandra
3.1 CQL – Cassandra Query Language . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Integration von Hadoop . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
17
18
4 Fazit
19
Literaturverzeichnis
20
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Jan Kristof Nidzwetzki, Thema 14: Cassandra
3
Abstract
Apache Cassandra ist eine NoSQL-Datenbank, welche darauf ausgelegt ist, große
Datenmengen auf einem Verbund von Servern zu verarbeiten. Die Ziele von Apache
Cassandra sind hohe Verfügbarkeit, sowie gute Skalierbarkeit. Daten werden dazu redundant gespeichert und es existiert kein single point of failure in der Architektur der
Software. Mittels Tunable Consistency kann bei jedem Lese- oder Schreibzugriff festgelegt werden, wie viele Server an diesem beteiligt sein müssen. Dies erlaubt, für jeden
Zugriff zu entscheiden, ob Performance oder Konsistenz im Vordergrund stehen.
In der vorliegenden Arbeit wird die Software Cassandra vorgestellt. Ebenso wird
kurz auf einige neuere Funktionen, wie die Abfragesprache CQL – Cassandra Query
Langugage und die Anbindung an das Map-Reduce Framework Hadoop, eingegangen.
1 Einleitung
Die Menge der weltweit gespeicherten Daten nimmt rasant zu. Diese Datenmenge ist mit traditionellen Relationalen Datenbank Management Systemen (RDBMS ) nur schwer zu verarbeiten. Seit einigen Jahren erfreuen sich sogenannte NoSQL-Datenbanken großer Beliebtheit.
NoSQL-Datenbanken verzichten auf einige Eigenschaften, welche RDBMS bieten. Beispielsweise fehlen oft Transaktionen oder eine durchgängig konsistente Sicht auf den Datenbestand.
Dafür bieten NoSQL-Datenbanken häufig bessere Skalierbarkeit und Ausfallsicherheit.
In der folgenden Arbeit wird das Datenbankmanagementsystem Cassandra vorgestellt.
Diese Software gehört zu der Familie der NoSQL-Datenbanken und wird heutzutage von
vielen Firmen eingesetzt, um große Mengen von Daten zu verarbeiten.
1.1 Geschichte
Das Cassandra Projekt wurde im Jahr 2007 von dem Betreiber der Social-Network Webseite
Facebook initiiert. Facebook bietet Benutzern auf der gleichnamigen Webseite die Möglichkeit, Nachrichten auszutauschen. Mit mehr als 10 Millionen Nutzern im Jahr 2007 stieß
Facebook an die Grenzen traditioneller RDBMS. Diese konnten die Nachrichten nur mit
einiger Verzögerung bereitstellen und skalierten nicht sonderlich gut [LM10] [LM09].
Facebook stellte ein Team von Entwicklern zusammen, um dieses Problem zu lösen. Das
Team entwickelte in den darauf folgenden Monaten die Software Cassandra. Die Software
wurde im Juli 2008 in ein Projekt bei Google Code überführt. Der Quelltext war ab diesem
Zeitpunkt frei verfügbar, jedoch konnten zunächst nur Mitarbeiter von Facebook Veränderungen vornehmen. Im Jahr 2009 wurde das Projekt an die Apache Software Foundation
übergeben [Hew10, S. 24]. Die Software wurde in Apache Cassandra umbenannt und steht
mittlerweile unter der Lizenz Apache License 2.0 [WIK13].
Apache Cassandra weist eine aktive Entwicklergemeinde auf und es erscheinen fortlaufend
neue Versionen der Software. Zum aktuellen Zeitpunkt, Mai 2013, steht die Version 1.2.2 auf
den Seiten des Projektes zum Download bereit.
4
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Im Jahr 2010 entwickelte Facebook sein Nachrichten-System von Grund auf neu. In dem
neuen System wurde auf den Einsatz von Cassandra verzichtet. Die Ablage und das Durchsuchen der Nachrichten erfolgt nun auf der Basis der Software HBase [FAC13].
1.2 Grundlagen
Die Software Cassandra ist ein verteiltes Datenbankmanagementsystem. Cassandra ist mit
dem Ziel entwickelt worden, große Mengen an Daten auf Standardhardware (Knoten) zu
verarbeiten. Weitere Ziele von Cassandra sind: hohe Verfügbarkeit, Skalierbarkeit und Fehlertoleranz. Darüber hinaus ist das Konzept der Tunable Consistency umgesetzt: Clients
können bei jeder Lese- oder Schreiboperationen festlegen, auf wie vielen Knoten diese stattfinden soll. Eine größere Anzahl von Knoten führt zu höherer Konsistenz, jedoch auch zu
schlechterer Performance (siehe Abschnitt 2.3.1) [CAS13b].
Geschrieben ist Cassandra in der Programmiersprache Java. Die Architektur von Cassandra orientiert sich an der Software Google Bigtable [CDG+ 08], das Datenmodell an der
Software Amazon Dynamo [DHJ+ 07]. Architektur und Datenmodell werden in Abschnitt 2
genauer beschrieben.
Der Autor Eben Hewitt beschreibt in seinem Buch Cassandra: The Definitive Guide die
Software Cassandra in 50 Wörtern wie folgt [Hew10, S.14]:
Apache Cassandra is an open source, distributed, decentralized, elastically scala”
ble, highly available, fault-tolerant, tuneably consistent, column-oriented database
that bases its distribution design on Amazon’s Dynamo and its data model on
Google’s Bigtable. Created at Facebook, it is now used at some of the most popular sites on the Web.“
1.3 Einsatzbereiche
Heutzutage setzen viele Unternehmen Cassandra ein. Insbesondere Unternehmen, welche
große Mengen an Daten zu speichern haben oder ein großes Wachstum der Daten erwarten,
setzten auf Cassandra. Hierzu zählen unter anderem [CAS13a]:
eBay: Die Internet Auktionsplattform eBay speichert Informationen über verkaufte Produkte in Cassandra.
IBM: Die Firma IBM bietet mit ihrem Produkt BlueRunner eine auf Cassandra basierende
E-Mail Anwendung an.
Next Big Sound: Der Musikanbieter Next Big Sound speichert die Hörgewohnheiten seiner
Nutzer in Cassandra ab.
Rackspace: Der Serverhoster Rackspace nutzt Cassandra zum Speichern und Auswerten
von Logfiles.
Twitter: Der Kurznachrichtendienst Twitter setzt Cassandra zur Analyse von Nachrichten
ein.
Häufig wird Cassandra in Kombination mit Hadoop eingesetzt. Cassandra übernimmt
das Bereitstellen der Daten, Hadoop wird für die Auswertung der Daten eingesetzt (siehe
Abschnitt 3.2).
5
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2 Cassandra
Zu Beginn dieses Abschnitts wird das Datenmodell von Cassandra beschrieben. Anschließend
wird die Architektur von Cassandra vorgestellt.
2.1 Datenmodell
Cassandras Datenmodel wird zu den Spaltenorientierten Datenmodellen (Column oriented
data models) gezählt. Zum Überblick: in einem Schlüsselraum werden Spaltenfamilien definiert. Jede Spaltenfamilie besitzt eine oder mehrere Spalten. Zusammengehörende Werte
werden in Zeilen mit eindeutigem Zeilenschlüssel zusammengefasst (Abbildung 1). In den
folgenden Abschnitten werden diese Begriffe genauer beschrieben.
Spaltenfamilie 1
Spalte 1
Spalte 2
Spalte 3
Wert 1
Wert 2
Wert 3
Zeilenschlüssel 2
Spaltenfamilie 1
Spalte 1
Spalte 4
Wert 1
Wert 4
Zeilenschlüssel 1
Schlüsselraum 1
Abbildung 1: Datenmodell von Cassandra (nach [Hew10, S.44]). Im Schlüsselraum Schlüsselraum 1 existieren zwei Zeilen mit den Zeilenschlüsseln
Zeilenschlüssel 1 und Zeilenschlüssel 2. Beide Zeilen gehören zur Spaltenfamilie Spaltenfamilie 1. Ihnen sind verschiedene Spalten mit Werten
zugeordnet.
2.1.1 Schlüsselräume
Schlüsselräume (Keyspaces) werden in Cassandra dazu eingesetzt, unterschiedliche Daten
voneinander zu trennen. Ein Schlüsselraum ist mit einer Datenbank in einem RDBMS zu
vergleichen. Schlüsselräume besitzen Metadaten, welche das Verhalten des Schlüsselraumes
festlegen. Hierzu zählen unter anderem [Hew10, S.46]:
Replikationsfaktor: Dieser Faktor legt fest, auf wie viele Knoten im Schlüsselraum gespeicherte Daten repliziert werden (siehe Abschnitt 2.2).
Platzierungsstategie für Replikate: Diese Strategie legt fest, wie Replikate auf unterschiedliche Knoten verteilt werden (siehe Abschnitt 2.2.2).
6
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2.1.2 Spaltenfamilien
Spaltenfamilien (Column Families) beschreiben das Format der abgelegten Daten. Sie sind
mit Tabellen in RDBMS zu vergleichen, weisen jedoch grundlegende Unterschiede auf. So
können in Spaltenfamilien jederzeit neue Spalten eingefügt werden, ohne die bestehenden
Daten zu beeinflussen. Eine Spaltenfamilie besteht aus einem Namen und einem Comperator,
welcher festlegt, wie Daten in dieser sortiert werden sollen [DAT13a].
2.1.3 Spalten
Vergleichbar zu Attributwerten in RDBMS besitzt das Datenmodell Spalten (Columns).
Diese bilden die kleinste Einheit im Datenmodel und sind für das Speichern von Name/Wert-Paaren zuständig. Eine Spalte besteht aus einem Namen, dem dazugehörigen Wert
und einen Zeitstempel (Timestamp).
Der Zeitstempel wird in Mikrosekunden angegeben. Er beschreibt, wann die Spalte das
letzte Mal geändert worden ist. Der Zeitstempel wird zur Versionierung der Daten eingesetzt
(siehe Abschnitt 2.3.3). Für Name und Wert können in Cassandra beliebige Byte-Arrays
verwendet werden.
2.1.4 Zeilen
Wie in Abbildung 1 dargestellt, werden zusammengehörige Werte von Spalten in einer Zeile
(Row ) zusammengefasst. In RDBMS ist dies mit einem Tupel zu vergleichen. Identifiziert
werden Zeilen über einen eindeutigen Zeilenschlüssel.
Beim Erzeugen einer Zeile müssen nicht alle in der Spaltenfamilie definierten Spalten mit
Werten versehen werden. In Abbildung 2 sind zwei Zeilen mit den Zeilenschlüsseln user4711
und user0815 definiert. Beide enthalten Daten der Spaltenfamilie Person. Für die erste Zeile
sind drei Spalten angegeben; für die zweite Zeile sind nur zwei Spalten angegeben.
Um in Cassandra auf Zeilen zuzugreifen, muss ihr Zeilenschlüssel bekannt sein. Alternativ
lassen sich Sekundärindizes anlegen, um Zeilen mit bestimmten Eigenschaften finden zu
können. Würde man für die Spalte Nachname in Abbildung 2 einen solchen Index anlegen,
ließen sich alle Zeilen ermitteln, in denen Nachname z. B. den Wert Müller“ annimmt.
”
2.1.5 Superspalten
Neben den vorgestellten Spalten existieren in Cassandra noch Superspalten (Super Colums).
Superspalten weichen von dem bisher vorgestellten Name-/Wert-Modell ab. In Superspalten
wird der Wert durch ein Array von Spaltenfamilien repräsentiert.
Superspalten werden eingesetzt, wenn Sammlungen von strukturierten Informationen gespeichert werden sollen. In der aktuellen Dokumentation von Cassandra werden Superspalten
als Anti-Pattern“ aufgeführt. Diese gelten als veraltet, liefern eine schlechte Performance
”
und werden eventuell in zukünftigen Versionen nicht mehr unterstützt1 .
1
Do not use super columns. They are a legacy design from a pre-open source release. This design was
”
structured for a specific use case and does not fit most use cases. [...] Additionally, super columns are not
supported in CQL 3.“ [DAT13a]
7
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Person
Vorname
Nachname
Alter
user4711
Jörg
Hansen
27
Zeitstempel: 3
Zeitstempel: 3
Zeitstempel: 3
Person
Vorname
E-Mail
user0815
Otto
mail@domain
Zeitstempel: 56
Zeitstempel: 45
Schlüsselraum 1
Abbildung 2: Das Datenmodell am Beispiel: Im Schlüsselraum Schlüsselraum 1 existieren
zwei Zeilen mit den Zeilenschlüsseln user0815 und user4711. Beide Zeilen
gehören zur Spaltenfamilie Person. In beiden Zeilen sind verschiedene Spalten
angegeben: Vorname, Nachname, Alter und E-Mail. In jeder Spalte ist durch
einen Zeitstempel vermerkt, wann diese das letzte Mal geändert worden ist.
2.2 Architektur von Cassandra
Ein Ziel von Cassandra ist es, die Verfügbarkeit von Daten auch dann sicherzustellen, wenn
einzelne Knoten ausfallen. Hierzu wird ein Verbund von mehreren Knoten (ein Cluster ) gebildet. Die Daten werden redundant auf mehreren Knoten abgelegt. Die Verteilung der Daten
auf die Server wird durch einen Partitionierer (siehe Abschnitt 2.2.1) gesteuert.
Der Partitionierer bildet den Zeilenschlüssel einer Zeile in einen konstanten Wertebereich
ab. Dieser Wertebereich wird in Cassandra in einem logischen Ring angeordnet. Zeilen werden gemäß des Partitionierers im Ring platziert. Jeder Knoten erhält beim ersten Start
einen Identifizierer, den Token, aus dem gleichen Wertebereich. An diese Position fügt der
Knoten sich in den Ring ein. Ein Knoten ist für die Werte zuständig, welche zwischen ihm
und seinem Vorgänger liegen. In Abbildung 3 ist der logische Ring und die Replikation von
Daten dargestellt. Partionierer können die Abbildung z. B. mit Hashfunktionen (Consistent
Hashing) vornehmen [KLL+ 97]. Es wird mit einem konstanten Wertebereich gearbeitet um
die Anzahl der Knoten verändern zu können ohne die Daten aller Knoten neu aufteilen zu
müssen. Der Wertebereich ist in Abbildung 3 als Intervall [0, 1] dargestellt.
Wird ein neuer Knoten in den Ring eingefügt, so analysiert er, welcher Knoten derzeit die
meisten Daten vorhalten muss. Um diesen Knoten zu entlasten, wählt der neue Knoten einen
Token, welcher zwischen diesem Knoten und seinem Vorgänger liegt. Gemäß des Wertes des
Tokens fügt er sich in den logischen Ring ein und nimmt dem stark belasteten Knoten einen
Teil seiner Daten ab.
8
Jan Kristof Nidzwetzki, Thema 14: Cassandra
1 0
G
A
F
E
p(Zeilenschlüssel )
B
D
C
Abbildung 3: Ablage und Replikation von Zeilen: Die Knoten eines Clusters teilen die Werte
des logischen Rings untereinander auf. Zeilen werden gemäß des Partionierers
P im Ring abgelegt. Die Zeile mit dem Wert p(Zeilenschlüssel1 ) wird auf den
nachfolgenden Knoten E abgelegt. Zusätzlich wird ein Replikat auf den Knoten
F und G abgelegt.
2.2.1 Partitionierer
Partitionierer sind dafür zuständig, aus dem Zeilenschlüssel einer Zeile die Position im logischen Ring zu berechnen. Sie legen damit fest, wie Daten auf die Knoten aufgeteilt werden. Cassandra stellt in der Version 1.1 zwei Partitionierer zur Verfügung. Ebenfalls können
durch die Implementierung der Schnittstelle org.apache.cassandra.dht.IPartitioner eigene Partitionierer implementiert werden.
Random Partitioner: Dieser Partitionierer wird im Standardfall verwendet. Er berechnet
den MD5-Hash eines Zeilenschlüssels. Hierdurch werden die Zeilen gleichmäßig über
alle Knoten im Cluster verteilt. Der Wertebereich dieses Partitionierers beträgt 0 bis
2127 − 1.
Byte-Ordered Partitioner: Zeilenschlüssel werden in Cassandra durch Byte-Arrays dargestellt. Dieser Partitionierer verwendet dieses Byte-Array für die Positionierung der
Zeilen im Ring. Daten mit ähnlichen Zeilenschlüssel werden nah beieinander gespeichert. Dies kann sich positiv auf Abfragen auswirken, welche auf einem Bereich von
Zeilenschlüssel operieren. Die benachbarte Speicherung ähnlicher Zeilen kann sich auch
nachteilig auswirken, da bestimmte Bereiche im Ring stärker genutzt werden als andere. Dies führt zu sogenannten hot spots: Knoten die stärker belastet sind als andere.
2.2.2 Replikation
Der Partitionierer legt fest, auf welchen Knoten Zeilen primär gespeichert werden. Um Ausfallsicherheit zu erreichen, wird jede Zeile mehrfach gespeichert. Auf welchen Knoten die
Replikate abgelegt werden, wird von der Platzierungsstategie für Replikate bestimmt. Wurde
beim Anlegen des Schlüsselraums ein Replikationsfaktor von 1 angegeben, so werden Zeilen
nur auf dem durch den Partitionierer bestimmten Knoten gespeichert. Wird der Replikationsfaktor auf N > 1 gesetzt, werden N − 1 Replikate auf anderen Knoten abgelegt.
9
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Cassandra bietet verschiedene Platzierungsstategien für Replikate an. Welche Strategie
verwendet werden sollte, gibt die zugrunde liegende physikalische Verteilung der Knoten vor.
Grundlegend wird zwischen Knoten im gleichen Rack und Knoten im gleichen Datacenter
unterschieden. Es wird davon ausgegangen, dass (i) einzelne Knoten, (ii) komplette Racks
oder (iii) komplette Datacenter ausfallen können. Um diese Ausfälle kompensieren zu können,
müssen die Replikate in verschiedenen Racks und in verschiedenen Datacentern abgelegt
werden. In der aktuellen Version bietet Cassandra die folgenden Strategien an:
Simple Strategy: Replikate werden bei den nächsten Knoten im logischen Ring abgelegt.
Die zugrunde liegende Topologie des Netzwerkes wird nicht berücksichtigt.
Old Network Topology Strategy: Es wird ein Replikat in einem zweiten Datacenter abgelegt. Alle weiteren Replikate werden über die Racks im ersten Datacenter verteilt.
Network Topology Strategy: Diese ähnelt der Old Network Topology Strategy. Hierbei
wird jedoch mehr als 1 Replikat im zweiten Datacenter untergebracht.
2.2.3 Snitches
Die beiden letztgenannten Replica Placement Strategies benötigen Informationen, in welchen
Racks und in welchen Datacentern sich welche Knoten befinden. Diese Informationen werden
von Snitches bereitgestellt. Die standardmäßig von Cassandra verwendete Simple Snitch
berechnet diese Informationen aus IP-Adressen. Knoten mit IPv4-Adressen mit gleichen
Werten im ersten und zweiten Oktett befinden sich im gleichen Datacenter. Ist auch das
dritte Oktett identisch, so befinden sich diese Knoten im gleichen Rack.
IP v4−Adresse
Beispiel:
}|
z
192.
100.
| {z168.} |{z}
Datacenter
Rack
{
001
|{z}
Knoten
Um komplexere Netzwerktopologien abbilden zu können, existiert zudem eine konfigurierbare Snitch, die PropertyFileSnitch. In dieser können Beziehungen zwischen Knoten,
Racks und Datacentern manuell hinterlegt werden. Ein Beispiel für eine solche Konfiguration
ist in Listing 1 aufgeführt. In dieser Konfiguration existieren sechs Knoten, zwei Datacenter
(DC1 und DC2) und in jedem Datacenter zwei Racks (RAC1 und RAC2).
Listing 1: Snitch Konfiguration mittels PropertyFileSnitch
1
2
3
4
# Data Center One
10.0.0.1= DC1 : RAC1
10.0.0.8= DC1 : RAC1
10.1.4.7= DC1 : RAC2
5
6
7
8
9
# Data Center Two
10.5.2.1= DC2 : RAC1
10.5.2.2= DC2 : RAC1
10.5.3.1= DC2 : RAC2
10
11
12
# default for unknown nodes
default = DC1 : RAC1
10
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2.2.4 Peer-to-Peer und Gossip
In vielen verteilten Systemen finden sich zwei unterschiedliche Klassen von Systemen: Koordinatoren und Arbeiter. Die Arbeiter sind für die Verarbeitung der Anfragen zuständig. Die
Koordinatoren übernehmen Aufgaben wie das Verteilen von Anfragen oder das Prüfen, ob
alle Mitglieder erreichbar sind. Oft stellen diese Koordinatoren einen single point of failure
dar. Fällt der Koordinator aus, ist das gesamte System nicht mehr funktionsfähig.
In Cassandra kommt eine Peer-to-Peer Architektur zum Einsatz: alle Knoten nehmen die
gleichen Aufgaben war. Anfragen können an jeden Knoten gestellt werden und der Ausfall eines Knotens sorgt höchstens für eine verringerte Leistungsfähigkeit des Systems, nicht
jedoch für den Ausfall des gesamten Systems. Zudem sorgt die Architektur dafür, dass problemlos weitere Knoten in das System integriert werden können (siehe Abschnitt 2.2).
Es wird ein Gossip protocol [DGH+ 87] für die Kommunikation der Knoten untereinander verwendet. Periodisch tauschen dazu Knoten Gossip-Nachrichten aus. Aus Sicht eines
Knotens (dem Gossiper ) sieht die Kommunikation wie folgt aus [Hew10, S. 89]:
1. Alle n Sekunden wählt der Gossiper (G) zufällig einen Knoten (K) aus seiner Nachbarschaft aus und beginnt mit der Kommunikation.
2. G sendet K eine GossipDigestSynMessage.
3. Empfängt K die Nachricht, so bestätigt er dies mit Versand einer GossipDigestAck
Message an G.
4. Den Empfang der Nachricht von K bestätigt G wiederum mit dem Versand einer
GossipDigestAck2Message an K.
Erhält der Gossiper keine Antwort auf seine GossipDigestSynMessage geht er davon aus,
dass der Knoten derzeit nicht erreichbar oder die Nachricht bei der Übertragung verloren
gegangen ist. Es wird von Cassandra eine Implementation des Φ Accrual Failure Detector
[HDYK04] eingesetzt. Dieser Detector sorgt dafür, dass Knoten erst nach einer bestimmten
Zeit als nicht erreichbar markiert werden.
Durch die Nachrichten erhält jeder Knoten mit der Zeit Informationen über seine Nachbarn. Neben der IP-Adresse wird die Menge der gespeicherten Informationen (Load ), sowie
die Positionen im Ring (Token) ausgetauscht. Mit dem Programm nodetool lassen sich diese
Informationen anzeigen. In Listing 2 sieht man einen Ring mit drei Knoten.
Listing 2: Ein Logischer Ring mit drei Knoten
1
root@node1 :˜# / root / cassandra / bin / nodetool ring
2
3
4
5
Datacenter : datacenter1
==========
Address Rack Status State
Load
6
7
8
9
node1
node2
node3
rack1 Up
rack1 Up
rack1 Up
Normal
Normal
Normal
93.96 KB
42.59 KB
1.5 MB
Owns
Token
3955191628143462120
30.07% -8944999014129822443
44.34% -766207061079759187
25.59% 3955191628143462120
11
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2.3 Lesen und Schreiben von Daten
In diesem Abschnitt wird beschrieben, wie Lese- und Schreibanforderungen behandelt werden. Es wird zudem die Idee der Tunable Consistency genauer erläutert. Darüber hinaus
werden drei Konzepte zum Beheben von Inkonsistenzen betrachtet: (i) Hinted Handoffs,
(ii) Anti-Entropy und (iii) Read Repair.
Um Daten zu lesen oder zu schreiben, kann sich ein Client mit jedem beliebigen Knoten
verbinden. Dieser Knoten übernimmt dann die Rolle eines Koordinierenden Knotens. Schematisch ist dies in der Abbildung 4 dargestellt. Der Knoten E übernimmt dort die Rolle des
Koordinierenden Knotens. Dieser leitet die Anfragen des Clients an die zuständigen Knoten weiter. An wie viele Knoten die Anfragen weitergeleitet werden, hängt vom gewählten
Konsistenz-Level und dem genutzten Replikationsfaktor ab (siehe Abschnitt 2.3.1).
1 0
F
Client
1 0
A
E
F
B
D
(a) Lesen von Daten
C
Client
A
E
B
D
C
(b) Schreiben von Daten
Abbildung 4: Lesen und schreiben von Daten. Der Client verbindet sich mit einem Knoten
im Ring. An diesen Knoten sendet er seine Lese- und Schreibanforderungen.
Dieser Knoten leitet die Anfragen an die zuständigen Knoten (A, B, C) weiter.
Abhängig von der gewählten Konsistenz werden die Anfragen an unterschiedlich viele Knoten weitergeleitet.
2.3.1 Tunable Consistency
Clients spezifizieren bei Lese- oder Schreiboperationen den Konsistenz-Level, welchen sie für
die Anfrage wünschen. Die möglichen Konsistenz-Level für das Lesen sind in Tabelle 1, die
für das Schreiben in Tabelle 2, aufgeführt. Um so höher der Konsistenz-Level gewählt wird,
desto mehr Knoten sind an der Anfrage beteiligt. Dies sorgt für verbesserte Konsistenz,
jedoch für schlechtere Performance. Das individuelle Festlegen des Konsistenz-Level bei Anfragen wird als Tunable Consistency bezeichnet.
Durch eine entsprechende Wahl von Knoten kann Read your Writes Konsistenz [TvS07,
S. 424f] erreicht werden. Dies bedeutet, dass auf eine Schreiboperation folgende Leseoperation die geschriebenen Daten sehen muss, sofern sich beide Operationen auf die gleiche Zeile
beziehen. Alternativ können beim Lesen der Zeile auch neuere Daten zurückgeliefert werden,
falls diese zwischenzeitlich von einem anderen Prozess aktualisiert wurde.
12
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Konsistenz-Level
Bedeutung
ONE
Es werden die Zeilen von dem Knoten zurückgeliefert, welcher als erstes
antwortet.
aktor
Haben ( Replikationsf
+ 1) Knoten geantwortet, werden die Zeilen mit
2
dem neuesten Zeitstempel an den Client ausgeliefert.
Verhält sich wie QUORUM, jedoch wird mit dem Ausliefern der Zeilen gewartet, bis die Zeilen von allen Knoten vorliegen.
QUORUM
ALL
Tabelle 1: Konsistenz-Level von Cassandra beim Lesen von Daten
Konsistenz-Level
Bedeutung
ZERO
Die Schreiboperation wird asynchron bearbeitet. Auftretende Fehler werden ignoriert.
Die Schreiboperation muss auf mindestens einem Knoten durchgeführt
worden sein. Hinted Handoffs sind erlaubt (siehe Abschnitt 2.3.2).
Die Schreiboperation muss auf mindestens einem Knoten bestätigt worden
sein.
aktor
Es müssen mindestens ( Replikationsf
+ 1) Knoten die Schreiboperation
2
bestätigen.
Die Schreiboperation muss von allen Knoten bestätigt worden sein, welche
für die Daten zuständig sind.
ANY
ONE
QUORUM
ALL
Tabelle 2: Konsistenz-Level von Cassandra beim Schreiben von Daten
Erreicht wird dies, indem mindestens ein Knoten an beiden Operationen beteiligt ist.
Dieser Knoten erhält in der Schreiboperation die geänderten Daten. Da der Knoten auch
an der Leseoperation beteiligt ist, werden die Daten wieder an den Client ausgeliefert. Da
immer die Daten mit dem neusten Zeitstempel an den Client ausgeliefert werden, erhält der
Client mindestens den soeben geschriebenen Stand der Daten. Formal kann dies über die
Ungleichung W + R > N beschrieben werden. In der Ungleichung steht W für die Anzahl
der Knoten, auf denen die Daten geschrieben wurden, R für die Anzahl der Knoten von den
die Daten gelesen wurden und N steht für den Replikationsfaktor des Schlüsselraums.
2.3.2 Hinted Handoff
Bei einem Hinted Handoff handelt es sich um einen Hinweis für einen Knoten, welcher aktuell
nicht erreichbar ist. Hinted Handoffs werden genutzt, um Schreibzugriffe zwischenzuspeichern
und später auszuführen, sobald der Zielknoten wieder erreichbar ist. Im Konsistenz-Level ANY
reicht das Erstellen eines Hinted Handoffs schon aus, um dem Client das Schreiben der Daten
erfolgreich bestätigen zu können, obwohl bislang kein Replikat aktualisiert worden ist.
Beispiel: Der Client C möchte Daten auf dem Knoten A verändern. Der Knoten A ist
aktuell nicht erreichbar. Der Client hat sich mit Knoten B verbunden und sendet diesem die
Schreibanforderung. Als Konsistenz-Level gibt er ANY an. Dem Knoten B ist es nun erlaubt,
die Schreibanforderung zu speichern und dem Client das Schreiben der Daten zu bestätigen.
Der Knoten B wartet bis der Knoten A wieder erreichbar ist und sendet diesem daraufhin
die Schreibanforderung.
13
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Hinted Handoffs sorgen dafür, dass Schreibzugriffe auch durchgeführt werden können,
wenn Knoten nicht erreichbar sind. Zudem sorgen sie dafür, das Knoten schnell auf einen
aktuellen Stand gebracht werden, sobald sie wieder erreichbar sind.
2.3.3 Anti-Entropy und Read Repair
Das Konsistenzmodell von Cassandra erlaubt vorübergehende Inkonsistenzen (Eventual Consistency). Neben den Hinted-Handoffs wird mit zwei weiteren Techniken gearbeitet, um Inkonsistenzen zu beheben: (i) Anti-Entropy und (ii) Read Repair.
Read Repair: Unabhängig vom gewählten Konsistenz-Level, fordert der Koordinierende Knoten bei einem Lesezugriff die Daten von allen Knoten an. Der Koordinierende Knoten
überprüft, ob alle erhaltenen Zeilen den gleichen Zeitstempel aufweisen. Sofern auf
Knoten veraltete Zeilen vorliegen, initiiert der Koordinierende Knoten einen Schreibzugriff, um die Zeilen zu aktualisieren (Abbildung 5). Der Konsistenz-Level bei lesenden Zugriffen gibt demnach nur an, wann der Koordinierende Knoten dem Client eine
Antwort übermittelt, nicht aber, von wie vielen Knoten die Zeilen gelesen werden.
Anti-Entropy: Mittels Read Repair werden Inkonsistenzen für Daten, welche häufig gelesen werden, schnell korrigiert. Inkonsistenzen in Daten, welche nur selten gelesen werden, werden mittels Anti-Entropy korrigiert. Hierzu tauschen die Knoten untereinander
Prüfsummen über die gespeicherten Daten aus. Verwendet werden dazu Merkle Trees
[RCM82] um mit möglichst wenig Netzwerkverkehr große Mengen an Daten überprüfen
und gegebenenfalls korrigieren zu können.
1 0
F
1 0
A
F
A
>
00
Client
E
0
00
,1
1
<
<3,100020>
<3
,1
00
02
0>
D
00
10
,
<3
B
C
(a) Lesen einer Zeile von allen Knoten
>
20
E
B
D
C
(b) Aktualisieren einer veralteten Zeile
Abbildung 5: Anwendung von Read Repair: In Abbildung (a) fordert der Knoten E eine
Zeile von den Knoten A, B und C an. Dabei stellt er fest, dass der Knoten A
einen veralteten Stand besitzt. Knoten B und C antworten mit dem Wert 3, geschrieben bei Zeitstempel 100 020. Der Knoten A antwortet hingegen mit dem
Wert 1 geschrieben bei Zeitstempel 100 000. Der Knoten wird in Abbildung (b)
aktualisiert.
14
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2.3.4 Persistenz
Dieser Abschnitt beschreibt, wie Daten lokal auf einem Knoten persistent gespeichert werden. Schreibzugriffe werden zunächst in einem Commit Log festgehalten. Sobald der Schreibzugriff im Commit Log steht, bestätigt der Knoten diesen als erfolgreich. Auch bei einem
Programmfehler oder Neustart kann der Schreibzugriff aus dem Commit Log wiederhergestellt werden.
Nachdem der Schreibzugriff im Commit Log festgehalten ist, werden die geänderten Daten
im Arbeitsspeicher, in einer Memtable, abgelegt. Die Daten sind dort gemäß ihres Zeilenschlüssels sortiert. Überschreitet die Memtable eine gewisse Größe, werden diese Daten auf
die Festplatte ausgelagert (flush) und die Memtable geleert. Die auf die Festplatte ausgelagerten Daten werden sortiert als SSTable (Sorted String Table) gespeichert. Da die Daten
bereits sortiert im Speicher vorliegen, können diese unverändert auf die Festplatte herausgeschrieben werden.
Sobald die Daten erfolgreich auf die Festplatte geschrieben worden sind, wird das Commit
Log geleert. Die dort vermerkten Schreibzugriffe sind nun persistent in der SSTable abgelegt.
SSTables sind unveränderlich. Eine einmal geschriebene SSTable kann nach dem Schreiben
nicht mehr verändert werden [CAS13c].
Um Speicherplatz zu sparen und die Anzahl der zu verwaltenden SSTables zu reduzieren, werden in regelmäßigen Abständen Compactions durchgeführt. Dabei werden die bestehenden SSTables in eine neue SSTable überführt. Veraltete, durch einen Schreibzugriff
aktualisierte, Daten werden dabei nicht übernommen. Nachdem die neue SSTable aufgebaut
worden ist, werden die bestehenden SSTables gelöscht (siehe Abbildung 6). Das Konzept der
Memtable und SSTables stammt aus der Architektur der Software Google Bigtable [CDG+ 08].
Schreibzugriff
2. Vermerken des Schreibzugriffs in der Memtable
Memory
1. Vermerken des Schreibzugriffs im Commit-Log
Disk
SSTables
Memtable
Flush
SSTable
Commit-Log
Compact
Abbildung 6: Architektur von Cassandra: Zusammenhang zwischen Commit-Log, Memtable,
SSTables und Compaction.
Zugriffe auf die Festplatte sind im Vergleich zu Zugriffen auf den Arbeitsspeicher um einige
Zehnerpotenzen langsamer. Soll ein Knoten Daten lesen, prüft dieser zuerst, ob die Daten im
Arbeitsspeicher, in der Memtable, vorhanden sind. Liegen die Daten dort nicht vor, müssen
alle SSTables nach dem neuesten Stand dieser Daten durchsucht werden.
Jan Kristof Nidzwetzki, Thema 14: Cassandra
15
Um diese Suche mit wenig Zugriffen auf die Festplatte durchzuführen, werden Bloom
Filter eingesetzt [Blo70]. Ein Bloom Filter ist ein nichtdeterministischer Algorithmus, um
speichersparend festzustellen, ob ein Wert in einer Sammlung von Werten auftaucht. Der
Algorithmus kann zuverlässig entscheiden, ob ein Wert nicht Element einer Sammlung ist.
Nichtzutreffende positive Antworten (False Positives) sind jedoch möglich.
Jeder SSTable wird ein Bloom Filter zugeordnet. Mithilfe der Filter kann festgestellt werden, in welchen SSTables die benötigten Daten nicht stehen. Diese SSTables müssen nicht
von der Festplatte geladen werden. Hierdurch kann die Anzahl der Zugriffe auf die Festplatte
erheblich reduziert werden. Nur auf SSTables, in denen die Daten womöglich stehen, muss
zugegriffen werden.
2.4 Sicherheit
Im Standardfall erlaubt Cassandra den Zugriff von beliebigen Clients. Eine Anmeldung ist
für den Zugriff auf die Daten nicht erforderlich. Dieser Ansatz geht davon aus, dass sich alle
Knoten in einem geschützten Netzwerk befinden. Jeder der Zugriff auf dieses Netzwerk hat,
darf auch auf die Daten zugreifen.
Ist dies nicht gewünscht, lässt sich eine Authentifizierung einrichten. Jeder Client, welcher
auf die Daten zugreifen möchte, muss sich mit einem Benutzernamen und einem Passwort
anmelden. Cassandra bringt hierzu einen SimpleAuthenticator mit. Dieser gleicht die Anmeldedaten mit zwei Dateien ab. In der Datei access.properties sind die Zugriffsberechtigungen hinterlegt. In der Datei passwd.properties sind die Benutzernamen und Passwörter
hinterlegt.
Beispiel: In Listing 3 wird der Zugriff auf den Schlüsselraum Keyspace1 konfiguriert. Die
Benutzer jsmith und Elvis Presley dürfen auf diesen nur lesend zugreifen. Der Benutzer
dilbert darf zudem auch Daten verändern [DAT13b].
Listing 3: Konfiguration des SimpleAuthenticator - access.properties
1
2
Keyspace1 . < ro >= jsmith , Elvis Presley
Keyspace1 . < rw >= dilbert
Die Passwörter für die Anmeldung an Cassandra sind im im Listing 4 angegeben.
Listing 4: Konfiguration des SimpleAuthenticator - passwd.properties
1
2
3
jsmith = havebadpass
Elvis Presley = graceland4ever
dilbert = nomoovertime
Reichen die vom SimpleAuthenticator angebotenen Möglichkeiten nicht aus, so lassen
sich eigene Authenticator-Module schreiben. Diese können genutzt werden um beispielsweise
Benutzer gegen eine Datenbank oder gegen einen LDAP-Server zu authentifizieren. Die selbst
entwickelten Klassen müssen das Interface org.apache.cassandra.auth.IAuthenticator
implementieren.
16
Jan Kristof Nidzwetzki, Thema 14: Cassandra
2.5 Performance
Im Jahr 2010 veröffentlichten die Autoren Avinash Lakshman und Prashant Mailk das erste
Paper zu Cassandra [LM10]. In diesem Paper sind auch einige Erfahrungen mit der Performance von Cassandra bei Facebook enthalten. Dort wurde zu dieser Zeit eine Installation
von Cassandra auf 150 Systemen, verteilt über zwei Rechenzentren, mit 50+ TB an Daten
betrieben. Wie im ersten Abschnitt beschrieben, haben Benutzer auf der Webseite von Facebook die Möglichkeit, sich gegenseitig Nachrichten zu schicken. Diese Nachrichten wurden
in dieser Cassandra-Installation gespeichert.
In dem Paper sind die Laufzeiten zweier Anfragen veröffentlicht (siehe Tabelle 3). Beide
Anfragen greifen lesend auf die gespeicherten Daten zu. (i) Search Interactions lädt alle
Nachrichten, welche ein Benutzer von einem anderen Benutzer erhalten hat. (ii) Term Search
durchsucht alle Nachrichten eines Benutzers nach einem Schlüsselwort.
Latenz
Search Interactions
Term Search
Min
Median
Max
7.69 ms
15.69 ms
26.12 ms
7.78 ms
18.27 ms
44.41 ms
Tabelle 3: Latenz von Anfragen des Cassandra-Clusters bei Facebook (nach [LM10, S. 5])
Im Jahr 2012 veröffentlichten Forscher in ihrem Paper Solving big data challenges for
”
enterprise application performance management“ einen Vergleich der Performance von verschiedenen Datenbankmanagementsystemen [RGVS+ 12]. Für den Vergleich haben die Autoren verschiedene Szenarien mit unterschiedlichen Anfragen konzipiert. Die Ergebnisse von
zwei Szenarien werden im folgenden kurz vorgestellt: (i) Workload R und (ii) Workload RW.
180000
160000
140000
120000
100000
80000
60000
40000
20000
0
250000
Throughput (Ops/sec)
Throughput (Operations/sec)
Im ersten Szenario werden 95% lesende Operationen und 5% schreibende Operationen
durchgeführt (Abbildung 7(a)). Im zweiten Szenario erfolgen 50% schreibende und 50% lesende Operationen (Abbildung 7(b)). Zwei Punkte fallen bei diesem Vergleich auf: (i) Cassandra skaliert fast linear hinsichtlich der Knoten und der möglichen Operationen in beiden
Szenarien. (ii) Ab acht Knoten liegt die Performance von Cassandra über der Performance
der anderen Systeme.
2
Cassandra
HBase
4
6
8
Number of Nodes
Voldemort
VoltDB
(a) Workload R
10
Redis
MySQL
12
200000
150000
100000
50000
0
2
4
Cassandra
HBase
6
8
Number of Nodes
Voldemort
VoltDB
10
12
Redis
MySQL
(b) Workload RW
Abbildung 7: Vergleich der Laufzeiten verschiedener Datenbankmanagementsysteme (nach
[RGVS+ 12, S. 6f])
Jan Kristof Nidzwetzki, Thema 14: Cassandra
17
3 Erweiterungen von Cassandra
Cassandra wurde seit der ersten Veröffentlichung stark weiterentwickelt. Eine große Community von Entwicklern veröffentlicht alle paar Monate neue Versionen mit neuen Funktionen.
Zwei dieser neueren Funktionen werden in diesem Abschnitt aufgegriffen. Dabei handelt es
sich zum einen um die Abfragesprache CQL – Cassandra Query Language mit der, ähnlich
der Sprache SQL (Structured Query Language), Anfragen formuliert werden können. Zum
anderen wird die Anbindung von Hadoop kurz vorgestellt.
3.1 CQL – Cassandra Query Language
Cassandra bietet mehrere Möglichkeiten, auf Daten zuzugreifen. Neben einer Schnittstelle
für Client-Bibliotheken, mittels des Protokolls Thrift [ASK07], lassen sich die Daten auch
über ein Command Line Interface (CLI ) ansprechen. In der Version 0.8 von Cassandra wurde zudem die Cassandra Query Language eingeführt.
Die Sprache CQL ist von der Syntax stark an SQL angelehnt. Mittels CQL können Daten
gelesen, geändert oder gelöscht werden. Auch strukturelle Änderungen an Spaltenfamilien
oder an Schlüsselräumen sind möglich. Zwei Beispiele zum Zugriff mittels CLI und CQL
finden sich in den Listings 5 und 6.
Listing 5: Abfrage einer Zeile – Casandra CLI und CQL
1
2
# CLI
get People [ ’21 ’];
3
4
5
# CQL
SELECT * from People WHERE key = 21;
Listing 6: Anlegen einer Zeile – Casandra CLI und CQL
1
2
3
4
# CLI
set users [ ’ jsmith ’][ firstname ] = ’ John ’;
set users [ ’ jsmith ’][ lastname ] = ’ Smith ’;
set users [ ’ jsmith ’][ age ] = ’22 ’;
5
6
7
# CQL
INSERT INTO users ( KEY , firstname , lastname , age )
VALUES ( ’ jsmith ’ , ’ John ’ , ’ Smith ’ , ’22 ’) ;
CQL wurde mit dem Ziel entwickelt, eine stabile und einfache Schnittstelle zu Cassandra bereitzustellen. Zudem sollte die Sprache schnell erlernbar für Anwender mit SQLKenntnissen sein. Hierdurch wurde auch die Interaktion mit Anwendungen vereinfacht.
Für die Programmiersprache Java existiert durch das Projekt cassandra-jdbc [JDB13] ein
JDBC-Treiber2 . Mit diesem Treiber kann auf Cassandra mit den gleichen Methoden wie auf
ein RDBMS zugegriffen werden. Zudem wurde beim Design von CQL darauf Wert gelegt,
2
JDBC - Java Database Connectivity
18
Jan Kristof Nidzwetzki, Thema 14: Cassandra
dass nachfolgend keine großen Änderungen an der Syntax der Sprache mehr erfolgen sollen.
Dies soll in Zukunft dafür sorgen, dass Cassandra den Anwendungen eine stabile Schnittstelle
anbietet. Die Syntax der CLI hat in den letzten Versionen von Cassandra größere Änderungen erfahren. Programme welche per CLI auf Daten zugreifen, müssen daher fortlaufend
angepasst werden.
Neben den vielen Ähnlichkeiten besitzen SQL und CQL auch grundlegende Unterschiede.
So sind in CQL keine Joins implementiert. Zudem sind in CQL Schlüsselwörter für die
Tunable Consistency enthalten. In vielen Anfragen lassen sich Konsistenz-Level angeben.
Ein Beispiel hierfür ist in Listing 7 zu finden. In diesem Listing wird mit dem KonsistenzLevel QUORUM gearbeitet.
Listing 7: Anlegen einer Zeile unter Angabe eines Konsistenz-Levels
1
2
3
4
5
# CLI
consistencylevel as QUORUM ;
set users [ ’ jsmith ’][ firstname ] = ’ John ’;
set users [ ’ jsmith ’][ lastname ] = ’ Smith ’;
set users [ ’ jsmith ’][ age ] = ’22 ’;
6
7
8
# CQL
INSERT INTO users ( KEY , firstname , lastname , age )
VALUES ( ’ jsmith ’ , ’ John ’ , ’ Smith ’ , ’22 ’)
USING CONSISTENCY QUORUM ;
3.2 Integration von Hadoop
Cassandra Datenbanken sind oft sehr groß. Möchte man die dort gespeicherten Daten auswerten, so bietet es sich an, die Daten mittels Map-Reduce zu verarbeiten und auszuwerten
[DG04]. Ein sehr verbreitetes Open-Source Framework hierfür ist Hadoop.
Für die Ablage von großen Datenmengen bringt Hadoop ein eigenes Dateisystem mit:
HDFS. Dieses Dateisystem ist auf die redundante Speicherung großer Datenmengen spezialisiert. In Cassandra sind die Daten bereits redundant gespeichert. Nutzt man die klassischen
Techniken von Hadoop, so müssen die Daten aus Cassandra exportiert und in HDFS importiert werden, bevor mit diesen gearbeitet werden kann. Neben einer Verdopplung des
genutzten Speicherplatzes, benötigt das Kopieren der Daten ins HDFS einige Zeit. Dies
sorgt bei großen Datenmengen für deutliche Verzögerungen, bis die eigentliche Auswertung
der Daten beginnen kann. Ebenfalls müssen Programme entwickelt werden, welche das Kopieren der Daten übernehmen.
Ab Cassandra Version 0.6 kann Hadoop direkt auf die in Cassandra gespeicherten Daten zugriffen. Ein Export der Daten in HDFS entfällt. Ebenfalls ist es möglich, von Hadoop
berechnete Ergebnisse wieder an Cassandra zu übergeben. Konkret stehen hierzu die Klassen
org.apache.cassandra.hadoop.ColumnFamilyInputFormat und org.apache.cassandra.
hadoop.ColumnFamilyOutputFormat zur Verfügung. Diese können in eigene Hadoop Programme eingebunden werden. Ebenfalls existiert eine Erweiterung für das Pig Framework
[ORS+ 08], welche einen direkten Zugriff auf die in Cassandra gespeicherten Daten erlaubt.
Jan Kristof Nidzwetzki, Thema 14: Cassandra
19
4 Fazit
In dieser Arbeit wurden die Architektur, die Geschichte und einige neuere Funktionen der
Software Apache Cassandra vorgestellt. Es wurde auf Themen wie Redundanz, Konsistenz,
Peer to Peer und Replikation eingegangen. Ebenso wurde der im Jahr 2012 durchgeführte
Performance-Vergleich verschiedener Datenbankmanagementsysteme angesprochen. Dieser
bescheinigt Cassandra, in vielen Szenarien, eine höhere Performance als anderen DBMS.
Auch wenn Cassandra heute nicht mehr bei dem ursprünglichen Entwickler (Facebook)
eingesetzt wird, nutzen vielen Firmen diese Software für eigene Projekte. Es ist damit zu
rechnen, dass aufgrund der rasant wachsenden Datenmengen, auch in Zukunft die Nachfrage
nach Cassandra und anderen NoSQL-Datenbanken nicht nachlassen wird.
20
Jan Kristof Nidzwetzki, Thema 14: Cassandra
Literatur
[ASK07]
Aditya Agarwal, Mark Slee, and Marc Kwiatkowski. Thrift: Scalable crosslanguage services implementation. Technical report, Facebook, 4 2007.
[Blo70]
Burton H. Bloom. Space/time trade-offs in hash coding with allowable errors.
Commun. ACM, 13(7):422–426, 1970.
[CAS13a]
Apache cassandra users, 2013.
http://planetcassandra.org/Company/ViewCompany - Abgerufen am 25.04.2013.
[CAS13b]
Apache cassandra website, 2013. http://cassandra.apache.org/ - Abgerufen am
25.04.2013.
[CAS13c]
Apache
Cassandra
Wiki
MemtableSSTable,
2013.
http://wiki.apache.org/cassandra/MemtableSSTable
Abgerufen
am
15.04.2013.
[CDG+ 08]
Fay Chang, Jeffrey Dean, Sanjay Ghemawat, Wilson C. Hsieh, Deborah A. Wallach, Mike Burrows, Tushar Chandra, Andrew Fikes, and Robert E. Gruber.
Bigtable: A distributed storage system for structured data. ACM Trans. Comput. Syst., 26(2):4:1–4:26, June 2008.
[DAT13a]
Apache cassandra documentation der firma datastax inc.,
http://www.datastax.com/docs/1.2/index - Abgerufen am 25.04.2013.
[DAT13b]
Apache cassandra documentation der firma datastax inc. - authentication, 2013.
http://www.datastax.com/docs/1.2/configuration/authentication - Abgerufen
am 15.04.2013.
[DG04]
Jeffrey Dean and Sanjay Ghemawat. Mapreduce: Simplified data processing on
large clusters. In OSDI, pages 137–150, 2004.
2013.
[DGH+ 87] Alan J. Demers, Daniel H. Greene, Carl Hauser, Wes Irish, John Larson, Scott
Shenker, Howard E. Sturgis, Daniel C. Swinehart, and Douglas B. Terry. Epidemic algorithms for replicated database maintenance. In Fred B. Schneider,
editor, PODC, pages 1–12. ACM, 1987.
[DHJ+ 07]
Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter
Vosshall, and Werner Vogels. Dynamo: amazon’s highly available key-value
store. In Thomas C. Bressoud and M. Frans Kaashoek, editors, SOSP, pages
205–220. ACM, 2007.
[FAC13]
Facebook:
The
underlying
technology
of
messages,
2013.
https://www.facebook.com/notes/facebook-engineering/the-underlyingtechnology-of-messages/454991608919 - Abgerufen am 25.04.2013.
[HDYK04] Naohiro Hayashibara, Xavier Défago, Rami Yared, and Takuya Katayama. The
accrual failure detector. In SRDS, pages 66–78. IEEE Computer Society, 2004.
[Hew10]
E. Hewitt. Cassandra: The Definitive Guide. O’Reilly Media, 2010.
Jan Kristof Nidzwetzki, Thema 14: Cassandra
21
[JDB13]
Webseite vom cassandra jdbc-treiber, 2013. http://code.google.com/a/apacheextras.org/p/cassandra-jdbc/ - Abgerufen am 22.04.2013.
[KLL+ 97]
David Karger, Eric Lehman, Tom Leighton, Rina Panigrahy, Matthew Levine,
and Daniel Lewin. Consistent hashing and random trees: distributed caching
protocols for relieving hot spots on the world wide web. In Proceedings of the
twenty-ninth annual ACM symposium on Theory of computing, STOC ’97, pages
654–663, New York, NY, USA, 1997. ACM.
[LM09]
Avinash Lakshman and Prashant Malik. Cassandra: a structured storage system
on a p2p network. In Proceedings of the twenty-first annual symposium on
Parallelism in algorithms and architectures, SPAA ’09, pages 47–47, New York,
NY, USA, 2009. ACM.
[LM10]
Avinash Lakshman and Prashant Malik. Cassandra: a decentralized structured
storage system. SIGOPS Oper. Syst. Rev., 44(2):35–40, April 2010.
[ORS+ 08]
Christopher Olston, Benjamin Reed, Utkarsh Srivastava, Ravi Kumar, and Andrew Tomkins. Pig latin: a not-so-foreign language for data processing. In Proceedings of the 2008 ACM SIGMOD international conference on Management
of data, SIGMOD ’08, pages 1099–1110, New York, NY, USA, 2008. ACM.
[RCM82]
Mountain View CA Ralph C. Merkle. Method of providing digital signatures.
Patent, 01 1982. US 4309569.
[RGVS+ 12] Tilmann Rabl, Sergio Gómez-Villamor, Mohammad Sadoghi, Victor MuntésMulero, Hans-Arno Jacobsen, and Serge Mankovskii. Solving big data challenges for enterprise application performance management. Proc. VLDB Endow.,
5(12):1724–1735, August 2012.
[TvS07]
Andrew S. Tanenbaum and Maarten van Steen. Distributed systems - principles
and paradigms (2. ed.). Pearson Education, 2007.
[WIK13]
Apache
cassandra
in
der
wikipedia,
2013.
http://en.wikipedia.org/w/index.php?title=Apache Cassandra&oldid=545483041
- Abgerufen am 25.04.2013.
FernUniversität in Hagen
Seminar 01912
im Sommersemester 2013
Big Data Management
Thema 3.7
H-Store & VoltDB
Referentin: Anette Naffin-Rehorst
Seite 2/19
Inhaltsverzeichnis
1 Motivation, Problem....................................................................................................................3
2 H-Store ........................................................................................................................................3
2.1 Systemarchitektur ................................................................................................................4
2.2 Physikalischer Aufbau..........................................................................................................4
2.3 Besondere Eigenschaften von H-Store.................................................................................5
2.3.1 Partitionierung..............................................................................................................5
2.3.2 Shared-Nothing............................................................................................................5
2.3.3 OLTP............................................................................................................................6
2.3.4 Das ACID-Prinzip .......................................................................................................6
2.4 Implementierung 2-Node-Cluster mit Ubuntu ....................................................................7
2.4.1 Einrichten der Umgebung............................................................................................8
2.4.2 Erster Node im Cluster.................................................................................................9
2.4.3 Zweiter Node im Cluster..............................................................................................9
2.4.4 Installation eines Projektes...........................................................................................9
2.4.5 Katalog ansehen.........................................................................................................10
2.5 Beispielprojekte.................................................................................................................10
2.5.1 Demonstration eines Benchmarks..............................................................................11
2.6 Vergleich mit herkömmlichen DBMS................................................................................11
2.6.1 Leistungsfähigkeit......................................................................................................12
2.6.2 Fehlertoleranz und Ausfallsicherheit..........................................................................12
3 VoltDB ......................................................................................................................................13
3.1 Architektur und physikalischer Aufbau..............................................................................13
3.2 Technische Besonderheiten ...............................................................................................13
3.3 Beschaffungsmöglichkeiten...............................................................................................14
3.3.1 Ein Beispiel ...............................................................................................................15
3.3.2 Einsatzmöglichkeiten ................................................................................................16
4 H-Store und VoltDB im Vergleich.............................................................................................17
4.1 Technische Details..............................................................................................................17
4.2 Einsatzgebiete....................................................................................................................18
4.3 Beschaffung und Kosten....................................................................................................18
Seite 3/19
1 Motivation, Problem
Heute ist es selbstverständlich den Browser zu öffnen, im Internet oder Intranet nach
Informationen zu suchen und diese auch zügig zu finden. Wenn es mal nicht so schnell geht,
wird geflucht. Die Ursachen dafür sind sehr vielfältig. Woran liegt es, dass die Daten nicht
schnell genug verfügbar sind? Die Ursache dafür kann eine zu geringe Bandbreite des momentan
zur Verfügung stehenden Netzwerkes sein oder zu viele User sind zur selben Zeit im Netz
unterwegs und suchen auf ähnlichen Servern ebenfalls nach Informationen. Weitere Ursachen
können zu geringe Kapazität an Speicher und Prozessoren der Datenbankserver sein. Die
Systeme sind nicht vorbereitet auf den Ansturm der ständig wachsenden Datenmengen. Unsere
Anforderungen und die der Unternehmen an die Systeme zur Speicherung von Daten wachsen
täglich. Es werden nicht nur mehr Daten abverlangt, sondern auch Performance. Dadurch sind
die Entwickler von Datenbanksystemen gezwungen, vorhandene Datenbank-Systemarchitekturen und Speichersysteme ständig zu verbessern.
Diese Ausarbeitung soll zwei Datenbanksysteme vorstellen, die diese Anforderungen erfüllen
können. H-Store und VoltDB sollen hier vorgestellt werden.
H-Store in freies System, das ständig weiterentwickelt wird. Neue Projekte sind dadurch sehr
schnell verfügbar. Auch für VoltDB, das kommerzielle System, gibt es eine frei erhältliche
„Community-Version“.
2 H-Store
## --------------------------------------------------------------------------## _ _
_____ ___ ______
## | || |___/ __ |_ / _ \| _ \ __|
## | __ |___\__ \ | | (_) | / _|
## |_||_|
|___/ |_|\___/|_|_\___|
## Next Generation OLTP Database Research
## --------------------------------------------------------------------------H-Store ist eine hochgradig verteilte und hochperformante relationale Datenbank. Sie ist
optimiert für Online-Transaktionsverarbeitung (OLTP). Die Verarbeitung erfolgt in Clustern, die
aus mehreren Knoten bestehen. Jeder Knoten im Cluster benutzt für die Verarbeitung seinen
eigenen Hauptspeicher und ist somit unabhängig von den anderen.
H-Store ist frei verfügbar und experimentell. Somit hat man auch keinen Anspruch auf Support.
Das Datenbankdesign stammt weitgehend aus den 70er Jahren. Durch die rasante Verbilligung
der Hardware ist es heute in großem Umfang möglich, auch sehr große OLTP-Applikationen im
Hauptspeicher moderner Serverhardware im Shared-Nothing Cluster laufen zu lassen. OLTPTransaktionen benötigen nur wenige Mikrosekunden, um ausgeführt zu werden.
Das H-Store Projekt ist eine Verbindung zwischen MIT, „Brown University“, „Yale University“
und „HP Labs“ ([2] ).
Seite 4/19
2.1 Systemarchitektur
Jede Relation in der Datenbank besteht entweder aus einer
oder mehreren Partitionen. Jede Partition wird auf mehrere
Seiten repliziert und gehostet. Eine Partition wird auf
mehrere Seiten verteilt und bildet damit ein
Replikationsset. Dabei gehören alle Knoten im Cluster zu
einer administrativen Domäne, die sich gegenseitig
vertrauen ([4] - Seite 1).
Der grundsätzliche Ablauf der Applikations-Verarbeitung
ist in Abbildung 1 zu sehen. Die OLTP1-Applikation fordert
über das H-Store System die Ausführung einer in der
Datenbank gespeicherten Prozedur an. Eine Instanz der
aufgerufenen Prozedur führt dann eine Transaktion aus, die
wiederum SQL-Kommandos ausführt. Wird zur Laufzeit Abb. 1:
von der OLTP-Applikation eine neue Transaktion H-Store Systemarchitektur, Quelle: [4]
angefordert, führt diese der „Stored-Procedure-Handler“
aus. Fordert die Transaktion eine Eingabe, muss der Client sie vom System anfordern. Sind alle
Variablen bekannt, wird eine Strategie für einen optimierten Ausführungsplan entwickelt.
Der Transaktions-Manager ist verantwortlich für die Koordination der Zugriffe auf die anderen
Seiten, über die er mit Sockets kommuniziert. Alle bekannten Variablen, die für die Abfrage
benutzt werden, werden durch den Transaktionsmanager auf alle Seiten im Cluster verteilt.
2.2 Physikalischer Aufbau
Eine H-Store Instanz wird definiert als 1 Cluster mit 2 Knoten.
In Abbildung 2 sieht man einen Knoten als einen einzelnen physikalischen Computer mit einer
oder mehreren Seiten. Eine Seite ist eine operationale Einheit, ein eigenständiger Dienst, auf
dem Transaktionen ausgeführt werden. Ein System mit mehreren Prozessoren kann so verwendet
werden, dass genau eine Seite einen Core benutzt - einen Ausführungsstrang. Die von der
externen Applikationen angeforderten Transaktionen werden dann nur von diesem Core
durchgeführt. Jede Seite arbeitet völlig unabhängig von den anderen. Weder Daten noch Speicher
auf einem Knoten werden geteilt.
Abb. 2:
H-Store: Physikalischer Aufbau, Quelle: [3]
1 OLTP – Online Transaktions Processing Memory Databases
Seite 5/19
2.3 Besondere Eigenschaften von H-Store
2.3.1 Partitionierung
Die Partitionierung der Relationen erfolgt in H-Store immer horizontal. Wird eine Relation
horizontal geteilt, entstehen zwei oder mehr kleinere Teile der Relation. Diese Teile heißen
Partitionen. Werden die Partitionen auf mehrere Seiten und Knoten verteilt, ergibt sich eine
Lastverteilung der gesamten Relation. In der Abbildung 3 ist der Aufbau der Partitionierung zu
sehen. Für jede Partition gibt es einen Primär-Teil und einen Backup-Teil auf einem anderen
Knoten, der schreibgeschützt ist. Fakt ist, dass zwischen den Knoten Replikationsnachrichten
ausgetauscht werden, um Änderungen auf die Backup-Teile zu übertragen und um die Abfragen
der Clients, die auf eine Partition auf einem anderen Knoten zielen, zu beantworten. Daten
werden nur an den Knoten geschrieben, wo ihre Primär-Partitionen liegen. Das erhöht die
Performance enorm. So sind einige Applikationen „perfectly partitionable“ ([1] - Seite 1), d.h. sie
werden auf nur einer einzigen Partition ausgeführt.
Abb. 3:
H-Store: Partitionierung eines Clusters, Quelle:[1]
Auch die Prozessverantwortung wird über verschiedene Knoten verteilt.
2.3.2 Shared-Nothing
Jeder Knoten kann unabhängig und eigenständig seine Aufgaben mit seinem eigenen Prozessor
und den zugeordneten Speicherkomponenten, wie Festplatte und Hauptspeicher, erfüllen. Kein
bestimmter, einzelner Knoten ist für die Verbindung zu einer Datenbank notwendig.
Jeder Knoten kann auch allein arbeiten. Die Performance der ausgeführten Applikation ist
selbstverständlich abhängig von den vorhandenen Hardwareressourcen des Knotens, wie Größe
des Hauptspeichers und Zugriffsgeschwindigkeit des Storage-Systems.
Arbeiten mehrere Knoten im Cluster zusammen, erfolgt der Zugriff des oder der Clients über das
Netzwerk. Die mögliche Netzwerkbandbreite und die Latenzzeit, mit der von den Clients auf die
H-Store-Nodes zugegriffen werden kann, ist ausschlaggebend für die Performance der
Applikation.
Je mehr Knoten gerade mit einer Applikation arbeiten, desto mehr Ressourcen stehen insgesamt
Seite 6/19
zur Verfügung. Die Performance der Applikation steigt und damit auch die Akzeptanz beim
Anwender.
2.3.3 OLTP
OLTP - Online Transaktion Processing wird auch Echtzeit-Transaktionsverarbeitung genannt.
Der Name lässt erahnen, was sich dahinter verbirgt. Es handelt sich um ein Paradigma, das bei
Datenbanksystemen, eine Transaktion sofort und jetzt, ohne Zeitverzögerung stattfinden lässt.
Viele Geschäftsprozesse können gleichzeitig stattfinden. Hierbei ist es wichtig, bei parallelen
Anfragen und Änderungen, die Transaktionsverarbeitung möglichst zügig zu gestalten. Das
bedeutet, eine geringe Antwortzeit auf eine Anfrage. OLTP-Applikationen sind nicht festplattenbasiert, sondern laufen vollständig im Speicher.
Sollen möglichst viele Transaktionen pro Zeiteinheit abgearbeitet werden, sind dafür
Datenbankserver erforderlich, die die entsprechenden Ressourcen, wie Hauptspeicher, schnelle
Festplatten sowie eine performante LAN- bzw. -WAN-Anbindung besitzen.
Bei einem Versandhandel werden z.B. alle Vorgänge eines Arbeitstages in einem EDV-System
gespeichert. Hier ist es wichtig, dass Lagerbestände stets aktuell abgefragt zu werden. Weiterhin
wollen Kunden den aktuellen Zustand ihrer Bestellvorgänge erfahren. Von den Informationen
über Geldeingänge, Kontostände und Buchungsvorgänge sind die Unternehmen abhängig. Sie
bilden die Grundlage für die Existenz eines Unternehmens. Bei einem Datenverlust muss es
möglich sein, die Daten wieder herzustellen.
OLTP – ist nicht zu verwechseln mit OLAP (Online Analytical Processing).
2.3.4 Das ACID-Prinzip
Das Akronym ACID ist eine Charakterisierung von Transaktionen und wurde 1983 von den
Informatikern Theo Härder und Andreas Reuter geprägt. Erstmals erwähnt wurde ACID im
„Paper Principles of Transaction-Oriented Database Recovery“.
ACID – Atomicity, Consistency, Isolation und Durability
oder
AKID – Atomarität, Konsistenz, Isoliertheit und Dauerhaftigkeit
Erklärungen der Begriffe:
Atomarität - die „Alles oder Nichts- Eigenschaft“
Eine Sequenz von Datenoperationen wird entweder ganz ausgeführt oder nicht ausgeführt.
Konsistenz: - Eine Sequenz von Daten-Operationen hinterlässt nach Beendigung einen
Seite 7/19
konsistenten Datenzustand, falls die Datenbank davor auch konsistent war. Das wird durch
Normalisierung der Datenstruktur sowie durch die Definition von Fremd- und Primärschlüsseln
erreicht. Unter Normalisierung eines relationalen Datenschemas (Tabellenstruktur) versteht man
die Aufteilung von Attributen (Tabellenspalten) in mehrere Relationen (Tabellen) gemäß den
Normalisierungsregeln, so dass eine Form entsteht, die keine vermeidbaren Redundanzen mehr
enthält. Dieses Kriterium bezieht sich auf inhaltliche und referenzielle Integrität, d.h. Datensätze
dürfen nur über Fremdschlüssel auf ihre Datensätze verweisen.
Isolation: - Nebenläufig ausgeführte Datenoperationen sollen sich nicht beeinflussen. Eine
laufende Transaktion darf nicht durch eine weitere parallel laufende Transaktion in einen
undefinierten Zustand gebracht werden, weil die Daten, auf die die Transaktion zugreift,
verfälscht werden.
Dauerhaftigkeit: - Die Daten werden nach Beendigung der Transaktion garantiert dauerhaft in
der Datenbank gespeichert. Dabei bedeutet dauerhaft: nach einem Systemausfall (Serverabsturz)
müssen die Daten wieder zur Verfügung stehen.
2.4 Implementierung 2-Node-Cluster mit Ubuntu
Als Dokumentationsquellen werden die Internetseiten der Brown University [5] verwendet.
Systemvoraussetzungen:
H-Store läuft nur auf 64-bit linux-basierenden Betriebssystemen mit Dual-Core Prozessoren und
mindestens 1,6 GHz.
H-Store ist getestet auf folgenden Plattformen:
- Ubuntu Linux 9.10+ (64-bit)
- Red-Hat Enterprise Linux 5.5 (64-bit)
- Mac OS X 10.6+ (64-bit)
Benötigte Software:
gcc/g++ +4.3, JDK +1.6, Python +2.7, Ant +1.7, Valgrind +3.5, ntp und SSH-Server
H-Store ist vom Grundsatz java-basiert. Zum Kompilieren wird der Java-Compiler Ant benutzt.
#sudo apt-get update
#sudo apt-get --yes install subversion gcc g++ openjdk-7-jdk valgrind ant
#sudo apt-get --yes install openssh-server ntp
Abb. 4: H-Store: Installation benötigter Software
C-Compiler und Python Umgebung gehören mit zum Umfang, um die dazugehörigen
Programme zu kompilieren bzw. auszuführen (Abbildung 4).
Es ist zwingend notwendig, dass alle Knoten im Netzwerk dieselbe Zeit haben. Die empfohlene
Methode ist, eine einzige Zeitquelle für alle Knoten zu benutzen. Der SSH-Server dient zum
gesicherten passwortlosen Login zwischen den Knoten eines Clusters.
Mögliche Installationsquellen:
Seite 8/19
Als Installationsquelle kann das Paket hstore-vldb2007.tgz oder ein GitHub1 verwendet werden.
Die Installation soll auf jedem Knoten im $HOME-Verzeichnis des Benutzers hstore erzeugt
werden. Es entsteht das Verzeichnis $HOME/h-store. Hier werden alle Quellen mit Beispielprojekten heruntergeladen. Das Verzeichnis $HOME/h-store bezeichnen wir als $HSTORE_HOME.
Mit dem Befehl „ant build“ werden alle Quellen über den gesamten Pfad übersetzt. Aus den
Quellen in $H-STORE_HOME/src werden Objekte in $H-STORE_HOME/obj/releases. Aus
Dateien vom Typ .java werden Java-Klassen vom Typ .class (Abbildung 5).
#cd $HOME
#git clone git://github.com/apavlo/h-store.git
#cd $HSTORE_HOME
# ant build
Abb. 5: H-Store: Quellen übersetzen
2.4.1 Einrichten der Umgebung
SSH-Kommunikation:
Für die Kommunikation zwischen den Cluster-Knoten ist das passwortlose SSH-Login zwischen
den Knoten erforderlich. Die SSH-Keys werden im Home-Verzeichnis des Benutzers gespeichert, der die H-Store Applikation ausführt.
# cd $HOME
# ssh-keygen -t dsa # (hier kein Password eingeben
# cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
Abb. 6: H-Store: ssh-keys erzeugen
Es wird ein Schlüsselpaar aus privatem und öffentlichem Schlüssel erzeugt. Der öffentliche
Schlüssel wird dann an die Datei ./ssh/authorized_keys angehängt. Dieser Vorgang muss auf
jedem Knoten ausgeführt werden und die Public-Keys aller Knoten auf jeden Cluster-Knoten
kopiert werden.
1 ein webbasierter Hosting-Dienst für Software-Entwicklungsprojekte
Seite 9/19
2.4.2 Erster Node im Cluster
Im GitHub befinden sich eine Reihe fertiger Beispielprojekte, die benutzt werden können. Ein
einziges Projekt besteht aus einer kompletten Applikation mit einem Datenbankschema und den
gespeicherten Prozeduren. Bevor eine Applikation aufgerufen werden kann, muss das Projekt
präpariert werden. Es entsteht das $PROJECT.jar-file im Verzeichnis $HOME/h-store
(Abbildung 7). Das Projekt besteht zunächst immer aus 1 Knoten, 2 Seiten und 2 Partitionen.
Cd $HOME/h-store
ant hstore-prepare -Dproject=$PROJECT
Abb. 7: H-Store: Projekt präparieren
2.4.3 Zweiter Node im Cluster
Ein Cluster mit einem Knoten ist in der Praxis nicht sinnvoll,
vielmehr soll die komplette Applikation über das Netzwerk
verteilt und auf mehreren Knoten ausgeführt werden. Dazu muss
das Projekt neu präpariert werden. In einer Text-Datei werden die
Information über die Clusterknoten und Partitionen abgelegt. In
diesem Beispiel hat der Knoten hstore1 eine Seite und zwei
Partitionen, der Knoten hstore2 hat mit Seite 1 drei Partitionen
und mit Seite 2 zwei Partitionen. In Abbildung 8 befindet sich ein
Beispiel für die Datei cluster.txt.
2.4.4 Installation eines Projektes
Cluster.txt
(Node:site:partition
hstore1:0:0
hstore1:0:1
hstore2:1:2
hstore2:1:3
hstore2:2:4
hstore2:1:5
hstore2:2:6
Abb. 8: H-Store:
Clusterkonfiguration
Sind die Voraussetzungen aus Punkt 2.4 erfüllt und die SSH-Kommunikation zwischen den
Knoten eingerichtet, kann das Projekt erstellt werden (Abbildung 9).
Cd $HOME/h-store
ant hstore-prepare -Dproject=wikipedia -Dhosts=cluster.txt
Abb. 9: H-Store: Projekt bereitstellen
Ein Beispielprojekt „Wikipedia“ wird präpariert für 2 Knoten: hstore1 hat Seite 0 mit Partition 0
und 1, hstore2 hat Seite 1 mit den Partitionen 2, 3, 5 und Seite 2 mit den Partitionen 4 und 6. Es
entsteht wikipedia.jar mit der gewünschten Clusterkonfiguration bezüglich der Knoten und der
Partitionierung.
Seite 10/19
2.4.5 Katalog ansehen
Der Katalog zeigt die Struktur eines Projektes.
Cd $HOME/h-store
ant catalog-viewer -Dproject=tpcc
Abb. 10: H-Store: Katalog ansehen
Mit dem Katalog-Viewer in Abbildung 11, kann man die gesamte Struktur für ein Projekt
ansehen. Sichtbar sind alle Tabellen, gespeicherten Prozeduren und die Cluster-Aufteilung mit
Knoten, Seiten und Partitionen.
Abb. 11: H-Store: der Katalog-Viewer zeigt die Projektstruktur
2.5 Beispielprojekte
Folgende Beispielprojekte wurden erstellt, um die Performance von OLTP-Applikation zu
messen [5].
TPC-C (tpcc)
Quellcode:
/src/benchmarks/org/voltdb/benchmark/tpcc
Anzahl Tabellen: 9, Anzahl Prozeduren: 5
Der Benchmark TPC-C ist ein geläufiger Industriestandard zur Untersuchung der Performance
eines OLTP-Systems. Hier wird ein Auftragssystem mit einem Zentrallager und deren
Applikationen simuliert.
Bingo Benchmark (bingo)
Quellcode:
/src/benchmarks/org/voltdb/benchmark/bingo
Anzahl Tabellen: 3, Anzahl Prozeduren: 4
Ein einfacher Benchmark, der eine Bingo-Halle imitiert. Das Original ist von VoltDB.
Seite 11/19
2.5.1 Demonstration eines Benchmarks
Zunächst muss der präparierte Benchmark gestartet werden. In unserem Fall wurde im $HSTORE_HOME-Verzeichnis eine Datei wikipedia.jar erzeugt. Mit dieser Datei wird der
Wikipedia-Benchmark als Server unter dem Port 21213 gestartet (Abbildung 12) und ebenfalls
cd $HOME/h-store
ant hstore-benchmark -Dproject=wikipedia
Abb. 12: H-Store: Start eines Projektes
auch auf allen anderen Cluster-Knoten. Danach kann man sich mit dem Client mit einem der
Knoten verbinden, egal mit welchem.
Der Aufruf eines Projektes kann von jedem beteiligten Knoten aus mit dem Projektnamen erfolgen (Abbildung 13), die Ausgabe ist zu sehen in Abbildung 14.
cd $HOME/h-store
./hstore wikipedia
Abb. 13: H-Store: Projekt aufrufen
In dem Eingabefenster können dann
SQL-Befehle abgesetzt werden, wie:
hstore> SELECT Count(*) from table;
Abb. 14: H-Store: Aufruf des Projektes
2.6 Vergleich mit herkömmlichen DBMS2
Die ersten DBMS gab es ab 1970. DBMS wurden oft auf einzelnen Servern in einer SingleInstanz betrieben. Was für eine Katastrophe, wenn der eine Server ausfiel und dieser dann wieder
hergestellt werden musste. Zunächst musste die Hardware beschafft werden, anschließend
kamen Backup-Strategien zum Tragen. Die Wiederherstellung war oft langwierig. Wie sieht ein
herkömmliches DBMS heute aus? Mehrprozessorsysteme mit viel Speicher arbeiten oft allein,
durch leistungsfähigere Hardware wird versucht, die Performance der Applikationen zu steigern.
H-Store gelingt es durch die Isolierung der Verarbeitungsstränge, Partitionierung und der
kompletten Verarbeitung aller Transaktionen im Hauptspeicher, die Anzahl der Transaktionen pro
Zeiteinheit enorm zu erhöhen. Eine schwierige Transaktion im TPC-C (Data Warehouse
Applikation) mit etwa 200 Datensätzen kann in weniger als einer Millisekunde gelesen werden
[9]. Bei anderen Datenbankherstellern, wie z.B. Oracle3, kann Partitionierung nicht nachträglich
installiert werden. Hochverfügbarkeit, z.B. das Einrichten von Cluster-Knoten muss von Anfang
an implementiert werden.
2 DBMS – Datenbank-Management-System
3 Oracle Corporation
Seite 12/19
2.6.1 Leistungsfähigkeit
H-Store ist eine hochperformantes, hochverfügbares OLTP-Datenbanksystem. Die Architektur
von H-Store zeigt, dass von Anfang an mit Prozessorcores und Partitionierungen gearbeitet wird.
Die Clustertechnologie ist vom Deployment eines Projektes an vorgesehen.
Durch das Hardware-Design eines Knotens wird die Prozessorleitung optimal ausgenutzt. Die
Transaktion verwendet auf einer Seite ihren eigenen Ausführungsstrang - einen Core des
Prozessors. Transaktionen werden vollständig bis zu Ende auf einer Seite des Knotens
ausgeführt, deshalb kann es dabei nicht zu Behinderungen kommen. Die Anwendungen laufen
vollständig im Speicher4. H-Store benutzt vorrangig „stored procedures“, die direkt in der
Datenbank gespeichert sind. Über mehrere verteilte Knoten kann die Last der Applikation
aufgeteilt werden.
2.6.2 Fehlertoleranz und Ausfallsicherheit
Das System kann recht einfach auf mehrere Knoten verteilt werden, die in einem SharedNothing-Cluster zusammenarbeiten. Durch Partitionierung der Daten und auch der Verantwortlichkeiten erreicht man eine weitere Steigerung der Performance.
Durch die jeweilige Backup-Partition erreicht man, trotz Ausfall eines Knotens, eine vollständige
Verfügbarkeit der Datenbank.
Fällt ein Knoten aus, übernehmen die anderen Knoten seine Aufgaben.
4 new anti-caching architecture in H-Store
Seite 13/19
3 VoltDB
_ __
____ ____ ____
| | / / ___ / / /_ / _ \/ __ )
| | / / __ \/ / __/ / / / __ |
| |/ / /_ / / / / _/ /_/ / / _ / /
|___/\____/_ /\__ /___ /____ /
-------------------------------------------Best-in-class, easily scalable throughput
3.1 Architektur und physikalischer Aufbau
VoltDB ist eine Open-Source Speicher-Datenbank, die auf der Basis von H-Store designet
wurde. Die Datenbank ist horizontal partitioniert und ist im Hauptspeicher der Knoten im Cluster
geladen ([6] - Seite19).
Um hohe Verfügbarkeit zu gewährleisten, werden die Partitionen auf mehrere Knoten im Cluster
repliziert. Jeder Knoten im Cluster hat pro CPU-Core eine Ausführungsseite. Jeder ClusterKnoten hat eine Initiator-Seite, welche Transaktionsinformationen zu den zugehörigen PartitionsReplikas bringt. Bei geschickter Replikationsaufteilung können die meisten Transaktionen
„single-sided“ ausgeführt werden. Man nimmt nur eine kleine Anzahl von Datensätzen.
Transaktionen können einfach hintereinander ohne konkurrierende Abläufe auf einer Partition
ausgeführt werden. Dadurch erreicht man einen sehr hohen Durchsatz von Transaktionen,
nämlich über 4K Transaktionen pro Sekunde und pro Core, dokumentiert in [6]. Bei Ausfall eines
Knotens steht die Datenbank durch die Partitionsreplikationen zur Verfügung.
3.2 Technische Besonderheiten
Checkpoints: (automatische Snapshots) [10]
Um jedem Ausfall begegnen zu können, schreibt jeder VoltDB-Knoten in bestimmten Abständen
einen Checkpoint auf die lokale Festplatte. Dieser Checkpoint speichert den exakten Status der
lokalen Datenbank und aller Transaktionen, die zu einem bestimmten Zeitpunkt abgeschlossen
wurden. Auf der lokalen Festplatte gibt es also immer einen konsistenten Zustand aller
Transaktionen, wenn auch nicht ganz aktuell. Zusätzlich wird das „Command-Logging-Tool“
benutzt, das auf jedem Knoten ausgeführt wird. Gespeichert wird eine Liste von aufgerufenen
„stored procedures“. Dieses Log enthält: Transaktions-Id, Parameter und Zeitstempel. Um die
Datenbank nach einem Ausfall auf den aktuellen Stand zurückzuführen, wird die Datenbank bis
zum letzte Checkpoint wiederhergestellt und danach die Kommandos aus dem Kommando-Log
nachgezogen, bis zu dem Zeitpunkt, kurz vor dem Ausfall der Datenbank.
Heart Beating: [10]
Die Datenbankserver verwenden einen „heartbeat“, um zu ermitteln, ob andere Knoten im
Cluster verfügbar sind. Wird der „heartbeat“ eines Knotens innerhalb einer bestimmten Zeit nicht
empfangen, wird angenommen, das der Knoten nicht verfügbar ist. Der Cluster wird dann
umkonfiguriert. Für die meisten Situationen ist der Standard-Wert von 10 Sekunden ausreichend.
VoltDB verwendet temporäre Tabellen, um Daten zwischenzuspeichern, während der
Verarbeitung von Transaktionen. Der Standardwert für die Temp-Tabelle beträgt 100 Megabyte.
Seite 14/19
K-Safety:
K-Safety ist ein Mechanismus (Abbildung 15), um die Ausfallsicherheit einer Datenbank zu
erhöhen. Standardmäßig steht der K-Faktor bei 0. Der Wert 1 bedeutet, dass dieses Future
Abb. 15:
VoltDB: K-Safety in Aktion, Quelle: [7]
aktiviert ist und für jede Partition gibt es zwei Kopien im Cluster. Bei Ausfall eines Knotens,
wird unter-brechungsfrei auf eine Kopie umgeschaltet. Im Fall von VoltDB sind die doppelten
Partitionen voll funktionsfähige Mitglieder des Clusters, einschließlich aller Lese- und
Schreiboperationen. Es bestehen keine Master-Slave-Beziehungen.
3.3 Beschaffungsmöglichkeiten
Auch VoltDB hat eine freie Version. Um die Community-Edition zu erhalten, muss man sich auf
der Webseite http://voltdb.com/community/downloads.php registrieren, danach erhält man den
Download-Link.
Downloads gibt es für Linux, Mac, Debian, EC2, als RPM und sogar für VMware.
Auch ein GitHub ist verfügbar. Diese darf aber nicht kommerziell genutzt werden!
Seite 15/19
3.3.1 Ein Beispiel
Die Erstellung des Projektes ist angelehnt an die VoltDB-Dokumentation [11].
Systemvoraussetzungen:
Ein 64-bit Linux-basiertes Betriebssystem:
CentOS Version 5.8+ und 6.3+, Ubuntu Versionen 10.4 und 12.4, Macintosh OSX 10.6+ .
Ein Dual-Core 2 x86_64 Prozessor mit 64 bit, und 1.6 Ghz.
Die Erforderliche Software:
Java, Sun JDK 6 update 21+, Python 2.4+, ant 1.7+ und ntp.
$cd $HOME
$git clone https://github.com/VoltDB/voltdb.git
$export PATH="$PATH:$HOME/voltdb/bin"
(in file: $HOME/.bashrc eintragen)
Abb. 16: VoltDB: Einrichten der Umgebung
Installiert man VoltDB (Abbildung 16), so entsteht wie bei H-Store unter dem aktuellen
Verzeichnis der Ordner voltdb – das $VOLTDB-HOME-Verzeichnis.
Ein ganz einfaches Beispiel ist die Erstellen des Projektes „Winzling“.
Zunächst wird der Projektordner erstellt. Das DDL-Schema in Abbildung 17 mit der Angabe der
Partitionierung wird in die Datei winzling.sql geschrieben.
CREATE TABLE winzling (
PROJEKT VARCHAR(15),
NAME VARCHAR(15),
DIALECT VARCHAR(15) NOT NULL,
PRIMARY KEY (DIALECT)
);
PARTITION TABLE WINZLING ON COLUMN DIALECT;
Abb. 17: VoltDB:Inhalt von winzling.sql
Jetzt kann die Anwendung kompiliert und danach getestet werden (Abbildung 18).
$ cd $VOLTDB_HOME/src/benchmarks/com/example/benchmark
$ mkdir winzling
$ cd winzling
$ vi winzling.sql
$ voltdb compile -o winzling.jar winzling.sql
$ voltdb create catalog winzling.jar
Abb. 18: VoltDB: Erstellen des Projektes „Winzling“
Seite 16/19
Danach kann mit dem Befehl „sqlcmd“ Verbindung zur gestarteten VoltDB-Applikation
„Winzling“ aufgenommen werden.
$ sqlcmd
> INSERT INTO WINZLING VALUES( 'Project','Tiny', 'English');
> SELECT * FROM WINZLING WHERE DIALECT='English';
PROJECT NAME DIALECT
------ ------ -------Project Tiny English
(1 row(s) affected)
3> EXIT
Der Befehl „voltadmin shutdown“ beendet die VoltDB-Applikation.
Die Skalierung des Clusters erfolgt in der Datei deployment.xml. Ohne Konfiguration ist die
Applikation nur für einen Knoten erstellt, in diesem Fall „localhost“. Das ist gut zum Testen,
aber nicht für den produktiven Einsatz geeignet. Um die Performance zu steigern, sollte ein
Cluster mit mehreren Knoten zum Einsatz kommen.
In dem Beispiel aus Abbildung 19 sind es 3 Knoten, 2 Seiten pro Knoten und ein kfactor von 0,
d.h. kein K-Safety.
<deployment>
<cluster hostcount="3" sitesperhost="2" kfactor="0"/>
</cluster>
<httpd enabled="true">
<jsonapi enabled="true" />
</httpd>
</deployment>
Abb. 19: VoltDB: deployment.xml
In [8] findet man weitere Parameter für die Steigerung der Performance der Applikationen.
VoltDB-Beispiele:
Sehr gut geeignet zum Experimentieren, sind die Beispiele der VoltDB-Distribution. Sie werden
auch im GitHub mitgeliefert. Sie befinden sich im VoltDB-Ordner unter examples.
Voltcache — demonstriert, wie VoltDB einen memory cache verwendet
Voltkv — erstellt einen Key-Value Store unter VoltDB
Voter — simuliert eine Telefon-Voting Applikation, ähnlich wie im TV
Seite 17/19
3.3.2 Einsatzmöglichkeiten
Überall dort, wo sehr große Datenmengen anfallen und Daten
sofort online und aktuell verfügbar sein müssen, ist VoltDB
gefragt. Ein „Up-to-Date“ von Produkten und Preisen ist auch
das Kernziel von Shopzilla.
Shopzilla hat einen Kundenstamm von mehr als 40 Millionen
Kunden weltweit. Shopzilla ist eines der führenden OnlineKaufhäuser [12]. Monatlich werden bis zu 100 Millionen
Produkte von Zehntausenden Einzelhändlern angeboten.
Shopzilla wechselte seine Inventar-Plattform von einer
Abb. 20: VoltDB: Shopzilla
traditionellen relationalen Datenbank 2013 zu VoltDB ( [12],
&VoltDB
Abbildung 20).
4 H-Store und VoltDB im Vergleich
H-Store ist eine Entwicklungsplattform. Die jeweils aktuellste Distribution erhält man auf der
Webseite http://hstore.cs.brown.edu/downloads/. Es gibt zwar aktuelle Dokumentation, vieles
bleibt jedoch undokumentiert, da hilft nur ausprobieren. Hier benötigt man in der Regel LinuxKenntnisse. Aber letztendlich wird man in der H-Store-Dokumentation immer auf das
kommerzielle Produkt „VoltDB“ verwiesen.
Die Dokumentation von VoltDB ist exzellent. Mithilfe dieser Anleitung [11] kann man Schritt für
Abb. 21: VoltDB: Enterprise Manager: rechts: "real-time
statistics" der aktuellen Datenbank, 4 Grafen zeigen PerformanzDaten, Quelle: [10]
Schritt eine VoltDB-Applikation erstellen. In der Enterprise-Version gibt es grafische Tools, die
den Administrationsaufwand enorm verringern, z.B. den Enterprise Manager in Abbildung 21.
Nicht nur die Clusterkonfiguration, sondern auch die Datenbankaktivitäten, sowie der
Clusterstatus stehen auf einen Blick zur Verfügung.
Seite 18/19
4.1 Technische Details
Obwohl VoltDB aus H-Store entstanden ist, ergeben sich doch gravierende Unterschiede. Im
Gegensatz zu H-Store erfolgt die Steuerung und Skalierung des gesamten Projektes in VoltDB
durch die Datei deployment.xml.
Auch der K-Faktor und die Snapshot Pfade werden hier eingestellt. Bei H-Store muss vor Beginn
des Projektaufrufs für alle Knoten im Cluster das passwortlose SSH-Login realisiert werden.
VoltDB unterscheidet zwischen verschiedene Editionen, z.B. Enterprise-Edition und
Community-Edition. VoltDB benutzt im Gegensatz zu H-Store Checkpoints, Procedure-Logging
und K-Safety. Damit ist die Skalierbarkeit und Ausfallsicherheit größer als bei H-Store.
4.2 Einsatzgebiete
H-Store ist Freeware und kann von jedem genutzt werden. Auf der Webseite [14] wird auf
VoltDB als kommerzielle Version von H-Store verwiesen. So betreut die Brown University und
Massachusetts Institute of Technology mehrere H-Store Projekte, wie z.B. „Automatic Database
Partitioning“ und „Predictive Modeling of OLTP Applications“. Mit H-Store kann jeder seine
Anwendungen selbst entwickeln und Benchmarks testen. Für den produktiven Einsatz wird
jedoch immer auf VoltDB verwiesen.
4.3 Beschaffung und Kosten
H-Store und VoltDB sind als freie Versionen (.tgz) und auch als GitHub erhältlich. Für den
kommerziellen Einsatz von VoltDB muss man je nachdem, mit wie vielen Knoten man den
Cluster betreiben und mit welcher Transaktionsgeschwindigkeit man arbeiten möchte,
Lizenzkosten bezahlen. Es handelt sich hierbei um eine jährliche Subskription. Ein Unternehmen
muss für einen Cluster mit 4 Knoten für eine VoltDB-Lizenz ab 15.000 US Dollar pro Jahr
bezahlen [13].
Wer eine hohe Verfügbarkeit der IT-Systeme haben möchte, muss dafür die entsprechende
Menge Geld bezahlen. Darüber muss jedes Unternehmen selbst entscheiden. Ein Ausfall von nur
einer Stunde, kann für ein Unternehmen gravierende Folgen haben [15].
Seite 19/19
Literatur:
[1] Evan P. C. Jones, MIT CSAIL, Cambridge, MA, USA/ Daniel J. Abadi, Yale University, New Haven, CT, USA/
Samuel Madden, MIT CSAIL, Cambridge, MA, USA, 2010: Low Overhead Concurrency Control for
Partitioned Main Memory Databases, Seite 3
[2] Brown University, 2013: http://hstore.cs.brown.edu, Massachusetts Institute of Technology, and Yale University
[3] Andrew Pavlo/ Jones Stanley Zdonik, Brown University,
Evan P.C. MIT CSAIL, 2012: On Predictive Modeling for Optimizing Transaction - Execution in Parallel OLTP
Systems, Seite 84
[4] Robert Kallman/ Hideaki Kimura/ Jonathan Natkins/ Andrew Pavlo/ Alexander Rasin/ Stanley Zdonik, Brown
University, Evan P. C. Jones/ Samuel Madden/ Michael Stonebraker/ Yang Zhang, Massachusetts Institute of
Technology, John Hugg/ Vertica Inc., jDaniel/ J. Abadi, Yale University, 2008: H-Store: A High-Performance,
Distributed Main Memory Transaction Processing System, Seiten 1-2
[5] Andy Pavlo, Brown University, 2013: „Documentation > Deployment > Supported Benchmarks“,
Content © 2013 Brown University, Massachusetts Institute of Technology, and Yale University,
http://hstore.cs.brown.edu/documentation/deployment/benchmarks , May 23rd, 2013
[6] Nirmesh Malvija, 2012: Recovery Algorithmus for IN-MEMORY-OLTP-Databases by Nirmesh Malvija, B.Tech
Computer Science and Engineering, Indian Institute of Technology Kanpur, Seite 20
[7] VoltDB, Inc., 2013: Using VoltDB V3.1, Seite 63
[8] VoltDB, Inc., 2013: VoltDB, Planning Guide, Version 3, Seiten 15-20
[9] Michael Stonebraker/ Samuel Madden/ Daniel J. Abadi/Stavros Harizopoulos, MIT CSAIL/ Nabil Hachem,
Avant Garde Consulting, LLC / Pat Helland, Microsoft Corporation, September 23-28, 2007: VLDB ’07,
Vienna, Austria, The End of an Architectural Era, (It’s Time for a Complete Rewrite), Seiten 1150-1153
[10] VoltDB, Inc., 2013: VoltDB Dokumentation „Management Guide“ Version 3.1, Seiten 26, 43-44
[11] VoltDB, Inc., 2013: Getting Started With VoltDB V3.0, Seiten 3-10
[12] Andrew Lampitt, 2013: Shopzilla buys into big data for inventory management,
http://www.javaworld.com/javaworld/jw-04-2013/130404-shopzilla-buys-into-big-data.html, InfoWorld,
04/04/13
[13] Falko Benthin, 2010: VoltDB: Neues Open-Source-DBMS verspricht Hochverfügbarkeit,
http://www.pro-linux.de/news/1/15714/voltdb-neues-open-source-dbms-verspricht-hochverfuegbarkeit.html
[14] Andy Pavlo, Brown University, 2012: „H-Store about“, Content © 2013 Brown University, Massachusetts
Institute of Technology, and Yale University, http://hstore.cs.brown.edu/about, December 13th, 2012
[15] Ulrich Lenz, 2007: „IT-Systeme: Ausfallsicherheit im Kostenvergleich“, © Copyright IDG BUSINESS MEDIA
GMBH München,
„http://www.tecchannel.de/server/hardware/458076/it_systeme_ausfallsicherheit_im_kostenve
rgleich/index2.html, 10.01.2007
Herunterladen