02.11.2013 Aufrufe

Verteilte Auswertung von RDF-Graphen mit MapReduce und ...

Verteilte Auswertung von RDF-Graphen mit MapReduce und ...

Verteilte Auswertung von RDF-Graphen mit MapReduce und ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

Bachelorarbeit<br />

<strong>Verteilte</strong> <strong>Auswertung</strong> <strong>von</strong><br />

<strong>RDF</strong>-<strong>Graphen</strong> <strong>mit</strong> <strong>MapReduce</strong><br />

<strong>und</strong> NoSQL-Datenbanken<br />

Antony R. Neu<br />

26.08.2011<br />

Albert-Ludwigs-Universität Freiburg im Breisgau<br />

Technische Fakultät<br />

Institut für Informatik


“Anyone can build a fast CPU.<br />

The trick is to build a fast system.“<br />

- Seymour Cray


Bearbeitungszeitraum<br />

26. 05. 2011 – 26. 08. 2011<br />

Gutachter<br />

Prof. Dr. Georg Lausen<br />

Betreuer<br />

Alexander Schätzle<br />

Martin Przyjaciel-Zablocki


Kurzfassung<br />

In der vorliegenden Arbeit wird die Speicherung <strong>und</strong> <strong>Auswertung</strong> <strong>von</strong> <strong>RDF</strong>-Daten<br />

<strong>mit</strong> der Kombination aus dem Apache Hadoop Framework <strong>und</strong> der NoSQL-Datenbank<br />

Cassandra untersucht. Cassandra ermöglicht die verteilte Speicherung der Daten<br />

<strong>und</strong> vereint dabei die Eigenschaften <strong>von</strong> Google BigTable <strong>und</strong> Amazon Dynamo. Um<br />

SPARQL Basic Graph Pattern-Anfragen auf den <strong>RDF</strong>-Daten parallel auszuwerten,<br />

wird das quelloffene Projekt Apache Hadoop verwendet. Dabei wird die Komponente<br />

<strong>MapReduce</strong> des Frameworks zur Berechnung der notwendigen Verbünde eingesetzt,<br />

während das verteilte Dateisystem HDFS der Speicherung der Zwischenergebnisse<br />

dient.<br />

Nachdem die Strategien zur <strong>Auswertung</strong> <strong>und</strong> Speicherung vorgestellt wurden, werden<br />

die technischen Details der Implementierung erläutert. Anschließend wird die<br />

Implementierung <strong>mit</strong> synthetisch generierten Daten evaluiert. Die Evaluation liefert<br />

zwar gute Laufzeiten bei der <strong>Auswertung</strong> der Daten, jedoch zeigen sich Probleme<br />

bei der Systemstabilität.<br />

Schlagwörter: <strong>MapReduce</strong>, Hadoop, Cassandra, NoSQL, <strong>RDF</strong> Triple Store, SP 2 Bench,<br />

Map-Side-Join<br />

1


Inhaltsverzeichnis<br />

Kurzfassung 1<br />

1 Einführung 4<br />

2 Gr<strong>und</strong>lagen 6<br />

2.1 Semantic Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2.1.1 Resource Description Framework . . . . . . . . . . . . . . . . 6<br />

2.1.2 SPARQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.2 Apache Hadoop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

2.3 Apache Cassandra . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.3.1 Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.3.2 Verteilung der Daten im Cluster . . . . . . . . . . . . . . . . . 15<br />

2.3.3 Datenzugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

3 Strategien zur <strong>Auswertung</strong> <strong>und</strong> Speicherung der <strong>RDF</strong>-Daten 18<br />

3.1 Speicherstrategie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

3.2 <strong>Auswertung</strong>sstrategie . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />

4 Implementierung <strong>und</strong> Optimierungen 25<br />

4.1 Einlesen der Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />

4.2 Auswerten der Anfragen . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

4.2.1 Generieren der Jobs . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

4.2.2 Map-Side Join . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

4.2.3 Alternativen zum Map-Side Join . . . . . . . . . . . . . . . . 28<br />

4.2.4 Optimierung für Hadoop . . . . . . . . . . . . . . . . . . . . . 30<br />

5 Evaluation 33<br />

5.1 Allgemeine Rahmenbedingunen . . . . . . . . . . . . . . . . . . . . . 33<br />

2


5.2 Laufzeiten <strong>und</strong> Beobachtungen . . . . . . . . . . . . . . . . . . . . . . 34<br />

5.2.1 Q1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

5.2.2 Q2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />

5.2.3 Q3A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />

5.2.4 Q10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

5.2.5 Einfluss des Konsistenzgrads . . . . . . . . . . . . . . . . . . . 39<br />

5.2.6 Einfluss des Replikationsfaktors . . . . . . . . . . . . . . . . . 40<br />

5.2.7 Datenverteilung . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />

5.3 Probleme <strong>und</strong> mögliche Ursachen . . . . . . . . . . . . . . . . . . . . 41<br />

6 Verwandte Arbeiten 43<br />

7 Zusammenfassung 45<br />

7.1 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3


1 Einführung<br />

Die Idee des Semantic Web, die im Jahr 2001 <strong>von</strong> Tim Berners-Lee vorgestellt wurde,<br />

ermöglicht Maschinen das automatische Ableiten <strong>von</strong> Wissen aus dem World<br />

Wide Web. Das stichwortbasierte World Wide Web, das man heute größtenteils<br />

vorfindet, verhindert, dass Maschinen die Semantik hinter den Wörtern verstehen.<br />

Das Resource Description Framework (<strong>RDF</strong>) wurde entwickelt, um eine Ressource<br />

<strong>mit</strong> semantischen Annotationen zu versehen [1]. Eine Ressource kann nicht nur eine<br />

Webseite sein, sondern jede Entität, die sich durch eine eindeutige URI (Unique<br />

Resource Identifier) identifizieren lässt.<br />

Die Vision des Semantic Web funktioniert nur dann, wenn es möglich ist, die riesigen<br />

Daten auszuwerten. Die Speicherung <strong>und</strong> Analyse der Datensätze können oft<br />

nicht <strong>von</strong> einem Rechner alleine realisiert werden. Auch Supercomputer bieten keine<br />

Alternative, denn das Semantic Web soll allen, auch kleineren Firmen oder Institutionen<br />

zur Verfügung stehen. Abhilfe schafft das Konzept des Cloud Computing:<br />

Die Berechnungen finden nicht mehr lokal statt, sondern werden an ein skalierbares<br />

Rechnernetz ausgelagert. Firmen wie Amazon <strong>und</strong> Rackspace vermieten solche<br />

Rechnernetze (Clouds), deren Leistung <strong>und</strong> Kosten sich nach der tatsächlichen Rechenlast<br />

richten. Zur Nutzung dieser skalierbaren Systeme bedarf es Anwendungen<br />

<strong>und</strong> Frameworks, die diese Form der Berechnungen unterstützen.<br />

Hadoop <strong>MapReduce</strong> ist eine quelloffene Implementierung des <strong>MapReduce</strong>-Frameworks,<br />

das ursprünglich <strong>von</strong> Google entwickelt worden ist. Es ermöglicht das Speichern<br />

großer Daten auf dem verteilten Dateisystem HDFS (Hadoop Distributed File System)<br />

<strong>und</strong> das Anwenden <strong>von</strong> Programmen auf diesen Daten. Facebook <strong>und</strong> Twitter<br />

setzen diese Technologie ein, um tausende Petabyte an Daten zu verwalten. Die<br />

Daten können nicht nur blockweise auf dem verteilten Dateisystem, sondern auch<br />

strukturiert <strong>mit</strong> Indizes in Datenbanken gespeichert werden. Das Hadoop-Projekt<br />

beinhaltet zu diesem Zweck eine eigene NoSQL-Datenbank HBase, die bereits zur<br />

Speicherung <strong>und</strong> <strong>Auswertung</strong> <strong>von</strong> <strong>RDF</strong>-Daten untersucht worden ist [2]. NoSQL-<br />

4


Einführung<br />

Datenbanken zeichnen sich durch den Verzicht auf Schemata zur besseren horizontalen<br />

Skalierung aus. Cassandra ist eine weitere NoSQL-Datenbank, <strong>mit</strong> der Firmen<br />

wie beispielsweise Facebook, Digg <strong>und</strong> Twitter gute Erfahrungen gemacht haben.<br />

Ziel dieser Arbeit ist es, die Eigenschaften, die sich aus der Kombination <strong>von</strong> Cassandra<br />

<strong>mit</strong> <strong>MapReduce</strong> ergeben, in Hinblick auf die <strong>Auswertung</strong> <strong>von</strong> SPARQL Basic<br />

Graph Patterns auf großen <strong>RDF</strong>-<strong>Graphen</strong> zu untersuchen. Die Arbeit beschränkt<br />

sich auf das Auswerten einer Folge <strong>von</strong> SPARQL Basic Graph Patterns <strong>mit</strong> maximal<br />

zwei Variablen pro Tripel. Hierfür wurde eine Implementierung entwickelt, die die<br />

Besonderheiten des Datenmodells zum schnellen Zugriff auf die Daten ausnutzt.<br />

Die Arbeit ist wie folgt aufgebaut: In Kapitel 2 werden die Gr<strong>und</strong>lagen des Semantic<br />

Webs <strong>und</strong> der verwendeten Technologien vorgestellt. Darauf aufbauend werden im<br />

folgenden Kapitel die allgemeinen Strategien zur <strong>Auswertung</strong> <strong>und</strong> Speicherung der<br />

<strong>RDF</strong>-Daten vorgestellt. Kapitel 4 erläutert wie diese Strategien in der Implementierung<br />

technisch umgesetzt worden sind. Die Ergebnisse der Evaluation werden in<br />

Kapitel 5 vorgestellt. Es wurden sowohl das Verhalten bei verschiedenen Datengrößen,<br />

als auch der Einfluss verschiedener Parameter untersucht. In Kapitel 6 werden<br />

verwandte Arbeiten vorgestellt <strong>und</strong> die Unterschiede aufgezeigt. Abschließend wird<br />

in Kapitel 7 ein Fazit <strong>und</strong> Ausblick gegeben.<br />

5


2 Gr<strong>und</strong>lagen<br />

2.1 Semantic Web<br />

Im Jahr 2001 stellte Tim Berners-Lee, der Direktor des W3C, seine Vision des Semantic<br />

Webs in einem Artikel in Scientific American vor [1]. Das Semantic Web soll<br />

nach Lee nicht das damalige World Wide Web ersetzen, sondern eine Erweiterung<br />

dessen sein. Webseiten sind größtenteils so aufgebaut, dass Menschen sie lesen <strong>und</strong><br />

verstehen können, Maschinen allerdings nicht. Maschinen können nicht erkennen, ob<br />

<strong>mit</strong> dem Wort „Bank“ das Finanzinstitut oder eine Sitzmöglichkeit im Park gemeint<br />

ist. Das Semantic Web erlaubt es Maschinen, Wissen aus Webseiten zu gewinnen<br />

<strong>und</strong> diese zu verknüpfen. In dem Artikel beschreibt Berners-Lee, dass es im Semantic<br />

Web intelligenten Systemen möglich sein wird, Informationen für die Benutzer zu<br />

gewinnen <strong>und</strong> zu verbinden, die sie sonst selbst recherchieren müssten. Dies sei nur<br />

<strong>mit</strong> einem offenen Standard möglich, der jedoch bis heute noch nicht ausreichend<br />

verwendet wird [3]. Seiten wie Wikipedia, Twitter oder Flickr stellen zwar diese Informationen<br />

bereit, da sie aber nicht gemeinsam offene Standards verwenden, können<br />

sie nicht <strong>mit</strong>einander verknüpft werden.<br />

2.1.1 Resource Description Framework<br />

Das Resource Description Framework (<strong>RDF</strong>) ist ein W3C-Standard, der es ermöglicht,<br />

Inhalte <strong>von</strong> Webseiten <strong>mit</strong> ihrer Bedeutung zu verknüpfen [4]. Das Hauptelement<br />

eines <strong>RDF</strong>-Dokuments ist die sogenannte Aussage. Sie besteht aus drei<br />

Elementen: Subjekt, Prädikat <strong>und</strong> Objekt. Das Subjekt ist eine URI (Unique Resource<br />

Identifier), die eindeutig eine Ressource, z.B. eine Person, beschreibt. Das<br />

Prädikat ist ebenfalls eine URI <strong>und</strong> dient der Beschreibung der Relation zwischen<br />

Subjekt <strong>und</strong> Objekt. Beim Objekt handelt es sich entweder um eine URI, wenn eine<br />

Beziehung zwischen zwei Ressourcen hergestellt werden soll, oder um ein Literal,<br />

6


2.1 Semantic Web<br />

zum Beispiel eine Zahl [3]. Sollen Subjekt oder Objekt nicht benannt werden, kann<br />

auch ein sogenannter leerer Knoten anstatt einer URI verwendet werden. Dadurch<br />

lassen sich Beziehungen zu Objekten modellieren, die nicht bekannt sind oder nicht<br />

benannt werden sollen. Betrachtet man die Aussagen als eine vom Subjekt ausgehende<br />

gerichtete Kante zwischen Subjekt <strong>und</strong> Objekt, die <strong>mit</strong> dem Prädikat beschriftet<br />

