Graphdatenbanken in der Praxis

Abfragen, Datenmodelle und Skalierbarkeit

© iStock/anyaberkut

25.03.2021

Die große Stärke von Graphdatenbanken – das sind Datenbanken, die anhand von Graphen vernetzte Informationen in Form von Knoten und Kanten in Verbindung bringen und speichern – ist die umfangreiche Abbildung von Beziehungen zwischen Datenpunkten. Dies ermöglicht eine intuitive Abbildung vieler Real-World-Szenarien, die gerade während der letzten Jahre stark an Bedeutung gewonnen haben. Zusätzlich ermöglichen es Graphdatenbanken, dass sich auch die Abfragen eng an die modellierte Realität anlehnen, wie in diesem Artikel beispielhaft gezeigt wird. 

Beispiel: IT-Systemüberwachung

Der hier gezeigte beispielhafte Graph stellt die Abhängigkeiten zwischen den Diensten einer IT-Infrastruktur dar. Dazu werden Serverprozesse als Graphknoten modelliert, wobei die Prozesse durch Kanten verknüpft sind, die Anfragen darstellen. Die Kanten sagen damit aus, dass jeder Knoten alle seine Nachfolger entlang der Kanten benötigt, um zu funktionieren. Aus dem Beispiel ist auch direkt ersichtlich, dass es eine Graphdatenbank ermöglicht, das Datenmodell sehr nahe an der Problemdomäne zu modellieren und so das Datenmodell intuitiv verständlich zu machen.

Graphdatenbanken und Abfragesprachen

Im Bereich der Graphdatenbanken gibt es neben Angeboten unter rein kommerzieller Lizenz (z.B. TigerGraph, Amazon Neptune) auch zahlreiche mit Open-Source-Lizenzen (z.B. Neo4j, ArrangoDB, OrientDB, JanusGraph), für die teilweise auch kommerzielle Lizenzen angeboten werden. Die am meisten verbreiteten Graphdatenbank Neo4j wird beispielsweise unter einer kommerziellen sowie auch einer Open-Source-Lizenz angeboten, wobei allerdings der Einsatz der Open-Source Variante auf einen einzelnen Server beschränkt ist.

Ein weiteres Unterscheidungsmerkmal ist die verwendete Abfragesprache wobei hier vor allem Cypher und Apache Tinkerpop Gremlin zu nennen sind. Während Cypher vor allem von der Graphdatenbanklösung Neo4J eingesetzt wird, ist Gremlin aus dem Apache Tinkerpop Projekt hervorgegangen, und wird von zahlreichen Graphdatenbanken wie beispielsweise Amazon Neptune oder JanusGraph unterstützt. Da die beiden Sprachen funktional äquivalent und automatisiert ineinander überführbar sind, stellt die Kenntnis nur einer der beiden Sprachen allerdings keinen limitierenden Faktor bei der Wahl des Datenbanksystems dar.

IT-Systemüberwachung: Gremlin-Abfragen

Grundsätzlich wird zwischen lokalen und globalen Graph-Abfragen unterschieden. Während globale Abfragen den gesamten Graphen einbeziehen, sind lokale Abfragen auf einen meist kleinen Teilbereich des Graphen beschränkt. Daher sind auch globale Queries bei großen Graphen laufzeitkritisch.

Lokale Abfragen

Ein einfaches Beispiel für eine lokale Graph-Abfrage im oben abgebildeten Graph wäre: Auf welche Serverprozesse werden die Anfragen vom Load-Balancer verteilt?
Folgende Abfrage in Gremlin beantwortet diese Frage:

gremlin> g.V().has('name', unfold().is('loadbalancer')).out('request').values('name')
==>webserver1
==>ftp-server
==>webserver0

Gremlin-Abfragen folgen dem Modell einer Graph-Traversierung. Hierbei wird ausgehend von einer Menge an Knoten, die auch nach Typ oder gewissen Attributwerten vorgefiltert werden können, der Graph traversiert, indem adjazenten Kanten gefolgt wird.

Dieser Teil der Abfrage besagt, dass der Knoten mit dem Attributschlüssel name und dem Attributwert loadbalancer als Startpunkt für die Traversierung g dient:

g.V().has('name', unfold().is('loadbalancer')) ...

Ausgehend vom loadbalancer Knoten wird nun der Wert des Attributs name für alle entlang von Kanten des Typs request adjazenten Knoten ausgegeben:

