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