wird, so ergibt sich ein gerichteter Graph für das <strong>RDF</strong>-Dokument, der als <strong>RDF</strong>-<br />

Graph bezeichnet wird. Abb. 2.1 zeigt ein Beispiel eines solchen <strong>RDF</strong>-<strong>Graphen</strong>, der<br />

Informationen zur Person Eric Miller beschreibt.<br />

Abbildung 2.1: Beispiel eines <strong>RDF</strong>-<strong>Graphen</strong>. Quelle: [5]<br />

Die Person wird dabei eindeutig anhand der URI http://www.w3.org/People/<br />

EM/contact#me identifiziert, sodass es zu keinen Mehrdeutigkeiten kommen kann.<br />

Als Enkodierung wird häufig die XML-Syntax oder die Turtle-Syntax (Terse <strong>RDF</strong><br />

Triple Language 1 ) gewählt, während Letztere leichter für Menschen zu lesen ist <strong>und</strong><br />

im Folgenden verwendet wird. In der Turtle-Syntax werden Aussagen wie Sätze der<br />

natürlichen Sprache <strong>mit</strong> der Reihenfolge Subjekt, Prädikat <strong>und</strong> Objekt geschrieben<br />

<strong>und</strong> <strong>mit</strong> einem Punkt beendet. Zur Abkürzung können für jedes <strong>RDF</strong>-Dokument<br />

Präfixe definiert werden, die automatisch ergänzt werden. Einige Tripel des <strong>RDF</strong>-<br />

<strong>Graphen</strong> aus Abb. 2.1 werden demnach wie in Listing 2.1 dargestellt enkodiert.<br />

1 http://www.w3.org/TeamSubmission/turtle/<br />

7


2.1 Semantic Web<br />

@prefix r d f : .<br />

@prefix contact : .<br />

http : / /www. w3 . org / People /EM/ contact#me r d f : type contact : Person .<br />

http : / /www. w3 . org / People /EM/ contact#me contact : fullName Eric M i l l e r .<br />

http : / /www. w3 . org / People /EM/ contact#me contact : mailbox mailto :em@w3. org .<br />

Listing 2.1: Beispielhaft Enkodierung <strong>von</strong> <strong>RDF</strong>-Daten<br />

Datenspeicher für <strong>RDF</strong>-Tripel werden häufig als Triple-Stores bezeichnet. Die Funktionalität<br />

solcher Stores reicht <strong>von</strong> einfachen Datenspeichern bis hin zu ausgereiften<br />

Datenbanksystemen, die Anfragen auf <strong>RDF</strong>-Daten auswerten können. Vertreter solcher<br />

ausgereifter Triple-Stores sind beispielsweise Sesame 2 <strong>und</strong> Jena 3 .<br />

2.1.2 SPARQL<br />

SPARQL (SPARQL Protocol And <strong>RDF</strong> Query Language) ist eine Anfragesprache<br />

für <strong>RDF</strong>-Daten, ähnlich SQL für relationale Datenbanken. Seit dem 15. Januar 2008<br />

ist SPARQL eine offizielle Empfehlung des W3C (W3C recommendation) [6]. Diese<br />

Einführung soll im Wesentlichen nur den Teil der Syntax abdecken, der auch <strong>von</strong><br />

der Implementierung unterstützt wird. Zum leichteren Verständnis, wird die Syntax<br />

anhand eines Beispiels aus [4] eingeführt.<br />

PREFIX ex <br />

SELECT ?titel ?author<br />

WHERE<br />

{ ?buch ex:VerlegtBei .<br />

?buch ex:Titel ?titel .<br />

?buch ex:Autor ?autor .<br />

}<br />

Listing 2.2: Beispielhafte SPARQL-Anfrage (Quelle: [4])<br />

Die in Listing 2.2 dargestellte Anfrage gibt die Titel <strong>und</strong> Autoren aller Bücher, die<br />

beim Springer-Verlag verlegt wurden, zurück. Wie im Beispiel zu sehen ist, besteht<br />

eine SPARQL-Anfrage zum größten Teil aus <strong>RDF</strong>-Tripeln, die in Turtle-Syntax geschrieben<br />

sind. Die Tripel beschreiben einen Subgraph des <strong>RDF</strong>-<strong>Graphen</strong>, auf den<br />

2 http://www.openrdf.org/<br />

3 http://jena.sourceforge.net/<br />

8


2.1 Semantic Web<br />

man die Anfrage stellt. An jeder Tripelposition (Subjekt, Prädikat oder Objekt) ist<br />

es möglich, eine Variable zu platzieren, die durch ein „?“ gekennzeichnet wird. Eine<br />

Menge solcher Tripel wird als Basic Graph Pattern bezeichnet. Die Formatierung<br />

der Ausgabe erfolgt <strong>mit</strong> dem Befehl „SELECT“ gefolgt <strong>von</strong> einer Teilmenge der<br />

verwendeten Variablen, d. h. es werden nur die Werte der Variablen ausgegeben, die<br />

nach „SELECT“ aufgeführt werden. Wie bei der Deklaration <strong>von</strong> <strong>RDF</strong>-Daten ist es<br />

möglich Präfixe zu definieren, um gekürzte Schreibweisen zu verwenden. SPARQL<br />

erlaubt weitere Befehle wie zum Beispiel „FILTER“, „LIMIT“ <strong>und</strong> „UNION“, um<br />

die Ergebnisse weiter einzuschränken oder zu erweitern.<br />

PREFIX ex <br />

SELECT ?titel ?author<br />

WHERE<br />

{ ?buch ex:Titel ?titel .<br />

?buch ex:Autor ?autor .<br />

{?buch ex:VerlegtBei .}<br />

UNION<br />

{?buch ex:VerlegtBei . }<br />

}<br />

Listing 2.3: Beispielhafte SPARQL-Anfrage <strong>mit</strong> Operatoren<br />

Die Anfrage, die in Listing 2.3 dargestellt ist, selektiert alle Paare aus Titel <strong>und</strong><br />

Autor der Verlage Springer <strong>und</strong> O’Reilly. Diese weiteren Befehle werden im Folgenden<br />

in dieser Arbeit nicht weiter betrachtet, da sie <strong>von</strong> der Implementierung<br />

nicht unterstützt werden. Ebenso erfolgt keine Formatierung durch „SELECT“. Die<br />

Implementierung beschränkt sich auf die <strong>Auswertung</strong> <strong>von</strong> Anfragen, die aus einem<br />

Basic Graph Pattern bestehen. Weiterhin wird angenommen, dass pro Tripel nicht<br />

mehr als zwei Variablen vorkommen. Die Anfragen haben also alle die Form, die in<br />

Listing 2.4 dargestellt wird.<br />

9


2.2 Apache Hadoop<br />

PREFIX p1 <br />

PREFIX ...<br />

SELECT *<br />

WHERE<br />

{ s1 p1 ?v1 .<br />

}<br />

?v1 p2 o2 .<br />

...<br />

Listing 2.4: SPARQL-Anfrage, die <strong>von</strong> der Implementierung unterstützt wird.<br />

2.2 Apache Hadoop<br />

Hadoop ist ein quelloffenes Projekt <strong>mit</strong> vielen Unterprojekten zur Speicherung <strong>und</strong><br />

Analyse großer Daten, das seine Wurzeln im Apache Nutch 4 Projekt hat. Hadoop<br />

wurde ursprünglich <strong>von</strong> Doug Cutting entwickelt <strong>und</strong> basiert auf dem verteilten<br />

Dateisystem Google FS, sowie Google <strong>MapReduce</strong>, einem Framework für nebenläufige<br />

Berechnungen über große Daten. <strong>MapReduce</strong> wurde ebenfalls <strong>von</strong> Google zur<br />

Wartung der Suchindizes entwickelt. Die beiden Technologien wurden 2003 <strong>und</strong> 2004<br />

in [7, 8] vorgestellt. Seit 2006 beteiligt sich Yahoo an dem Projekt <strong>und</strong> entwickelte<br />

zwischenzeitlich seine eigene Distribution. 2008 wurde das Projekt zu einem offiziellen<br />

Apache Top-Level-Projekt <strong>und</strong> wurde seitdem <strong>von</strong> Unternehmen wie Last.fm,<br />

Facebook <strong>und</strong> der NewYork-Times eingesetzt [9].<br />

Das Projekt besteht aus mehreren Bestandteilen <strong>und</strong> Unterprojekten, unter anderem:<br />

Pig, Hive <strong>und</strong> HBase. Letzteres ist eine NoSQL Datenbank, die Ähnlichkeiten<br />

zu Cassandra aufweist, jedoch auf Hadoop basiert. Pig vereinfacht die Analyse<br />

<strong>von</strong> Daten <strong>und</strong> stellt häufig genutze Methoden, z.B. Joins bereit. Hive ist eine<br />

Datawarehouse-Applikation, die eine SQL-ähnliche Sprache zur Analyse der auf dem<br />

HDFS gespeicherten Daten implementiert. Im Folgenden wird ausschließlich auf die<br />

Bestandteile <strong>MapReduce</strong> <strong>und</strong> HDFS eingegangen, da sie in der Implementierung<br />

verwendet wurden.<br />

Eine wichtige Kernidee <strong>von</strong> <strong>MapReduce</strong> ist die Ausnutzung <strong>von</strong> Lokalität. Die Daten<br />

werden <strong>mit</strong>tels HDFS auf den verschiedenen Rechnern (Knoten) im Netzwerk<br />

gespeichert. Möchte eine Anwendung auf die Daten zugreifen, greift jeder Knoten<br />

4 http://nutch.apache.org/<br />

10


2.3 Apache Cassandra<br />

auf die Daten zu, die ihm lokal zur Verfügung stehen. Dadurch werden weniger Daten<br />

über das Netzwerk ausgetauscht <strong>und</strong> im Idealfall haben alle Knoten die gleiche<br />

Größe an Daten zu verarbeiten.<br />

Da<strong>mit</strong> dies möglich ist, müssen die Anwendungen dem Map-Reduce-Paradigma folgen.<br />

Das Paradigma besagt, dass jedes Programm, ein sogenannter Job, aus einer<br />

Map- <strong>und</strong> einer Reduce-Phase besteht. Ein Knoten verarbeitet zunächst mehrere<br />

Map-Tasks, die eine Teilmenge der Daten (Inputsplits) als Eingabe erhalten. Die<br />

Inputsplits sind lokal auf dem Knoten gespeichert. Eine Eingabe besteht immer aus<br />

einem Schlüssel <strong>und</strong> einem Wert, ebenso die Ausgabe der Mapper (MapInputKey,<br />

MapInputValue, MapOutputKey <strong>und</strong> MapOutputValue). Nach der Map-Phase werden<br />

die Daten anhand des MapOutputKey sortiert <strong>und</strong> zusammengefasst. Sie werden<br />

dann an Reduce-Tasks, die auf anderen Knoten laufen können, geschickt. Ein<br />

Reduce-Task erhält einen Schlüssel (ReduceInputKey) <strong>und</strong> eine Menge <strong>von</strong> Werten<br />

(ReduceInputValues) als Eingabe. Die Ausgabe der Reducer ist ebenfalls ein<br />

Schlüssel <strong>und</strong> ein zugehöriger Wert. Das Format des ReduceInputKey ist identisch<br />

<strong>mit</strong> dem Format des MapOutputKey. Die Eingabewerte sind die Werte, die sortiert<br />

<strong>und</strong> zusammengefasst worden sind, <strong>und</strong> den gleichen MapOutputKey haben. Es ist<br />

also gewährleistet, dass allen Reduce-Tasks jeweils alle diejenigen Werte zur Verfügung<br />

stehen, die <strong>mit</strong> dem gleichen MapOutputKey versehen worden sind. Schließlich<br />

werden nach der Verarbeitung in der Reduce-Phase ReduceOutputKey <strong>und</strong> ReduceOutputValue<br />

auf das HDFS geschrieben. Abb. 2.2 visualisiert den Workflow einer<br />

<strong>MapReduce</strong>-Anwendung[9, 10].<br />

Setzt man die Anzahl der Reduce-Tasks auf null, so erfolgt weder eine Sortierung<br />

noch eine weitere Verarbeitung durch den Reducer. Die Ergebnisse der Map-Phase<br />

werden direkt in das HDFS geschrieben. Das bedeutet, dass auch kein Austausch<br />

der Daten über das Netzwerk erfolgt, wie es beim Sortieren <strong>und</strong> Zusammenfassen<br />

vor der Reduce-Phase der Fall wäre.<br />

2.3 Apache Cassandra<br />

Klassiche SQL-Datenbanken haben sich in allen Bereichen der Informatik fest etabliert.<br />

Wachsende Datenmengen erfordern jedoch eine Möglichkeit der Skalierung bei<br />

der Analyse der Daten. Um dieser Nachfrage gerecht zu werden, entstanden die sogenannten<br />

NoSQL-Datenbanken („not only SQL“ oder „no SQL“). Eine Gemeinsam-<br />

11


2.3 Apache Cassandra<br />

Abbildung 2.2: <strong>MapReduce</strong> Workflow. Quelle: [10]<br />

keit aller NoSQL-Datenbanken, ist der Verzicht auf fixe Schemata zu Gunsten einer<br />

besseren horizontalen Skalierung. Weitere Gemeinsamkeiten sind die verteilte Speicherung<br />