g.V().has('name', unfold().is('loadbalancer')).out('request').values('name')

Diese Abfrage ist lokal, daher hängt die Antwortzeit – wenn der Startknoten bekannt ist – nicht von der Größe des Graphen ab.

Globale Abfragen

Der Graph kann außerdem dazu eingesetzt werden, zu beurteilen, wie kritisch der Ausfall einer Komponente für das Gesamtsystem ist. Man kann nun beispielsweise eine Abfrage formulieren, die angibt wie viele andere Prozesse einen Prozess benötigen. Dazu werden alle Pfade vom Load-Balancer aus berücksichtigt. Die zugehörige Gremlin-Abfrage lautet wie folgt:

gremlin> g.V().map(union(values('name'),repeat(_.in()).until(inE().count().is(0)).emit().dedup().count()).fold())
==>[loadbalancer,0]
==>[webserver0,1]
==>[webserver1,1]
==>[tomcat,3]
==>[database,4]
==>[ftpserver,1]

Dieser Teil der Abfrage besagt, dass die Menge aller Knoten als Startpunkt für die Traversierung g dient:

g.V()

Hier wird als Ergebnis eine Map erstellt, die als Schlüssel den Wert des Attributs name entält, was der Lesbarkeit des Ergebnisses dient.

g.V().map(union(values('name'), ...)

Hier wird für jeden Knoten über alle eingehenden Kanten (_.in()) iteriert bis der angetroffene Knoten keine eingehenden Kanten (inE()) mehr aufweist. Für jede dieser Schleifen werden die angetroffenen Knoten nach Entfernung der Duplikate gezählt (.emit().dedup().count()) und in einer Liste zusammengefasst (.fold()):

... repeat(_.in()).until(inE().count().is(0)).emit().dedup().count()).fold() ...

Dies ist ein Beispiel für eine globale Abfrage die über den gesamten Graphen iteriert, womit ihre Laufzeit im Gegensatz zu lokalen Queries von der Größe des Graphen abhängt.

Datenmodellierung: Abbildung der Realität vs. Abfrageperformance

Die Datenmodellierung weist für Graphdatenbanken die Vorteile auf, dass das Datenmodell eng an der Realität entworfen werden und flexibel erweitert werden kann. Andererseits hat die Struktur des Graphen auch direkten Einfluss auf die Effizienz von Abfragen, da über Verlinkungen Knoten schneller erreicht werden können. Ein guter Ansatz ist daher mit dem Entwurf des Datenmodells nahe an der Realität zu starten, und im Fall von Performanceproblemen bei Abfragen den Graphen entsprechend um zusätzliche Knoten und Kanten zu erweitern. Dadurch wird einerseits die Abbildung der Realität – und damit das Verständnis des Datenmodells – im Kern nicht beeinträchtigt, andererseits können zum Beispiel neue Abfragen effizient behandelt werden.

Performanz und Skalierbarkeit

Vergleichbar mit relationalen Datenbanksystemen bieten auch Graphdatenbanken Möglichkeiten der Indizierung von Knoten oder Kanten an. Somit ist es möglich über einen Index schnell auf alle Knoten zuzugreifen, bei denen eine Menge an Attributen als Schlüssel verwendet werden kann. Diese Ergebnismenge kann in der Folge als Startpunkt für eine Graphtraversierung verwendet werden. Zusätzlich zu den Möglichkeiten der Indizierung ist die horizontale Skalierbarkeit der Graphdatenbank ein Schlüsselkriterium, um bei einer stetig wachsenden Datenmenge nicht in Probleme mit der Abfrage-Performance zu laufen.

Als lizenzkostenfreie Lösung für eine skalierbare Graphdatenbank bietet sich zum Beispiel die Open-Source Datenbank Janusgraph an, mit der unter anderem ein Hadoop-Cluster als Speicherbackend eingesetzt werden kann. Darüber hinaus kann Apache Spark als Graph Compute Engine eingesetzt werden, um die Ausführung globaler Queries in Graphen bis in den Terrabytebereich managebar zu machen.

>> Lesen Sie mehr.

Autor

DI Paul Heinzlreiter ist Senior Data Engineer in der Abteilung Logistics Informatics der RISC Software GmbH.

Kontakt

RISC Software GmbH
Softwarepark 32a
4232 Hagenberg
www.risc-software.at


Das könnte Sie auch interessieren: