Eine Abfragesprache – Gremlin
Zurzeit gibt es keine standardisierten Graphanfragesprachen, die alle Bereiche und Projekte in diesem Bereich abdecken. Jedoch gibt es im RDF- Bereich SPARQL, eine an SQL angelehnte Abfragesprache, die sich vor allem auf die Beschreibung von Beispielteilgraphen konzentriert, um dann passende Mengen von Statements herauszufiltern. Es gibt aber eine ganze Menge von Graphen die nicht RDF-kompatibel sind und eigene, nicht standardisierte Strukturen benutzen, wie z.B. unser Matrix-Graph und andere domänenspezifische Datenmengen. Andere Initiativen setzen auf JSON-basierte Varianten, wie zum Beispiel MQL, die Anfragesprache für Freebase. All diese Sprachen funktionieren nur auf ihren eigenen Datenmengen und bieten keinen oder sehr wenig Unterstützung für die wirklich interessanten Graphalgorithmen und heuristischen Analysemethoden, die für heutige große Graphen erforderlich sind.
Für die komplexeren und interessanteren Fragen, die direkt auf einem Netzwerk aufsetzen (ohne und mit RDF), ist zurzeit die XPath-orientierte Graphenprogrammiersprache Gremlin in der Entwicklung. Über die Schaffung eines generellen Graphenmodells, das alle Features der bestehenden Modelle und Theorien vereinigt, wurde eine Plattform geschaffen, um darunter unterschiedliche Datenmodellierungen einzuhängen. Zurzeit gibt es schon einige von diesen Implementierungen. Sie reichen vom einfachen in-memory TinkerGraph, über die RDF-SAIL-Schnittstelle für AllegroGraph, Sesame und das ThinkerPop LinkedData SAIL (ursprünglich von Josh Shinavier für die Ripple-Programmiersparche) bis hin zu Neo4j.
Gremlins Syntax basiert auf XPath, um auch tiefe Wegbeschreibungen durch den Graphen gut ausdrücken zu können. Viele einfache Fälle sehen damit im Prinzip wie normales XPath aus.
Für das verwendete Matrix-Beispiel könnte eine Gremlin-Sitzung ungefähr so aussehen (nachdem Gremlin installiert und gremlin.sh gestartet wurde):
peterneubauer$ ~/code/gremlin/gremlin.sh\,,,/(o o)-----oOOo-(_)-oOOo-----gremlin> #öffne einen Neo4j Graphen als default ($_g)gremlin> $_g := neo4j:open('tmp/matrix')==>neo4jgraph[tmp/matrix]gremlin> #Die Knoten mit Eigenschaftengremlin> $neo := g:add-v(g:map('name','Neo'))==>v[1]gremlin> $morpheus := g:add-v(g:map('name','Morpheus'))==>v[2]gremlin> $trinity := g:add-v(g:map('name','Trinity'))==>v[3]gremlin> $cypher := g:add-v(g:map('name','Cypher'))==>v[4]gremlin> $smith := g:add-v(g:map('name','Agent Smith'))==>v[5]gremlin> $architect := g:add-v(g:map('name','The Architect'))==>v[6]gremlin> #Die Kantengremlin> g:list($cypher,$neo,$trinity)[g:add-e($morpheus,'KNOWS',.)]==>v[4]==>v[1]==>v[3]gremlin> g:add-e($cypher,'KNOWS',$smith)==>e[3][4-KNOWS->5]gremlin> g:add-e($trinity,'LOVES',$neo)==>e[4][3-LOVES->1]gremlin> g:add-e($architect,'HAS_CODED',$smith)==>e[5][6-HAS_CODED->5]gremlin> #Setze Neo als Startknoten ($_) über eine Volltextsuchegremlin> $_ := g:key('name','Neo')==>v[1]gremlin> #ist das auch Neo?gremlin> ./@name==>Neogremlin> #Wie sehen die Kanten aus?gremlin> ./bothE==>e[0][1-KNOWS->2]==>e[4][3-LOVES->1]gremlin> #Nimm nur die KNOWS-Kantengremlin> ./bothE[@label='KNOWS']==>e[0][1-KNOWS->2]gremlin> #Die Namen von Neo's Freundengremlin> ./bothE[@label='KNOWS']/inV/@name==>Morpheusgremlin>gremlin> #Die Freunde der Freunde, 2 Schrittegremlin> repeat 2$_ := ./outE[@label='KNOWS']/inVend==>v[4]==>v[3]gremlin> #Und deren Namen?gremlin> ./@name==>Cypher==>Trinitygremlin> #Alle Knoten im Graphen mit einer ausgehenden LOVES Kantegremlin> $_g/V/outE[@label='LOVES']/../@name==>Trinity
Tiefe Algorithmen – Value in Relationships
Das vorstehende Beispiel ist ein sehr naives Szenario. Interessanter wird es bei Algorithmen über große Graphen. Algorithmen wie Eigenvector Centrality und Dijkstra sind hier nicht brauchbar, da sie jeden Knoten im Graphen berücksichtigen müssen. Hier kommen heuristische Konzepte wie Grammar-based Random Walkers und Spreading Activation ins Spiel (Ein guter Artikel dazu von Marko Rodriguez, dem Author von Gremlin, hier). Der Google-PageRank-Algorithmus ist eine solche Heuristik und wird in Gremlin ungefähr so modelliert (hier als Beispiel über den Graphen aller Lieder, Konzerte und Platten der Gruppe Greatful Dead, mit 2500 Schleifen und einem Energieverlust von 15 % in jeder Schleife):
$_g := tg:open()g:load('data/graph-example-2.xml')$m := g:map()$_ := g:key('type', 'song')[g:rand-nat()]repeat 2500$_ := ./outE[@label='followed_by'][g:rand-nat()]/inVif count($_) > 0g:op-value('+',$m,$_[1]/@name, 1.0)endif g:rand-real() > 0.85 or count($_) = 0$_ := g:key('type', 'song')[g:rand-nat()]endendg:sort($m,'value',true())
Was uns die folgenden gewichteten Songs gibt:
==>DRUMS=44.0==>PLAYING IN THE BAND=38.0==>ME AND MY UNCLE=32.0==>TRUCKING=31.0==>CUMBERLAND BLUES=29.0==>PROMISED LAND=29.0==>THE MUSIC NEVER STOPPED=29.0==>CASSIDY=26.0==>DARK STAR=26.0==>NOT FADE AWAY=26.0==>CHINA CAT SUNFLOWER=25.0==>JACK STRAW=25.0==>TOUCH OF GREY=24.0==>BEAT IT ON DOWN THE LINE=23.0==>BERTHA=23.0
Wie man sieht, spielt Gremlin seine Stärken bei tieferen Analysen aus. Ein anderes interessantes Beispiel, bei dem die unterliegenden Daten direkt aus dem Internet als alleinige Datenquelle gezogen werden, ist ein Empfehlungsalgorithmus für Musik über LinkedData und DBPedia.
Fazit und Ausblick
Sollte dies nicht reichen, kann man natürlich immer der Domäne angepasste Sharding-Grenzen anwenden, ohne deshalb die harten Konzepte von Key Values oder Dokumenten einführen zu müssen. Ob das in einem z. B. Dokumentenmodell resultiert oder man eine "Objektdatenbank" für seine Domäne baut, richtet sich nach dem Anwendungsfall.
Der Code für diesen Artikel liegt hier
Für das ausgezeichnete Feedback und die hilfreichen Hinweise während des Entstehens dieses Artikels möchte ich mich besonders bei Michael Hunger bedanken.