der Daten, dynamische Anpassung der gespeicherten Daten <strong>und</strong> der Verzicht<br />

auf ACID-Eigenschaften wie sie bei SQL-Systemen üblich sind. Momentan gibt<br />

es mehr als h<strong>und</strong>ert verschiedene NoSQL-Datenbanken 5 <strong>mit</strong> verschiedenen Datenmodellen.<br />

Sie lassen sich u. a. in dokumentenorientierte Datenbanken (CouchDB 6 ),<br />

<strong>Graphen</strong>datenbanken (Neo4J 7 ) oder Objektdatenbanken (ZODB 8 ) einteilen. Cassandra<br />

fällt in die Kategorie Key-Value-Store [11, 12].<br />

Cassandras Datenmodell basiert auf der NoSQL-Datenbank Google Bigtable, während<br />

die Verteilung der Daten Gemeinsamkeiten <strong>mit</strong> Amazon Dynamo aufweist.<br />

Ursprünglich wurde es <strong>von</strong> Facebook entwickelt, um das Durchsuchen des Nachrichtensystems<br />

zu realisieren. Seit 2010 ist das Projekt ein Top-Level-Projekt der<br />

Apache Fo<strong>und</strong>ation.<br />

5 http://nosql-database.org/<br />

6 http://couchdb.apache.org/<br />

7 http://neo4j.org/<br />

8 http://www.zodb.org/<br />

12


2.3 Apache Cassandra<br />

2.3.1 Datenmodell<br />

Das Gr<strong>und</strong>element des Datenmodells ist die Spalte (engl. column). Eine Spalte besteht<br />

aus drei Elementen: einem Schlüssel, einem Wert <strong>und</strong> einem Zeitstempel. Der<br />

Zeitstempel wird verwendet, um eventuell auftretende Konflikte in der verteilten Datenmenge<br />

zu lösen. Der Schlüssel <strong>und</strong> der Wert lassen sich als Abbildung verstehen,<br />

in der der Schlüssel auf den Wert abgebildet wird. Eine Spalte kann auch weitere<br />

Spalten enthalten <strong>und</strong> wird in diesem Fall als Superspalte (engl. super column)<br />

bezeichnet. Superspalten haben keinen Zeitstempel, da die Subspalten Zeitstempel<br />

enthalten. Ein Datensatz besteht aus mehreren Spalten oder Superspalten <strong>und</strong> wird<br />

als Zeile (engl. row) bezeichnet. Der Datensatz wird eindeutig anhand seines Zeilenschlüssels<br />

(engl. row key) identifiziert [11].<br />

Datensätze werden in Spaltenfamilien (engl. column families) zusammengefasst, ähnlich<br />

den Tabellen in SQL. Eine Spaltenfamilie kann nur entweder Spalten oder Superspalten<br />

enthalten. Enthält sie Letzteres handelt es sich um eine Superspaltenfamilie.<br />

Im Gegensatz zum relationalen Datenmodell, hat nicht jeder Datensatz die gleiche<br />

Anzahl Columns. Ebenso können Datensätze frei um weitere Columns erweitert werden.<br />

In relationalen Datenbanken wäre eine Änderung der Tabelle notwendig. Mehre<br />

Spaltenfamilien werden in einem Keyspace zusammengefasst.<br />

Ein alternativer Ansatz ist das Betrachten des Datenmodells als multidimensionale<br />

Map. Abhängig da<strong>von</strong>, ob Superspaltenfamilien oder Spaltenfamilien verwendet<br />

werden, ergibt sich eine Map <strong>mit</strong> 4 oder 5 Dimensionen, die in Listing 2.5 dargestellt<br />

wird.<br />

Keyspace [ ColumnFamilyName ] [ Rowkey ] [ Columnname ] = value<br />

Keyspace [ ColumnFamilyName ] [ Rowkey ] [ SuperColName ] [ SubColName ] = value<br />

Listing 2.5: Cassandras Datenmodell als multidimensionale Map<br />

Abb. 2.3 zeigt den Aufbau einer Superspaltenfamilie anhand eines Beispiels. Die<br />

Studenten-Superspalten enthalten unterschiedliche Subspalten, da nicht zu jedem<br />

Studenten eine Handynummer existiert. Außerdem besuchen nicht alle Studenten die<br />

gleichen Veranstaltungen, sodass auch hier eine unterschiedliche Anzahl an Subspalten<br />

entsteht. Auch die Anzahl Superspalten ist nicht fix <strong>und</strong> könnte unterschiedlich<br />

sein.<br />

Die Daten werden beim Einfügen in die Datenbank sortiert, sodass keine nachträgliche<br />

Sortierung erfolgt. Eine Funktion wie „ORDER BY“ aus SQL gibt es nicht.<br />

13


2.3 Apache Cassandra<br />

Student<br />

(SuperColumnFamily)<br />

Rowkey<br />

:<br />

217654<br />

SuperColumn: Kontakt<br />

Telefon 004848<br />

Mobil 789705408<br />

SuperColumn: Noten<br />

Info 1 3,7<br />

Info 2 2,0<br />

Praktikum 1,3<br />

Rowkey<br />

:<br />

235678<br />

SuperColumn: Kontakt<br />

Telefon 4589<br />

ICQ 549879<br />

SuperColumn: Noten<br />

Info 1 1,7<br />

BWL 1 1,3<br />

eMail<br />

ex@example.org<br />

...<br />

Abbildung 2.3: Beispielhafte SuperColumnFamily<br />

Dadurch werden hohe Leseraten erzielt, jedoch kann die Sortierung nicht beim Lesezugriff<br />

festgelegt werden. Bei Spaltenfamilien erfolgt die Sortierung über den Namen<br />

der Spalte, bei Superspaltenfamilien über den Namen der Superspalten <strong>und</strong><br />

Subspalten. Der Datenbankentwickler kann zwischen verschiedenen vordefinierten<br />

Vergleichern (engl. Comparator) auswählen oder einen eigenen implementieren. In<br />

der Version 0.8.2 stellt Cassandra folgende Vergleicher zur Verfügung: AsciiType,<br />

BytesType, CompositeType, LexicalUUIDType, LongType, TimeUUIDType <strong>und</strong><br />

UTF8Type [11]. Beim Erstellen einer Spaltenfamilie wird jeweils ein Vergleicher für<br />

die Super- <strong>und</strong> Subspalten festgelegt.<br />

Während klassische relationale Datenbanken aus Tabellen bestehen, die über einen<br />

Verb<strong>und</strong> bei einer Anfrage <strong>mit</strong>einander verknüpft werden können, müssen bei Cassandra<br />

die Daten vorab denormalisiert werden. Die Daten werden bei der Denormalisierung<br />

mehrmals unterschiedlich formatiert in die Datenbank geschrieben, z.B.<br />

würden alle benötigten Verbünde vorab berechnet <strong>und</strong> abgespeichert werden.<br />

Eben Hewitt beschreibt in [11], dass es wichtig ist, sich bei der Entwicklung einer<br />

Anwendung mögliche Anfragen an die Datenbank vorab zu überlegen <strong>und</strong> die Daten-<br />

14


2.3 Apache Cassandra<br />

bank entsprechend aufzubauen. Hewitt empfiehlt eine Spaltenfamilie pro möglicher<br />

Art der Anfrage.<br />

2.3.2 Verteilung der Daten im Cluster<br />

Cassandra bietet verschiedene Möglichkeiten der Partitionierung. Es werden vordefinierte<br />

Partitionierer (engl. Partitioners) <strong>mit</strong>geliefert, die um eigene Partitionierer,<br />

die das IPartitioner Interface implementieren, erweitert werden können [11].<br />

Gr<strong>und</strong>sätzlich werden zur Bestimmung des Knotens, auf dem die Daten gespeichert<br />

werden sollen, der Zeilenschlüssel, der Replikationsfaktor (engl. replication factor)<br />

<strong>und</strong> die Replikationsstrategie verwendet 9 . Der Replikationsfaktor gibt an, wieviele<br />

Kopien der Daten im Cluster existieren. Ein Replikationsfaktor <strong>von</strong> 3 bedeutet<br />

beispielsweise, dass ein Datensatz dreimal im Cluster abgespeichert wird. Der Randompartitioner<br />

erlaubt es, die Daten gleichmäßig über den Cluster zu verteilen, da<br />

als Schlüssel der MD5-Hashwert des Zeilenschlüssels verwendet wird. Allerdings bedeutet<br />

dies auch, dass die Daten unsortiert vom System ausgegeben werden <strong>und</strong><br />

Anfragen, die gezielte Zeilenbereiche auswählen, ineffizient ausgeführt werden. Der<br />

OrderPreservingPartitioner verwendet die UTF8-Repräsentation des Zeilenschlüssels<br />

als Schlüssel. Folglich werden die Daten anhand des Zeilenschlüssels physisch<br />

abgespeichert. Je nach Zeilenschlüssel kann dies bedeuten, dass die Daten unregelmäßig<br />

über den Cluster verteilt werden, da die zu speichernden Daten oftmals nicht<br />

gleichverteilt sind. Wird als Zeilenschlüssel zum Beispiel der Nachname einer Person<br />

verwendet, so gäbe es eine Häufung bei den Knoten, die die Nachnamen, die<br />

<strong>mit</strong> „S“ beginnen, abspeichern. Die Last wäre also nicht regelmäßig verteilt. Vorteilhaft<br />

ist allerdings das Ausgeben sortierter Daten <strong>und</strong> die Möglichkeit, effizient<br />

Zeilenbereichsanfragen auszuwerten [11, 13].<br />

2.3.3 Datenzugriff<br />

Die Speicherung der Daten weist erhebliche Unterschiede zu relationalen Datenbanksystemen<br />

auf, folglich erfolgt der Datenzugriff ebenfalls auf eine andere Art.<br />

Cassandra ermöglicht den Zugriff der gespeicherten Daten per Thrift 10 <strong>und</strong> stellt<br />

hierfür eine API zur Verfügung. Die Entwickler <strong>von</strong> Cassandra empfehlen jedoch,<br />

9 http://wiki.apache.org/cassandra/FAQ#replicaplacement<br />

10 http://thrift.apache.org/<br />

15


2.3 Apache Cassandra<br />

nicht direkt per Thrift auf Cassandra zuzugreifen, sondern auf einen Client in der<br />

gewünschten Programmiersprache zurückzugreifen[11, 13].<br />

Der Konsistenzgrad (engl. consistency level) wird pro Schreib- oder Leseanfrage angegeben<br />

<strong>und</strong> definiert die Dauerhaftigkeit (engl. durability) der Anfrage. Die möglichen<br />

Konsistenzgrade bei einer Leseanfrage werden nachfolgend aufgelistet[11, 13]:<br />

ONE: Die Anfrage wird sofort vom ersten Knoten, an den die Anfrage gestellt wird,<br />

beantwortet. Dabei ist es möglich, dass das Ergebnis veraltet ist. Im Hintergr<strong>und</strong><br />

wird ein Prozess gestartet, der überprüft, ob die Daten auf den Knoten<br />

identisch sind, die gemäß Replikationsfaktor Kopien der Daten speichern. Ist<br />

dies nicht der Fall, wird das Datum verwendet, dessen Zeitstempel aktueller<br />

ist. Dieser Vorgang wird als „read repair“ bezeichnet.<br />

QUORUM: Die Anfrage wird erst beantwortet, wenn n + 1 Knoten die Daten auf<br />

2<br />

Konsistenz überprüft haben, wobei n der Replikationsfaktor ist. Im Hintergr<strong>und</strong><br />

wird ein „read repair“-Prozess gestartet, der alle Kopien im Cluster<br />

überprüft.<br />

ALL : Alle n Knoten, auf denen sich eine Kopie der Daten befindet, müssen die<br />

Konsistenz bestätigen. Die Anfrage schlägt fehl, wenn einer der Knoten nicht<br />

verfügbar ist.<br />

Werden die Konsistenzgrade „ONE“ oder „QUORUM“ verwendet, ist es möglich,<br />

dass die Anfrage einen veralteten Wert zurückliefert. Das Reparieren der Daten<br />

erfolgt in diesem Falle erst, nachdem der Wert zurückgegeben worden ist. Da alle<br />

weiteren Anfragen nach der Reparatur konsistent sind, wird die Konsistenz <strong>von</strong><br />

Cassandra als „eventually consistent“ bezeichnet.<br />

Beim Schreiben stehen weitere Konsistenzgrade zur Verfügung:<br />

ANY : Der Schreibvorgang muss nur <strong>von</strong> einem Knoten bestätigt werden, unabhängig<br />

vom Replikationsfaktor. Dabei wird ein sogenannter Hint als Bestätigung<br />

akzeptiert. Ein Hint wird dann zurückgeliefert, wenn ein Knoten, auf dem<br />

die Daten als Kopie vorliegen, nicht verfügbar ist. Cassandra speichert den<br />

Schreibvorgang zwischen <strong>und</strong> schreibt die Daten, sobald der Knoten wieder<br />

erreichbar ist.<br />

ONE : Dieser Konsistenzgrad verhält sich wie „ANY“, jedoch reicht ein Hint als<br />

Bestätigung nicht aus.<br />

16


2.3 Apache Cassandra<br />

QUORUM : n + 1 Knoten, wobei n der Replikationsfaktor ist, müssen den Schreibvorgang<br />

bestätigen.<br />

2<br />

ALL : Alle n Knoten müssen den Schreibvorgang bestätigen.<br />

Nachdem ein Konsistenzgrad ausgewählt worden ist, kann nun der Zugriff auf die<br />

Daten erfolgen. Wird das Datenmodell als multidimensionale Map betrachtet, erfolgt<br />

der Zugriff durch Angeben der Schlüssel. Je nachdem, welche Schlüssel angegeben<br />

worden sind, werden Subspalten, Superspalten oder ganze Zeilenbereiche zurückgegeben.<br />

Eine Filterung der gewünschten Daten ist durch Definition <strong>von</strong> Wertebereichen<br />

(engl. ranges) möglich. Wird bei einer Anfrage ein SlicePredicate definiert,<br />

so werden nur die dadurch definierten Spalten zurückgegeben. Dabei gibt es zwei<br />

Möglichkeiten, die gewünschten Spalten zu definieren. Entweder werden die Spaltennamen<br />

explizit angegeben oder es wird ein Bereich definiert, der durch einen<br />

Anfangs- <strong>und</strong> Endschlüssel gegeben ist. Die Daten zwischen Start <strong>und</strong> Ende ergeben<br />

sich aus dem verwendeten Vergleicher. Per SlicePredicate kann außerdem eine<br />

Li<strong>mit</strong>ierung der Spaltenanzahl („count“) <strong>und</strong> die Umkehrung der Reihenfolge („reversed“)<br />

erfolgen. Es ist nicht möglich ein SlicePredicate für Subspalten zu definieren.<br />

Wird eine Superspalte ausgewählt, werden immer alle Subspalten serialisiert. Eine<br />

Anwendung, die nicht alle Subspalten benötigt, muss also nach der Serialisierung<br />

filtern.<br />

Die Funktionen get_range_slices <strong>und</strong> multi_get_range der Cassandra API<br />

erlauben es, die resultierenden Zeilen der Anfrage einzuschränken. Sie erfordern aber,<br />

um effektiv genutzt werden zu können, den Einsatz des veralteten OrderPreserving-<br />

Partitioner. Eine Übersicht aller weiteren Funktionen findet sich in [11, 13].<br />

17


3 Strategien zur <strong>Auswertung</strong> <strong>und</strong><br />

Speicherung der <strong>RDF</strong>-Daten<br />

In diesem Kapitel werden die Strategien zur Speicherung <strong>und</strong> <strong>Auswertung</strong> der <strong>RDF</strong>-<br />

Daten <strong>mit</strong> Cassandra <strong>und</strong> Hadoop beschrieben. Details der Implementierung werden<br />

in Kapitel 4 erläutert.<br />

3.1 Speicherstrategie<br />

Die Speicherstrategie der Implementierung basiert auf einem Vorschlag, der <strong>von</strong><br />

Ladwig <strong>und</strong> Harth in [14] vorgestellt wurde. Die Autoren präsentieren ein Speicherschema<br />

für <strong>RDF</strong>-Daten, sowie einen <strong>RDF</strong>-Layer <strong>und</strong> <strong>RDF</strong>-Store (Cumulus<strong>RDF</strong>) für<br />

Cassandra. Die Daten werden insgesamt dreimal abgespeichert, um schnelles Nachschlagen<br />

aller acht möglichen Tripelmuster zu gewährleisten. Zum Speichern der Daten<br />

werden in Cassandra drei Superspaltenfamilien verwendet: „SPO“, „POS“ <strong>und</strong><br />

„OSP“. Die Tripel werden auf Zeilenschlüssel, Superspaltenname <strong>und</strong> Subspaltenname<br />

abgebildet, wobei der Wert der Subspalte leer gelassen wird. Dies ist notwendig,<br />

um mehrwertige Prädikate zu unterstützen. In der Spaltenfamilie „SPO“ wird das<br />

Subjekt als Zeilenschlüssel, das Prädikat als Superspaltenname <strong>und</strong> das Objekt als<br />

Subspaltenname verwendet. Äquivalent werden die Tripel für die Spaltenfamilien<br />

„POS“ <strong>und</strong> „OSP“ abgebildet. Abb. 3.1 verdeutlicht die Speicherstrategie anhand<br />

eines Beispiels. Die <strong>RDF</strong>-Daten modellieren Bekanntschaften zwischen Benutzern,<br />

sowie deren Alter. Es werden die drei Superspaltenfamilien, sowie die zugehörigen<br />

Schlüssel <strong>und</strong> Spaltennamen aufgeführt. Aus Platzgründen werden lediglich Auszüge<br />

gezeigt.<br />

18


3.1 Speicherstrategie<br />

data.n3<br />

...<br />

ex:Tom ex:userKnows ex:Jerry<br />

ex:Tom ex:userKnows ex:Chris<br />

ex:Jerry ex:userKnows ex:Chris<br />

…<br />

ex:Tom ex:userAge 28<br />

ex:Tom ex:userAge 25<br />

...<br />

Cluster: Cluster01<br />

Keyspace: <strong>RDF</strong>-Data<br />

SPO<br />

ex:Jerry<br />

ex:userKnows<br />

ex:userAge<br />

ex:Tom „“<br />

ex:Chris „“<br />

...<br />

25 „“<br />

...<br />

...<br />

ex:Tom<br />

ex:userKnows<br />

ex:Jerry „“<br />

ex:Chris „“<br />

...<br />

...<br />

ex:userAge<br />

28 „“<br />

...<br />

...<br />

POS<br />

ex:userKnows<br />

ex:Chris<br />

ex:Tom<br />

ex:Tom „“<br />

ex:Jerry „“<br />

...<br />

ex:Jerry „“<br />

...<br />

...<br />

ex:userAge<br />

ex:25<br />

ex:28<br />

ex:Jerry „“ ex:Tom „“<br />

...<br />

...<br />

...<br />

OSP<br />

...<br />

25<br />

ex:Jerry<br />

ex:userAge „“<br />

...<br />

ex:Chris<br />

...<br />

ex:Tom<br />

ex:userKnows „“<br />

...<br />

...<br />

...<br />

Abbildung 3.1: Beispielhafte Speicherung der Daten<br />

19


3.1 Speicherstrategie<br />

Tab. 3.1 führt die möglichen Tripelmuster auf, sowie die Spaltenfamilien, die zur<br />

<strong>Auswertung</strong> verwendet werden können. Es wird bei dieser Strategie versucht, möglichst<br />

viele Schlüssel beim Zugriff festzulegen. Die Schlüssel werden immer in der<br />

Reihenfolge Zeilenschlüssel, Superspaltenname <strong>und</strong> Subspaltenname festgelegt. Bei<br />

einer Variablen werden folglich Zeilenschlüssel <strong>und</strong> Superspaltenname (via SlicePredicate)<br />

beim Zugriff festgelegt. Bei zwei Variablen im Tripelmuster kann nur der<br />

Zeilenschlüssel festgelegt werden. Bei dieser Strategie muss also nur im Falle <strong>von</strong><br />

drei Variablen über die Zeilenschlüssel iteriert werden. Dadurch wird das Ergebnis<br />

der Anfrage frühzeitig eingeschränkt. Ein wesentlicher Nachteil ist der hohe Speicherplatzbedarf.<br />

Ein Datum wird in drei verschiedenen Spaltenfamilien gespeichert.<br />

Wird zusätzlich ein Replikationsfaktor größer 1 gewählt, existieren weitere Kopien<br />

der Daten. Bei einem Replikationsfaktor <strong>von</strong> 3 ergäben sich so<strong>mit</strong> 9 Kopien (insgesamt)<br />

der Daten im System.<br />

Triple Pattern Index<br />

(s, p, o) SPO, POS, OSP<br />

(s, p, ?) SPO<br />

(?, p, o) POS<br />

(s, ?, o) OSP<br />

(?, p, ?) POS<br />

(s, ?, ?) SPO<br />

(?, ?, o) OSP<br />

(?, ?, ?) SPO, POS, OSP<br />

Tabelle 3.1: Tripelmuster <strong>mit</strong> zu verwendender Spaltenfamilie. Quelle: [14]<br />

Der <strong>RDF</strong>-Store Cumulus<strong>RDF</strong>[14] unterstützt keine leeren Knoten, da hier ein Verb<strong>und</strong><br />

notwendig ist. Die Autoren beschränken sich aber hier auf das Nachschlagen<br />

<strong>von</strong> Tripelmustern.<br />

Implementierungen, die <strong>RDF</strong>-Daten auf Hadoops verteilten Dateisystem speichern,<br />

nutzen häufig das Prinzip der vertikalen Partitionierung aus. Die <strong>RDF</strong>-Tripel werden<br />

anhand des Prädikats in Dateien aufgeteilt. Bei der <strong>Auswertung</strong> <strong>von</strong> Anfragen<br />

muss, sofern ein Prädikat gegeben ist, nur die Datei geladen werden, die das Prädikat<br />

enthält. Es wurde versucht diesen Ansatz auf Cassandra zu übertragen, indem man<br />

pro Prädikat einen Keyspace anlegt. Allerdings zeigte sich, dass Cassandra Datenmodell<br />

nicht zur Verwendung vieler Keyspaces ausgelegt ist. So wird der Keyspace<br />

gewöhnlich bei der Initialisierung festgelegt, danach folgen die Anfragen auf diesen<br />

Keyspace. Außerdem müsste zur Beantwortung <strong>von</strong> Anfragen <strong>mit</strong> einer Variable als<br />

20


3.1 Speicherstrategie<br />

Prädikat ein Index gespeichert werden, der alle Keyspace-Namen enthält. Mit dessen<br />

Hilfe würde dann über alle Keyspaces iteriert.<br />

Bei der Implementierung wurde festgestellt, dass die Verteilung der Daten auf die<br />

Knoten im Cluster <strong>mit</strong> Hadoop nicht optimal ist. Die Rechenlast wird nicht ausgeglichen<br />

verteilt, sondern wird nur einem Knoten zugewiesen. Dies liegt an den<br />

ausgewählten Spaltenfamilien, sowie dem frühzeitigen Festlegen des Zeilenschlüssels.<br />

Daher werden für den ersten Verb<strong>und</strong>, bei dem die Daten <strong>von</strong> Hadoop geladen<br />

werden, andere Spaltenfamilien verwendet. Details zur Optimierung werden in<br />

Kap. 4.2.4 erläutert.<br />

21


3.2 <strong>Auswertung</strong>sstrategie<br />

3.2 <strong>Auswertung</strong>sstrategie<br />

Die vorgestellte Implementierung unterstützt Anfragen, die aus Basic Graph Patterns<br />

<strong>mit</strong> bis zu 2 Variablen pro Tripelmuster bestehen. Zur <strong>Auswertung</strong> wird für<br />

jedes konsekutive Paar an Tripelmustern ein Verb<strong>und</strong> (engl. join) über die gemeinsamen<br />

Variablen dieser Tripel durchgeführt. Pro Verb<strong>und</strong> wird ein <strong>MapReduce</strong>-Job<br />

generiert, der die Daten aus Cassandra lädt <strong>und</strong> das Zwischen- oder Endergebnis<br />

auf das HDFS schreibt. Für n Tripel sind also n-1 <strong>MapReduce</strong>-Jobs nötig, um die<br />

Anfrage zu beantworten.<br />

Abb. 3.2 zeigt den Datenfluss der <strong>MapReduce</strong>-Jobs. Der erste der insgesamt n-1 Join-<br />

Jobs lädt den ersten <strong>und</strong> zweiten Datensatz aus Cassandra. Die Zwischenergebnisse<br />

werden auf das HDFS in einem temporären Ordner gespeichert. Der zweite Job<br />

erhält die Zwischenergebnisse vom HDFS, sowie den zweiten Datensatz aus Cassandra.<br />

Der Job speichert die Zwischenergebnisse auf das HDFS. Dieser Vorgang<br />

wiederholt sich bis zum letzten Job, der das Endergebnis auf das HDFS schreibt.<br />

Das Endergbnis enthält alle resultierenden Abbildungen der Variablen auf einen der<br />

Anfrage entsprechenden Wert.<br />

Cassandra<br />

1.Input laden<br />

2.Input laden<br />

2.Input<br />

Join 1 Join 2 Join n-1<br />

...<br />

Zwischenergebnisse<br />

laden<br />

Zwischenergebnisse<br />

speichern<br />

Zwischenergebnisse<br />

speichern<br />

Zwischenergebnisse<br />

laden<br />

Endergebnis<br />

speichern<br />

HDFS<br />

Abbildung 3.2: Job-Sequenz <strong>mit</strong> Datenfluss<br />

22


3.2 <strong>Auswertung</strong>sstrategie<br />

Der Verb<strong>und</strong> wird als sogenannter Map-Side-Join realisiert. Das bedeutet, dass der<br />

Verb<strong>und</strong> innerhalb der Map-Phase des <strong>MapReduce</strong>-Programms bereits abgeschlossen<br />

wird. Bei einem Verb<strong>und</strong> werden Tripel, die sich aus einem linken Tripelmuster<br />

ergeben, <strong>mit</strong> Tripeln, die <strong>von</strong> einem rechten Tripelmuster stammen, verb<strong>und</strong>en. In<br />

der Map-Phase werden die Tripel der linken Seite geladen <strong>und</strong> es werden dynamisch<br />

für jedes dieser Tripel die zu verbindenden Tripel der rechten Seite geladen. Da<br />

auf eine Reduce-Phase bei dieser Methode verzichtet wird, wird die Zeit eingespart,<br />

die sonst nötig wäre, um die Daten zu sortieren <strong>und</strong> nach Schlüssel zu gruppieren.<br />

Schließlich wird auch Netzwerkverkehr eingespart, da die Daten nicht an die Reducer<br />

geschickt werden müssen.<br />

In der Map-Phase wird jeweils über die Daten iteriert, die sich aus dem linken<br />

Tripelmuster ergeben. Es ergibt sich eine Menge an Abbildungen <strong>von</strong> der Menge<br />

der Variablen auf die jeweiligen Werte, die nachfolgend Mapping genannt wird. Für<br />

jedes Mapping werden nun die Daten, die verb<strong>und</strong>en werden sollen, angefragt <strong>und</strong><br />

geladen. Dabei wird ausgenutzt, dass es eine Schnittmenge der Variablen zwischen<br />

den beiden Tripelmustern gibt.<br />

Linkes Tripelmuster:<br />

?inproc rdf:Type bench: Inproceeding<br />

Rechtes TripelMuster:<br />

?inproc dc:creator ?author<br />

Mappings:<br />

Gemeinsame Variable: ?inproc<br />

{?author =persons:Eileen_Heick,<br />

?inproc=inproc:Inproceeding1}<br />

{?author=persons:Paul_Erdoes,<br />

?inproc=inproc:Inproceeding1}<br />

Iteration<br />

{?inproc=inproc:Inproceeding1}<br />

Iteration<br />

{?inproc=inproc:Inproceeding2}<br />

{?inproc=inproc:Inproceeding3}<br />

null<br />

null<br />

{?inproc=inproc:Inproceeding4}<br />

{?author=persons:Gizla_Nordmark,<br />

?inproc=inproc:Inproceeding4}<br />

Iteration<br />

{?author=persons:Eileen_Heick, ?inproc=inproc:Inproceeding1}<br />

{?author=persons:Paul_Erdoes, ?inproc=http:inproc:Inproceeding1}<br />

{?author=persons:Gizla_Nordmark, ?inproc=inproc:Inproceeding4}<br />

Abbildung 3.3: Beispielhafter Verb<strong>und</strong> in der Map-Phase des Jobs<br />

Abb. 3.3 verdeutlicht den Verb<strong>und</strong> anhand eines Beispiels. Die linke <strong>und</strong> rechte Seite<br />

23


3.2 <strong>Auswertung</strong>sstrategie<br />

bilden jeweils Mappings (Abbildungen) der Variablen auf den jeweiligen Wert, der<br />

in der Zeile in Cassandra vorliegt oder vom HDFS geladen wurde, wenn es sich um<br />

ein Zwischenergebnis handelt. Im Beispiel wird über alle möglichen Werte für die<br />

Variable ?inproc iteriert. Durch die vorhandene Schnittmenge der linken <strong>und</strong> rechten<br />

Seite ergeben sich vor dem Zugriff auf die Datenbank Werte für die gemeinsamen<br />

Variablen. Nun wird für die rechte Seite dynamisch auf Cassandra zugegriffen. Dabei<br />

werden die bekannten Schlüssel, die sich aus den gemeinsamen Variablen ergeben,<br />

berücksichtigt. Im Beispiel wird also erkannt, dass ?inproc eine gemeinsame Variable<br />

ist. Folglich gibt es für die rechte Seite nur noch die Variable ?author, da der<br />

jeweilige Wert für ?inproc <strong>von</strong> der linken Seite übernommen wird. Es gibt nun zwei<br />

Möglichkeiten: Entweder erhält man eine Menge an Mappings, über die man nun<br />

iteriert <strong>und</strong> das Kreuzprodukt <strong>mit</strong> dem linken Mapping bildet oder es gibt kein<br />

Mapping für die rechte Seite. Dies ist dann der Fall, wenn die Cassandra-Datenbank<br />

für die definierten Schlüssel keinen Wert liefert. In diesem Fall wird diese Zeile nicht<br />

im Verb<strong>und</strong> berücksichtigt. Im Beispiel liegen also für inproc:Inproceeding2 <strong>und</strong> inproc:Inproceeding3<br />

keine Daten über die Autoren vor, sodass der Verb<strong>und</strong> für diese<br />

Werte nicht berechnet wird. Für die anderen Werte liegen Daten über die Autoren<br />

vor, sodass das Kreuzprodukt berechnet werden kann.<br />

24


4 Implementierung <strong>und</strong><br />

Optimierungen<br />

Insgesamt wurden zwei Anwendungen implementiert. Die erste Anwendung dient<br />

zum Speichern <strong>von</strong> <strong>RDF</strong>-Daten in Cassandra. Sie erhält eine im N3-Format vorliegende<br />

<strong>RDF</strong>-Datei als Eingabe <strong>und</strong> speichert die Daten in die drei Spaltenfamilien<br />

„SPO“, „POS“ <strong>und</strong> „OSP“ (siehe Kap. 3.1). Der Benutzer kann den Keyspace festlegen,<br />

sowie die Anzahl der Tripel, die pro Batch in die Datenbank geschrieben werden,<br />

um die Schreibgeschwindigkeit zu regulieren. Die Anwendung wird auf einem Knoten<br />

des Clusters geladen <strong>und</strong> dort ausgeführt.<br />

Die zweite Anwendung, die der <strong>Auswertung</strong> der SPARQL-BGP Anfragen dient, wird<br />

<strong>mit</strong> Hadoop gestartet <strong>und</strong> läuft verteilt auf dem Cluster. Als Eingabe erhält das<br />

Programm die Anfrage, sowie den Namen des Keyspace, auf dem die <strong>Auswertung</strong><br />

durchgeführt werden soll. Die Ausgabe erfolgt auf dem HDFS. Abb. 4.1 zeigt die<br />

Systemarchitektur. Links ist die Anwendung zur <strong>Auswertung</strong> der SPARQL-BGP-<br />

Anfragen zu sehen, während rechts das Einleseprogramm dargestellt wird.<br />

Beide Anwendungen wurden in der Programmiersprache Java implementiert. Der<br />

Hector Client 1 ermöglicht den Zugriff auf Cassandra in Java-Programmen <strong>und</strong> wird<br />

in beiden Anwendungen verwendet. Das selbsternannte Ziel <strong>von</strong> Hector ist es, dem<br />

Entwickler Funktionen zur Verfügung zu stellen, um einfacher auf Cassandra zuzugreifen.<br />

Dabei greift der Client selbst per Thrift-API auf Cassandra zu <strong>und</strong> ist so<strong>mit</strong><br />

ein Wrapper dieser Funktionen. Die <strong>MapReduce</strong>-Jobs greifen per ColumnFamilyInputFormat<br />

<strong>und</strong> Hector-Client auf die Datenbank zu. Das ColumnFamilyInputFormat<br />

wird vom Cassandra-Projekt zur Verfügung gestellt, um Daten aus Cassandra <strong>mit</strong><br />

<strong>MapReduce</strong> zu verarbeiten.<br />

1 http://prettyprint.me/2010/02/23/hector-a-java-cassandra-client/<br />

25


4.1 Einlesen der Daten<br />

Abbildung 4.1: Systemarchitektur der Implementierung<br />

4.1 Einlesen der Daten<br />

Die Daten liegen im N3-Format auf dem Server vor. Zum Einlesen wurde ein Programm<br />

implementiert, das die Daten <strong>mit</strong> dem Parser des Sesame-Frameworks 2 in<br />

Aussagen unterteilt. Die Präfixe werden vom Parser automatisch erweitert, sodass<br />

die URIs in voller Länge abgespeichert werden. Zum Schreiben der Daten in die<br />

Cassandra-Datenbank wird der Hector-Client eingesetzt. Die Laufzeit wird verbessert,<br />

wenn die Funktion addInsertion statt insert verwendet wird. Auf diese Weise<br />

werden die Daten zunächst als sogenannter Batch gesammelt <strong>und</strong> dann gleichzeitig<br />

in die Datenbank geladen. Batchgrößen zwischen 500 <strong>und</strong> 2000 lieferten gute Ergebnisse.<br />

Die Laufzeit verbessert sich bei größeren Batchgrößen wenig, aber das Risiko<br />

einer OutOfMemory-Exception steigt, da die Daten auf den Heap der Java Virtual<br />

Machine zwischengespeichert werden.<br />

2 http://www.openrdf.org/<br />

26


4.2 Auswerten der Anfragen<br />

4.2 Auswerten der Anfragen<br />

Abb. 4.2 zeigt den Datenfluss der ersten Anwendung. Nach dem Parsen werden aus<br />

den Tripelmustern die <strong>MapReduce</strong> Jobs erzeugt. Anschließend wird die Sequenz<br />

dieser Jobs ausgeführt.<br />

Anfrage<br />

ARQ Parser<br />

●<br />

Parsen der Anfrage<br />

●<br />

Aufteilung in Tripelmuster<br />

Tripelmuster<br />

JobCreator<br />

JoinConfigHelper<br />

●<br />

Erzeugung der <strong>MapReduce</strong>-Jobs<br />

●<br />

Festlegung <strong>von</strong> Spaltenfamilien <strong>und</strong><br />

Prädikaten in der Konfiguration<br />

<strong>MapReduce</strong>-Jobs <strong>mit</strong> Konfigurationen<br />

●<br />

Ausführung der Jobs auf dem<br />

Cluster<br />

●<br />

Zugriff auf Cassandra<br />

●<br />

Verb<strong>und</strong> erfolgt als Map-Side-Join<br />

●<br />

Ausgabe auf HDFS<br />

Ergebnis<br />

Abbildung 4.2: Übersicht des Programmverlaufs<br />

4.2.1 Generieren der Jobs<br />

Die Anfrage wird zuerst an den ARQ-Parser des Jena-Frameworks 3 übergeben <strong>und</strong><br />

wird in Tripelmuster aufgeteilt. Diese werden an die Klasse JobCreator übergeben.<br />

3 http://jena.sourceforge.net/ARQ/<br />

27


4.2 Auswerten der Anfragen<br />

Die Klasse JobCreator erstellt anhand dieser Eingabe eine Liste <strong>von</strong> <strong>MapReduce</strong>-<br />

Jobs <strong>mit</strong> jeweils eigener Konfiguration. Zum Erstellen der Konfiguration wird die<br />

Klasse JoinConfigHelper verwendet, die Funktionen zum Lesen <strong>und</strong> Schreiben der<br />

Programm-Parameter bereitstellt. Für jeden Job wird nun die Eingabe, Ausgabe,<br />

Mapper-Klasse, sowie weitere Parameter, die vom Hector Client benötigt werden,<br />

festgelegt. Die Ausgabe ist eine Liste ausführbarer <strong>MapReduce</strong>-Jobs, die einen im<br />

Folgenden beschriebenen Map-Side-Join durchführen.<br />

4.2.2 Map-Side Join<br />

Der Verb<strong>und</strong> (engl. join), der zur <strong>Auswertung</strong> mehrerer Tripelmuster notwendig ist,<br />

wurde wie in Kap. 3.2 beschrieben als Map-Side-Join implementiert. Die Daten werden<br />

beim ersten Verb<strong>und</strong> per ColumnFamilyInputFormat für Hadoop in die Mapper<br />

geladen. In jedem weiteren Verb<strong>und</strong> wird das FileInputFormat verwendet, um die<br />

Zwischenergebnisse vom HDFS zu laden. Werden die Daten vom HDFS geladen,<br />

liegen sie bereits als Mapping vor. Werden sie aus Cassandra geladen, muss daraus<br />

zunächst ein Mapping erzeugt werden. Zu jedem Verb<strong>und</strong> gibt es ein linkes <strong>und</strong><br />

ein rechtes Tripelmuster. Aus jedem Tripelmuster der Anfrage ergibt sich gemäß<br />

der vorgestellten Speicherstrategie eine Spaltenfamilie. Außerdem werden, je nach<br />

Anzahl der Variablen, Namen für Zeilenschlüssel, Super- <strong>und</strong> Subspalten anhand<br />

der Konfiguration festgelegt. Jeder Mapper lädt die Daten, die lokal auf seinem<br />

Cassandra-Knoten zur Verfügung stehen. Da jeweils zwei Mapper-Klassen, für das<br />

Laden der Daten aus Cassandra oder dem HDFS, benötigt werden, wurde die Durchführung<br />

des Verb<strong>und</strong>s ausgelagert. Die Berechnung des Verb<strong>und</strong>s erfolgt stattdessen<br />

<strong>mit</strong> der Klasse JoinPerformer, um red<strong>und</strong>anten Code zu vermeiden.<br />

4.2.3 Alternativen zum Map-Side Join<br />

Alternativ zum vorgestellten Map-Side Join gibt es Map-Side-Merge-Joins <strong>und</strong> Reduce-<br />

Side-Joins. Im Folgenden werden die Funktionsweisen kurz vorgestellt <strong>und</strong> begründet,<br />

warum eine Implementierung dieser Verbünde nicht möglich war.<br />

Ein Reduce-Side-Join markiert die Daten während der Map-Phase, sodass in der<br />

Reduce-Phase alle Daten so gruppiert sind, dass ein Reducer alle Datensätze hat, die<br />

verb<strong>und</strong>en werden müssen. Um die Daten zu laden, wird die MultipleInputs-Klasse<br />

verwendet, die es erlaubt verschiedene Datenquellen als Input festzulegen <strong>und</strong> ihnen<br />

28


4.2 Auswerten der Anfragen<br />

jeweils einen eigenen Mapper zuzuweisen. Dies funktioniert problemlos, wenn die Daten<br />

vom HDFS <strong>und</strong> Cassandra geladen werden sollen, was ab dem zweiten Join der<br />

Fall ist. Der erste Join, bei dem aus zwei Spaltenfamilien die Daten geladen werden,<br />

bereitet jedoch Probleme. Die Ursache befindet sich hauptsächlich in den Klassen<br />

ColumnFamilyInputformat <strong>und</strong> JoinConfigHelper. Diese Klassen sind so ausgelegt,<br />

dass nur eine Spaltenfamilie <strong>und</strong> SlicePredicate pro Job ausgewählt werden kann. Es<br />

wurde versucht die Klassen um diese Funktionalität zu erweitern. Dies gelang auch<br />

für kleinere Inputdateien. Ab einer bestimmten Größe der Input-Dateien schlugen<br />

die Jobs <strong>mit</strong> einer OutOfMemory-Ausnahme fehl. Eine Implementierung gestaltete<br />

sich so<strong>mit</strong> schwierig <strong>und</strong> konnte aus Zeitgründen nicht fertiggestellt werden, da<br />

tiefgreifendere Änderungen am bereitgestellten Quellcode nötig gewesen wären.<br />

Ein Map-Side-Merge-Join hat viele Gemeinsamkeiten <strong>mit</strong> dem implementierten Map-<br />

Side-Join. Bei einem solchen Verb<strong>und</strong> wird ebenfalls auf die Reduce-Phase verzichtet.<br />

Allerdings wird versucht durch gezieltes Partitionieren <strong>und</strong> Sortieren der Daten<br />

den Verb<strong>und</strong> schon beim Laden der Daten in den Mapper zu sortieren. Es gibt<br />

verschiedene Vorraussetzungen für einen Map-Side-Merge-Join [15]:<br />

1. Beide Datensätze müssen identisch partitioniert <strong>und</strong> sortiert sein. Ist dies der<br />

Fall muss der Verb<strong>und</strong> der ersten Partition des ersten Datensatzes <strong>mit</strong> der<br />

ersten Partition des zweiten Datensatzes berechnet werden. Anschließend wird<br />

der Verb<strong>und</strong> der weiteren Partitionen berechnet bis schließlich der gesamte<br />

Verb<strong>und</strong> berechnet worden ist.<br />

2. Der Typ der Input- <strong>und</strong> Output-Schlüssel muss identisch sein, da sonst die<br />

Daten nicht identisch partitioniert würden.<br />

Das Hadoop-Framework stellt zu diesem Zweck die Klasse CompositeInputformat<br />

bereit. Zunächst trat das gleiche Problem wie beim Reduce-Side-Join auf, da die<br />

Daten aus mehreren Spaltenfamilien geladen werden müssen. Ein weiteres Problem<br />

betraf die Ordnung <strong>und</strong> Partitionierung. Datensätze werden in Cassandra prinzipiell<br />

so geordnet, wie sie geschrieben werden. In der Regel erfolgt also keine Ordnung der<br />

Schlüssel. Verwendet man den OrderPreservingPartitioner, ist zwar eine Ordnung<br />

gegeben, die Daten werden aber dann nicht mehr gleichmäßig im Cluster verteilt.<br />

Außerdem werden nicht alle Datensätze über den Zeilenschlüssel verb<strong>und</strong>en. Insgesamt<br />

wären sowohl tiefgreifendere Änderungen am Quelltext vorzunehmen, als auch<br />

das Speicherverfahren soweit abzuändern, da<strong>mit</strong> die Daten richtig partitioniert würden.<br />

29


4.2 Auswerten der Anfragen<br />

4.2.4 Optimierung für Hadoop<br />

Für den ersten Verb<strong>und</strong> wird das ColumnFamilyInputFormat für Hadoop, das <strong>von</strong><br />

Cassandra zur Verfügung gestellt wird, verwendet. Ist der Cluster richtig konfiguriert,<br />

greift der Mapper auf die Daten zu, die lokal auf seinem Knoten in Cassandra<br />

gespeichert werden. Folglich wird Lokalität ausgenutzt <strong>und</strong> die Daten können verteilt<br />

ausgewertet werden. Es wird am folgenden Beispiel (Listing 4.1) erläutert, warum<br />

das verwendete Nachschlageverfahren für den ersten Verb<strong>und</strong> nicht sinnvoll ist:<br />

ex : Thomas ex : userKnows ? person<br />

Listing 4.1: Tripelmuster einer SPARQL-BGP-Anfrage<br />

Nach dem oben beschriebenen Nachschlageverfahren sollte die ColumnFamily „SPO“<br />

verwendet werden. Zeilenschlüssel <strong>und</strong> Spaltenname sind bekannt <strong>und</strong> es muss nur<br />

über alle Subspalten itereriert werden, die <strong>mit</strong> Daten, die aus einem weiteren Tripel<br />

gewonnen werden, verb<strong>und</strong>en werden. Es ergibt sich jedoch ein entscheidender<br />

Nachteil. Die Daten werden auf die Knoten per Random-Partitioner verteilt, indem<br />

der Zeilenschlüssel verwendet wird. Eine Zeile wird komplett auf einem Knoten<br />

abgespeichert. Je nach Replikationsfaktor gibt es weitere Kopien, die aber bei der<br />

Verwendung des ColumnFamilyInputformat nicht ausreichend verwendet werden.<br />

Da der Schlüssel im Beispiel <strong>mit</strong> “ex:Thomas” festgelegt wurde, werden die Daten<br />

nur auf einem Knoten lokal verfügbar sein <strong>und</strong> auch nur hier verarbeitet. Dies hat<br />

zur Folge, dass nur ein Knoten den Verb<strong>und</strong> ausführt, während die anderen Knoten<br />

schnell ihre Map-Tasks abarbeiten <strong>und</strong> untätig für die weitere Job-Ausführung<br />

bleiben.<br />

Abbildung 4.3: Zustand der Mapper ohne Optimierung (Bildschirmaufnahme)<br />

30


4.2 Auswerten der Anfragen<br />

Abb. 4.3 veranschaulicht diesen Sachverhalt in einem Cluster <strong>mit</strong> 9 Knoten. 8 der 9<br />

Knoten sind nach wenigen Sek<strong>und</strong>en fertig, während der letzte Knoten sehr lange<br />

rechnet. Der Kreis in der Grafik verdeutlicht, dass der 9. Map-Task noch nicht<br />

fertiggestellt worden ist. Bei der <strong>Auswertung</strong> der Anfrage Q3A auf 25 Millionen<br />

Tripel dauerte der Verb<strong>und</strong> 39 Minuten, da eine Teilung des Rechenaufwands nicht<br />

stattfand.<br />

Beim Zugriff auf Cassandra wird die Spaltenfamilie so gewählt, dass der Zeilenschlüssel<br />

festgelegt werden kann. Ändert man das Verfahren so ab, dass der Zeilenschlüssel<br />

nicht festgelegt wird <strong>und</strong> so<strong>mit</strong> eine andere Spaltenfamilie festgelegt wird, werden alle<br />

Knoten am Verb<strong>und</strong> beteiligt. Bei dem obigen Beispiel wird auf die Spaltenfamilie<br />

„OSP“ zugegriffen. Es wird ein SlicePredicate <strong>mit</strong> dem Namen „ex:Thomas“ festgelegt<br />

<strong>und</strong> außerdem ein Subspaltenname „ex:userknows“. Es wird auf allen Knoten<br />

über alle Objekte iteriert.<br />

Abbildung 4.4: Zustand der Mapper nach der Optimierung (Bildschirmaufnahme)<br />

Abb. 4.4 zeigt den Zustand der Mapper wenige Sek<strong>und</strong>en nach Programmstart <strong>und</strong><br />

illustriert die Optimierung. Alle Knoten führen Map-Tasks aus, deren Anzahl kann<br />

außerdem vom ColumnFamilyInputFormat erhöht werden (<strong>von</strong> 9 auf 117). Die Ausführung<br />

verbesserte sich durch diese Optimierung bei 25 Millionen Tripeln <strong>von</strong> 39 auf<br />

2 Minuten. Für die acht möglichen Tripelmuster ergeben sich die Spaltenfamilien,<br />

die in Tab. 4.1 aufgeführt sind.<br />

Enthält ein Tripelmuster zwei Variablen, werden diese auf den Zeilenschlüssel <strong>und</strong><br />

Subspaltenname abgebildet, um ein Slicepredicate definieren zu können. Sonst würden<br />

alle Subspalten geladen werden <strong>und</strong> anschließend gefiltert. Dies liegt daran, dass<br />

es kein SubSlicepredicate gibt. Es ist daher unvorteilhafter einen Zeilenschlüssel <strong>und</strong><br />

31


4.2 Auswerten der Anfragen<br />

Triple Pattern Index<br />

(s, p, o) SPO, POS, OSP<br />

(s, p, ?) OSP<br />

(?, p, o) SPO<br />

(s, ?, o) POS<br />

(?, p, ?) SPO<br />

(s, ?, ?) OSP<br />

(?, ?, o) POS<br />

(?, ?, ?) nicht unterstützt<br />

Tabelle 4.1: Tripelmuster <strong>mit</strong> zu verwendender Spaltenfamilie für ColumnFamily-<br />

Inputformat beim ersten Verb<strong>und</strong><br />

Subspaltennamen zu definieren <strong>und</strong> den Superspaltennamen <strong>und</strong>efiniert zu lassen.<br />

Da die Zwischenergebnisse vom HDFS gelesen werden, sodass eine gute Skalierung<br />

gegeben ist, gilt diese Optimierung nur für den ersten Verb<strong>und</strong>. Alle weiteren Zugriffe<br />

auf Cassandra via Hector Client erfolgen nach dem Schema, das in [14] vorgestellt<br />

wird.<br />

32


5 Evaluation<br />

In diesem Kapitel wird geprüft, wie sich die Implementierung bei unterschiedlichen<br />

Datensätzen verhält. Es soll überprüft werden, ob die Kombination aus Hadoop <strong>und</strong><br />

Cassandra effizient im Hinblick auf die Laufzeit ist. Dafür werden die Laufzeiten verglichen,<br />

die sich bei der Anwendung auf unterschiedlichen Datensatzgrößen ergeben.<br />

Außerdem werden verschiedene Cassandra-spezifische Einstellungen variiert, um deren<br />

Einfluss auf die Laufzeit zu untersuchen. Es werden zunächst die Rahmenbedingen<br />

der Evaluation betrachtet, anschließend werden die Laufzeiten präsentiert <strong>und</strong><br />

interpretiert. Abschließend werden Probleme aufgezeigt, die während der Evaluation<br />

auftraten <strong>und</strong> mögliche Ursachen benannt.<br />

5.1 Allgemeine Rahmenbedingunen<br />

Zur Evaluation wird ein Cluster bestehend aus 10 Servern verwendet. Jeder Server<br />

hat einen Intel Xeon Prozessor 3,16 GHZ, 4GB Arbeitsspeicher <strong>und</strong> 3 TeraByte<br />

Festplattenkapazität. Die einzelnen Server, fortan Knoten genannt, kommunizieren<br />

über ein Gigabit-Lan <strong>mit</strong>einander. Das Betriebssystem ist die 64-Bit Version der<br />

Ubuntu-Distribution in der Version 9.10. Als <strong>MapReduce</strong>-Framework wurde „Cloudera’s<br />

Distribution for Hadoop 3“ (CDH3) 1 verwendet. Java ist in Version 1.6.0_26<br />

installiert. Cassandra ist zur Zeit der <strong>Auswertung</strong> in Version 0.8.2 installiert. Einer<br />

der Knoten fungiert ausschließlich als Jobtracker <strong>und</strong> Namenode <strong>und</strong> ist so<strong>mit</strong> an<br />

der Berechnung nicht direkt beteiligt. Die Datenbank läuft ebenfalls nur auf 9 der<br />

10 Knoten, um Störungen <strong>mit</strong> dem Hadoop Jobtracker zu vermeiden.<br />

Die Gr<strong>und</strong>lage der Evaluation bilden Daten, die <strong>mit</strong> dem Benchmark SP 2 Bench generiert<br />

worden sind. Der Benchmark generiert Semantic Web Daten im <strong>RDF</strong>-Format,<br />

die auf der Verteilung echter Daten basieren. Als Referenz dient die Literaturdatenbank<br />

DBLP. Als Benchmark für SPARQL-Anfragen entwickelt, stellt SP 2 Bench<br />

1 http://www.cloudera.com/hadoop/<br />

33


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

außerdem ausgewählte Anfragen zum Testen bereit [16]. Es wurden insgesamt Daten<br />

zwischen 1 Millionen <strong>und</strong> 50 Millionen Tripel evaluiert. Größere Datensätze<br />

konnten leider aufgr<strong>und</strong> wiederholter Ausfälle des Clusters nicht eingespielt werden.<br />

Die Ausfälle sind höchstwahrscheinlich auf eine suboptimale Konfiguration der Speicherzuweisung<br />

zurückzuführen. Aus zeitlichen Gründen war eine Optimierung der<br />

entsprechenden Konfigurationsparameter leider nicht möglich. Allerdings ist da<strong>von</strong><br />

auszugehen, dass weitere Optimierungen die Ausfälle beheben würden.<br />

5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

Die Zeitmessung erfolgt <strong>mit</strong> dem Java-Befehl System.nanoTime(). Dieser wird als<br />

erster <strong>und</strong> letzter Befehl des Programms aufgerufen. Insgesamt wurden 4 Anfragen<br />

an das System gerichtet, die auf den <strong>von</strong> SP 2 Bench bereitgestellten Anfragen basieren.<br />

Es wurden eine kurze Anfrage (Q10), zwei Anfragen <strong>mit</strong>tlerer Länge (Q1, Q3a)<br />

<strong>und</strong> eine lange Anfrage (Q2) ausgewählt. Bei einigen Anfragen mussten Teile der<br />

Anfrage weggelassen oder umgeschrieben werden, da es sich nicht bei allen Anfragen<br />

um reine BGP-Anfragen handelt. Der Replikationsfaktor der Cassandra-Datenbank<br />

wurde auf 3 gesetzt. Dies entspricht dem Replikationsfaktor des HDFS.<br />

5.2.1 Q1<br />

SELECT *<br />

WHERE {<br />

}<br />

?journal rdf:type bench:Journal .<br />

?journal dc:title "Journal 1 (1940)"^^xsd:string .<br />

?journal dcterms:issued ?yr .<br />

Listing 5.1: Anfrage Q1<br />

Die Anfrage Q1 aus Listing 5.1 besteht aus drei Tripelmustern <strong>und</strong> wird <strong>mit</strong> zwei<br />

Verbünden ausgewertet. Die Laufzeiten (Tab. 5.1, Abb. 5.1) wachsen wie erwartet<br />

annähernd linear für unterschiedliche Datensatzgrößen.<br />

34


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

Q1 1M 5M 10M 25M 50M<br />

Laufzeit 00:00:19 00:00:36 00:00:40 00:01:00 00:01:54<br />

Ergebnisse 1 1 1 1 1<br />

Tabelle 5.1: <strong>Auswertung</strong> der Anfrage Q1<br />

00:02:01<br />

00:01:44<br />

00:01:26<br />

Zeit (hh:mm:ss)<br />

00:01:09<br />

00:00:52<br />

00:00:35<br />

00:00:17<br />

00:00:00<br />

0 10 20 30 40 50 60<br />

<strong>RDF</strong> Tripel( in Millionen)<br />

Abbildung 5.1: <strong>Auswertung</strong> der Anfrage Q1<br />

35


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

5.2.2 Q2<br />

SELECT *<br />

WHERE {<br />

}<br />

?inproc rdf:type bench:Inproceedings.<br />

?inproc dc:creator ?author.<br />

?inproc bench:booktitle ?booktitle.<br />

?inproc dc:title ?title.<br />

?inproc dcterms:partOf ?proc. ?inproc rdfs:seeAlso ?ee.<br />

?inproc swrc:pages ?page. ?inproc foaf:homepage ?url .<br />

?inproc dcterms:issued ?yr.<br />

Listing 5.2: Anfrage Q2<br />

Die Anfrage Q2, die in Listing 5.2 aufgeführt wird, ist die längste Anfrage <strong>und</strong> besteht<br />

aus insgesamt 8 Verbünden. Es handelt sich hierbei nicht um die ursprüngliche<br />

Anfrage <strong>von</strong> SP 2 Bench, da „OPTIONAL“- <strong>und</strong> „ORDER BY“-Befehle weggelassen<br />

werden mussten.<br />

Q2 1M 5M 10M 25M 50M<br />

Laufzeit 00:02:31 00:06:26 00:14:20 00:51:20 01:48:29<br />

Ergebnisse 64679 252754 613729 1896297 4230949<br />

Tabelle 5.2: <strong>Auswertung</strong> der Anfrage Q2<br />

Die Laufzeiten, dargestellt in Tab. 5.2 <strong>und</strong> Abb. 5.2 sind deutlich länger verglichen<br />

<strong>mit</strong> den anderen Anfragen. Auch bei dieser Anfrage kann ein linearer Verlauf der<br />

Laufzeiten festgestellt werden, wobei das Maximum bei 01:48:29 liegt.<br />

5.2.3 Q3A<br />

Die Anfrage Q3A (Listing 5.3) ist ähnlich zur Anfrage Q1 <strong>und</strong> besteht aus einem<br />

Verb<strong>und</strong>. Die Anfrage Q3A, die <strong>von</strong> SP2Bench bereitsgestellt wird, sah einen<br />

„FILTER“-Befehl vor. Dies wurde vermieden, indem der Wert als Prädikat des<br />

zweiten Tripelmusters eingefügt worden ist. Die Laufzeiten werden in Tab. 5.3 <strong>und</strong><br />

Abb. 5.3 tabellarisch <strong>und</strong> graphisch dargestellt. Auch hier ist eine Linearität der<br />

Laufzeit in Abhängigkeit der Datengröße zu erkennen.<br />

36


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

01:55:12<br />

01:40:48<br />

01:26:24<br />

Zeit (hh:mm:ss)<br />

01:12:00<br />

00:57:36<br />

00:43:12<br />

00:28:48<br />

00:14:24<br />

00:00:00<br />

0 10 20 30 40 50 60<br />

<strong>RDF</strong>-Tripel (in Millionen)<br />

Abbildung 5.2: <strong>Auswertung</strong> der Anfrage Q2<br />

SELECT *<br />

WHERE {<br />

}<br />

?article rdf:type bench:Article .<br />

?article swrc:pages ?value .<br />

Listing 5.3: Anfrage Q3A<br />

Q3 1M 5M 10M 25M 50M<br />

Laufzeit 00:00:18 00:00:38 00:00:58 00:02:15 00:03:41<br />

Ergebnisse 52406 193786 323456 598936 929547<br />

Tabelle 5.3: <strong>Auswertung</strong> der Anfrage Q3A<br />

37


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

00:04:19<br />

00:03:36<br />

Zeit (hh:mm:ss)<br />

00:02:53<br />

00:02:10<br />

00:01:26<br />

00:00:43<br />

00:00:00<br />

0 10 20 30 40 50 60<br />

<strong>RDF</strong>-Tripel (in Millionen)<br />

Abbildung 5.3: <strong>Auswertung</strong> der Anfrage Q3A<br />

5.2.4 Q10<br />

SELECT *<br />

WHERE { ?subject ?predicate person:Paul_Erdoes. }<br />

Listing 5.4: Anfrage Q10<br />

Q10 1M 5M 10M 25M 50M<br />

Laufzeit 00:00:12 00:00:43 00:00:59 00:01:55 00:02:45<br />

Ergebnisse 572 656 656 656 656<br />

Tabelle 5.4: <strong>Auswertung</strong> der Anfrage Q10<br />

Q10, dargestellt in Listing 5.4, ist die kleinste der betrachteten Anfragen <strong>und</strong> besteht<br />

lediglich aus einem Tripelmuster. So<strong>mit</strong> werden die Daten in der Map-Phase<br />

eingelesen <strong>und</strong> direkt auf das HDFS geschrieben. Für die ersten beiden Datensätze<br />

lieferte das Programm das richtige Ergebnis <strong>und</strong> terminierte. Bei größeren Datensätzen<br />

blieb die Map-Phase bei 99% stehen <strong>und</strong> wurde nach wenigen Minuten<br />

abgebrochen, jedoch waren die Ergebnisse korrekt. Die in Tab. 5.4 <strong>und</strong> Abb. 5.4 aufgeführten<br />

Laufzeiten beziehen sich bei diesem Datensatz auf die Zeit, bei der das<br />

Programm 99% erreicht hat. Insgesamt ist auch hier eine Linearität der Laufzeit <strong>mit</strong><br />

steigender Datensatzgröße zu erkennen. Allerdings machte sich die Instabilität des<br />

Systems bemerkbar.<br />

38


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

00:03:36<br />

00:02:53<br />

Zeit in hh:mm:ss<br />

00:02:10<br />

00:01:26<br />

00:00:43<br />

00:00:00<br />

0 10 20 30 40 50 60<br />

<strong>RDF</strong>-Tripel (in Millionen)<br />

Abbildung 5.4: <strong>Auswertung</strong> der Anfrage Q10<br />

5.2.5 Einfluss des Konsistenzgrads<br />

Nachdem unterschiedliche Datensatzgrößen betrachtet wurden, wurden unterschiedliche<br />

Konsistenzgrade bei den Anfragen betrachtet. Der Hector Client geht standardmäßig<br />

zum Lesen vom Konsistenzgrad „QUORUM“ aus. Für den größten Datensatz<br />

(50M ) wurden die ersten drei Anfragen <strong>mit</strong> den Konsistenzgraden „ONE“<br />

<strong>und</strong> „ALL“ wiederholt. Die gemessenen Laufzeiten, in Tabelle Tab. 5.5 aufgeführt,<br />

weisen nur geringe Änderungen auf, die vernachlässigt werden können, da sie auf<br />

Schwankungen der Auslastung des Clusters zurückzuführen sind. Folglich scheint<br />

eine Änderung des Konsistenzgrads bei den gewählten Anfragen keinen Einfluss auf<br />

die Laufzeit zu haben.<br />

50M ONE QUORUM ALL<br />

Q1 00:02:05 00:02:05 00:02:04<br />

Q2 01:41:58 01:48:42 01:40:37<br />

Q3A 00:03:48 00:03:31 00:03:51<br />

Tabelle 5.5: Anfragen <strong>mit</strong> unterschiedlichem Konsistenzgrad<br />

39


5.2 Laufzeiten <strong>und</strong> Beobachtungen<br />

5.2.6 Einfluss des Replikationsfaktors<br />

Zunächst wurde versucht, den Replikationsfaktor schrittweise zu erhöhen. Nachdem<br />

der Replikationsfaktor für den Keyspace 10M auf 4 gesetzt wurde, musste nach<br />

Anleitung ein Repair-Vorgang aufgerufen werden. Nach einigen Minuten war der<br />

Repair-Vorgang abgeschlossen, jedoch gaben die Anfragen fehlerhafte Ergebnisse<br />

aus. Es war zu beobachten, dass bei jedem Programmaufruf die Anzahl eingelesener<br />

Rekords schwankte. Auch nach mehreren St<strong>und</strong>en Wartens, einem Cleanup-Vorgang<br />

<strong>und</strong> einem weiteren Repair-Vorgang, gab es keine Verbesserung. Daher wurde ein<br />

weiterer Keyspace <strong>mit</strong> einem Replikationsfaktor <strong>von</strong> 6 erstellt <strong>und</strong> die Daten erneut<br />

eingespielt. Die Map-Tasks griffen wie erwartet auf sechs verschiedene Knoten<br />

zu, um die Daten zu laden. Die Ergebnisse waren korrekt, die Laufzeiten (Abb. 5.5,<br />

Tab. 5.6) zeigten jedoch keine Verbesserung, sondern waren durchschnittlich um ca.<br />

50% erhöht. Da <strong>mit</strong> einem höheren Replikationsfaktor mehr Kopien der Daten im<br />

Cluster vorhanden sind, wurden kürzere Zugriffszeiten <strong>und</strong> so<strong>mit</strong> kürzere Gesamtlaufzeiten<br />

erwartet.<br />

02:52:48<br />

02:24:00<br />

Zeit (hh:mm:ss)<br />

01:55:12<br />

01:26:24<br />

00:57:36<br />

50M RF 3<br />

50M RF 6<br />

00:28:48<br />

00:00:00<br />

Q1 Q2 Q3A<br />

Anfrage<br />

Abbildung 5.5: Vergleich der Laufzeiten bei unterschiedlichem Replikationsfaktor<br />

bei 50 Millionen Tripel<br />

40


5.3 Probleme <strong>und</strong> mögliche Ursachen<br />

Q1 Q2 Q3A<br />

10M RF 3 00:00:40 00:14:20 00:00:58<br />

10M RF 6 00:01:20 00:22:13 00:02:07<br />

25M RF 3 00:01:00 00:51:20 00:02:15<br />

25M RF 6 00:02:51 01:13:56 00:04:11<br />

50M RF 3 00:01:54 01:48:29 00:03:41<br />

50M RF 6 00:05:42 02:35:52 00:07:43<br />

Tabelle 5.6: Anfragen <strong>mit</strong> unterschiedlichem Replikationsfaktor<br />

5.2.7 Datenverteilung<br />

Die Verteilung der Daten erfolgt trotz Verwendung des RandomPartitioner nicht<br />

ganz gleichmäßig. Abb. 5.6 zeigt die neun Knoten, auf denen Cassandra installiert<br />

ist, sowie die Größe der gespeicherten Daten. Die Größen variieren <strong>von</strong> 10,69 GB<br />

bis 16,61 GB.<br />

Abbildung 5.6: Auszug aus der Clusterübersicht (Bildschirmaufnahme)<br />

5.3 Probleme <strong>und</strong> mögliche Ursachen<br />

Bei der Evaluation traten verschiedene Probleme auf, die sowohl das Einlesen als<br />

auch das Auswerten der Daten betrafen. Beim Einlesen der Datensätze <strong>mit</strong> 50 Millionen<br />

Tripeln oder größer war nach einigen St<strong>und</strong>en mehrere Knoten nicht verfügbar<br />

<strong>und</strong> das Einlesen war fehlgeschlagen. Auch nach einem Neustart der Systeme stürzten<br />

einige wieder ab. Erst das Löschen des zum Datensatz gehörenden Keyspace<br />

löste das Problem. Nach Auswerten der log-Dateien wurde ein Überlaufen des Java-<br />

Heap-Space festgestellt. Cassandra schreibt die Daten zunächst in eine Datenstruktur<br />

MemTable, die im Speicher liegt. Erreicht die Tabelle eine in der Konfiguration<br />

41


5.3 Probleme <strong>und</strong> mögliche Ursachen<br />

festgelegte Größe, werden die Daten auf die Festplatte geschrieben. Um die Auslastung<br />

der Datenbank zu senken, wurde die einzulesende Datei zunächst in Dateien<br />

<strong>mit</strong> 250.000 Zeilen Länge aufgeteilt. Die aufgeteilten Dateien wurden <strong>mit</strong> jeweils 3<br />

Minuten Pause zwischen den einzelnen Teilen eingelesen, um der Datenbank mehr<br />

Zeit zum Leeren der internen Datenstrukturen zu geben. Da dies immer noch zu<br />

Problemen führte, wurde das Einleseprogramm durch Einfügen <strong>von</strong> Sleep-Befehlen<br />

verändert, sodass das Einlesen künstlich verlangsamt wurde. Durch diese Änderungen<br />

gelang es, den Datensatz bestehend aus 50 Millionen Tripel einzulesen. Allerdings<br />

war es nicht möglich, größere Datensätze in akzeptabler Zeit einzulesen. Da ein<br />

Absturz immer <strong>mit</strong> dem Neustart des gesamten Clusters verb<strong>und</strong>en war, gestaltete<br />

sich die Problemsuche <strong>und</strong> das Testen verschiedener Einleseparameter schwierig.<br />

42


6 Verwandte Arbeiten<br />

In diesem Kapitel werden Arbeiten <strong>und</strong> Projekte, die <strong>mit</strong> dieser Arbeit verwandt<br />

sind vorgestellt. Dabei werden die Gemeinsamkeiten <strong>und</strong> Unterschiede aufgezeigt.<br />

Das Heart-Projekt 1<br />

(Highly Extensible & Accumulative <strong>RDF</strong> Table) kombiniert<br />

Apache Hadoop <strong>und</strong> die NoSQL-Datenbank HBase zur effizienten Speicherung <strong>und</strong><br />

<strong>Auswertung</strong> <strong>von</strong> <strong>RDF</strong>-Daten. Ähnlich zur der in dieser Arbeit vorgestellten Implementierung<br />

werden die Daten in HBase so abgespeichert, dass Verbünde berechnet<br />

werden können. Das Projekt befindet sich im Anfangsstadium <strong>und</strong> scheint noch keine<br />

Implementierung aufzuweisen.<br />

Die Kombination aus Hadoop <strong>und</strong> HBase lässt sich ebenfalls in [2] wiederfinden.<br />

Die Autoren Sun <strong>und</strong> Jin präsentieren einen skalierbaren <strong>RDF</strong>-Store, der HBase zur<br />

Speicherung der Daten verwendet. Die Daten werden ähnlich zu der in Kap. 3.1 vorgestellten<br />

Speicherstrategie mehrfach abgespeichert, um eine effiziente <strong>Auswertung</strong><br />

aller Tripelmuster zu gewährleisten. Außerdem wird ein Greeedy-Algorithmus zur<br />

<strong>Auswertung</strong> <strong>von</strong> SPARQL Basic Graph Pattern <strong>mit</strong> Hadoop vorgestellt.<br />

Der in [14] beschriebene <strong>RDF</strong>-Triple-Store Cumulus<strong>RDF</strong> speichert <strong>RDF</strong>-Daten in<br />

Cassandra, um effizientes Nachschlagen einzelner Tripelmuster zu gewährleisten . Eine<br />

<strong>Auswertung</strong> komplexerer Anfragen, wie etwa SPARQL-BGP-Anfragen, ist jedoch<br />

nicht möglich. Die Speicherstrategie <strong>von</strong> Cumulus<strong>RDF</strong> wurde in der vorgestellten<br />

Implementierung übernommen <strong>und</strong> zur <strong>Auswertung</strong> angepasst. Eine konkrete Implementierung<br />

des beschrieben <strong>RDF</strong>-Triple-Stores ist vorhanden <strong>und</strong> frei zugänglich 2 .<br />

Ein weiteres Projekt, das sich <strong>mit</strong> der Speicherung <strong>von</strong> <strong>RDF</strong>-Daten <strong>mit</strong> Cassandra<br />

befasst, ist rdf-cassandra 3 . Das Projekt stellt einen StorageAdapter für die Programmiersprache<br />

Ruby bereit. Die Daten werden nur einfach abgespeichert <strong>und</strong> entsprechen<br />

demnach ungefähr dem Schema der Spaltenfamilie „SPO“ in der vorliegenden<br />

1 http://wiki.apache.org/incubator/HeartProposal<br />

2 http://code.google.com/p/cumulusrdf/<br />

3 https://github.com/bendiken/rdf-cassandra<br />

43


Verwandte Arbeiten<br />

Implementierung.<br />

Die <strong>von</strong> dem Unternehmen Datastax entwickelte Hadoop-Distribution Brisk 4 kombiniert<br />

Hadoop <strong>und</strong> Cassandra. Es handelt sich dabei zwar nicht um einen <strong>RDF</strong>-<br />

Triple-Store, jedoch wird auch hier das Potenzial der Kombination beider Technologien<br />

deutlich. Das HDFS wird durch ein auf Cassandra basierendes Dateisystem,<br />

CassandraFS, ersetzt. Die Distribution stellt außerdem die Kernfunktionalitäten<br />

<strong>MapReduce</strong>, Hive, sowie Job- <strong>und</strong> Tasktracker zur Verfügung. Aus der Kombination<br />

ergibt sich die Möglichkeit sowohl Echtzeit-Anwendungen zu realisieren als auch die<br />

zeitintensivere Analyse <strong>mit</strong> <strong>MapReduce</strong> durchzuführen.<br />

4 http://www.datastax.com/products/brisk<br />

44


7 Zusammenfassung<br />

Die vorliegende Arbeit untersuchte, ob eine effiziente Speicherung <strong>und</strong> <strong>Auswertung</strong><br />

<strong>von</strong> <strong>RDF</strong>-Daten <strong>mit</strong> der Kombination aus dem quelloffenen Apache Hadoop Framework<br />

<strong>und</strong> <strong>und</strong> der NoSQL-Datenbank Apache Cassandra möglich ist. Das hierfür<br />

entwickelte System ermöglicht die Speicherung <strong>von</strong> <strong>RDF</strong>-Daten, sowie die <strong>Auswertung</strong><br />

<strong>von</strong> SPARQL Basic Graph Pattern-Anfragen auf diesen Daten.<br />

Um einen schnellen Zugriff auf die gewünschten <strong>RDF</strong>-Daten zu erhalten, werden die<br />

<strong>RDF</strong>-Daten dreimal in verschiedene Spaltenfamilien abgespeichert. Bei der <strong>Auswertung</strong><br />

wird die Anfrage zunächst in Tripelmuster unterteilt. Anschließend wird eine<br />

Sequenz <strong>von</strong> <strong>MapReduce</strong>-Jobs erzeugt, die Verbünde zwischen den Tripelmustern<br />

berechnen. Der Verb<strong>und</strong> selbst ist ein Map-Side-Join, bei dem die Daten während<br />

der Map-Phase für jedes Tripel dynamisch geladen <strong>und</strong> verb<strong>und</strong>en werden. Die Zwischenergebnisse<br />

<strong>und</strong> das Endergebnis werden auf das verteilte Dateisystem HDFS<br />

geschrieben.<br />

Die Evaluation der Anwendung <strong>mit</strong> dem SPARQL-Benchmark SP 2 Bench zeigte einerseits<br />

vielversprechende Laufzeiten der Anwendung auf größeren Datensätze, andererseits<br />

Probleme bei der Ausführung auf dem Cluster.<br />

Zusammenfassend kann die Implementierung als „Proof-of-Concept“ betrachtet werden,<br />

dessen Erkenntnisse bei der Implementierung als Gr<strong>und</strong>lage weiterer Projekte<br />

in diesem Bereich dienen können.<br />

7.1 Ausblick<br />

Über die Implementierung der Anwendung <strong>und</strong> der vorliegenden Arbeit hinausgehend<br />

sollten verschiedene Einstellungen bei der Konfiguration <strong>von</strong> Hadoop <strong>und</strong> Cassandra<br />

betrachtet werden. Insbesondere der parallele Einsatz im Rechner-Cluster<br />

45


7.1 Ausblick<br />

kann weiter untersucht <strong>und</strong> dokumentiert werden. Ebenfalls ist es vorstellbar verschiedene<br />

Joins zu implementieren <strong>und</strong> deren Effizienz zu untersuchen, was aufgr<strong>und</strong><br />

der aufgeführten Li<strong>mit</strong>ierung seitens des ColumnFamilyInputFormat noch<br />

nicht möglich ist. Die Implementierung unterstützt nur einen Teil des SPARQL-<br />

Standards <strong>und</strong> könnte zur Unterstützung des gesamten SPARQL-Standards erweitert<br />

werden. Außerdem hat die Reihenfolge der Tripelmuster einen großen Einfluss<br />

auf die Laufzeit der Verbünde <strong>und</strong> bietet so<strong>mit</strong> Möglichkeiten der Optimierung. Abschließend<br />

könnte das Gesamtsystem <strong>mit</strong> einem ähnlichen System, z.B. auf HBase<br />

aufbauend, verglichen werden, um die Gesamtleistung einordnen zu können.<br />

46


Literaturverzeichnis<br />

[1] T. Berners-Lee, J. Hendler, and O. Lassila, “The Semantic Web (Berners-Lee<br />

et. al 2001),” May 2001.<br />

[2] J. Sun and Q. Jin, “Scalable rdf store based on hbase and mapreduce,” in Advanced<br />

Computer Theory and Engineering (ICACTE), 2010 3rd International<br />

Conference on, vol. 1. IEEE, 2010, pp. V1–633.<br />

[3] N. Shadbolt, W. Hall, and T. Berners-Lee, “The semantic web revisited,” Intelligent<br />

Systems, IEEE, vol. 21, no. 3, pp. 96 –101, jan.-feb. 2006.<br />

[4] P. Hitzler, M. Krötzsch, S. Rudolph, and Y. Sure, Semantic Web: Gr<strong>und</strong>lagen.<br />

Springer, 2008.<br />

[5] F. Manola and E. Miller, Eds., <strong>RDF</strong> Primer, ser. W3C Recommendation.<br />

World Wide Web Consortium, Feb. 2004.<br />

[6] E. Prud’hommeaux and A. Seaborne, Eds., SPARQL Query Language for <strong>RDF</strong>,<br />

ser. W3C Recommendation. World Wide Web Consortium, Jan. 2008.<br />

[7] S. Ghemawat, H. Gobioff, and S.-T. Leung, “The google file system,” in SOSP,<br />

M. L. Scott and L. L. Peterson, Eds. ACM, 2003, pp. 29–43.<br />

[8] J. Dean and S. Ghemawat, “Mapreduce: a flexible data processing tool,” Commun.<br />

ACM, vol. 53, no. 1, pp. 72–77, 2010.<br />

[9] T. White, Hadoop: The Definitive Guide; 2nd rev. ed. O’Reilly, 2011.<br />

[10] “Module 4: Mapreduce,” http://developer.yahoo.com/hadoop/tutorial/<br />

module4.html, Online, letzter Aufruf 03.08.2011.<br />

[11] E. Hewitt, Cassandra: The Definitive Guide. O’Reilly Media, 2010.<br />

[12] R. Cattell, “Scalable sql and nosql data stores,” 2010.<br />

[13] “Cassandra wiki api,” http://wiki.apache.org/cassandra/API/, Online, letzter<br />

Aufruf 03.08.2011.<br />

47


[14] G. Ladwig and A. Harth, “An rdf storage scheme on key-value stores for linked<br />

data publishing,” Tech. Rep., 2010.<br />

[15] J. Lin and C. Dyer, “Data-intensive text processing with mapreduce,” vol. 3,<br />

no. 1, pp. 1–177, 2010.<br />

[16] M. Schmidt, T. Hornung, G. Lausen, and C. Pinkel, “Sp2bench: A sparql performance<br />

benchmark,” 2008.


Erklärung<br />

Hier<strong>mit</strong> erkläre ich, dass ich diese Abschlussarbeit selbständig verfasst habe, keine<br />

anderen als die angegebenen Quellen/Hilfs<strong>mit</strong>tel verwendet habe <strong>und</strong> alle Stellen,<br />

die wörtlich oder sinngemäß aus veröffentlichten Schriften entnommen wurden, als<br />

solche kenntlich gemacht habe. Darüber hinaus erkläre ich, dass diese Abschlussarbeit<br />

nicht, auch nicht auszugsweise, bereits für eine andere Prüfung angefertigt<br />

wurde.<br />

Ort, Datum<br />

Unterschrift

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!