复杂Gremlin查询的调试方法

摘要:Gremlin是图数据库查询使用最普遍的基础查询语言。Gremlin的图灵完备性,使其能够编写非常复杂的查询语句。对于复杂的问题,我们该如何编写一个复杂的查询?以及我们该如何理解已有的复杂查询?本文带你逐步抽丝剥茧,完成复杂查询的调试。

1. Gremlin简介

Gremlin是[Apache TinkerPop](http://tinkerpop.apache.org/) 框架下的图遍历语言。Gremlin是一种函数式数据流语言,可以使得用户使用简洁的方式表述复杂的属性图(property graph)的遍历或查询。每个Gremlin遍历由一系列步骤(可以存在嵌套)组成,每一步都在数据流(data stream)上执行一个原子操作。

Gremlin是一种用于描述属性图中行走的语言。图形遍历分两个步骤进行。  

1.1. 遍历源(TraversalSource)

开始节点选择(Start node selection)。所有遍历都从数据库中选择一组节点开始,这些节点充当图中行走的起点。

Gremlin中的遍历是从TraversalSource开始的。  GraphTraversalSource提供了两种遍历方法。

* GraphTraversalSource.V(Object ... ids):从图形的顶点开始遍历(如果未提供id,则为所有顶点)。

* GraphTraversalSource.E(Object ... ids):从图形的边缘开始遍历(如果未提供id,则为所有边)。

1.2. 图遍历(GraphTraversal)

走图(Walking the graph)。从上一步中选择的节点开始,遍历会沿着图形的边行进,以根据节点和边的属性和类型到达相邻的节点。遍历的最终目标是确定遍历可以到达的所有节点。您可以将图遍历视为子图描述,必须执行该子图描述才能返回节点。

V()和E()的返回类型是GraphTraversal。 GraphTraversal维护许多返回GraphTraversal的方法。GraphTraversal支持功能组合。 GraphTraversal的每种方法都称为一个步骤(step),并且每个步骤都以五种常规方式之一调制(modulates)前一步骤的结果。

1. map:将传入的遍历对象转换为另一个对象(S→E)。

2. flatMap:将传入的遍历对象转换为其他对象的迭代器($S\subseteq E^*$)。

3. filter:允许或禁止遍历器进行下一步(S→S∪∅)。

4. sideEffect:允许遍历器保持不变,但在过程中产生一些计算上的副作用(S↬S)。

5. branch:拆分遍历器并将其发送到遍历中的任意位置(S→{$S1→E^*,…,S_n→E^*$}→E*)。


GraphTraversal中几乎每个步骤都从MapStep,FlatMapStep,FilterStep,SideEffectStep或BranchStep扩展得到。

* 举例:找到makro认识的人

```

gremlin> g.V().has('name','marko').out('knows').values('name') 

==>vadas

==>josh

```


1.3. Gremlin是图灵完备的(Turing Complete)

这也就时说任何复杂的问题,都可以用Gremlin描述。 

下面就调试和编写复杂的gremlin查询,给出指导思路和方法论。

2. 复杂Gremlin查询的调试

Gremlin的查询都是由简单的查询组合成复杂的查询。所以对于复杂Gremlin查询可以分为以下三个步骤,并逐步迭代完成所有语句的验证,此方法同样适用编写复杂的Gremlin查询。

2.1. 迭代步骤

1. 拆分分析步骤,划大为小,逐步求证;

2. 输出分步骤的结果,明确步骤的具体输出内容;

3. 对输出结果进行推导和检验;

扩大分析步骤,回到步骤1继续,直到清楚所有结果。

* **注:此方法参照[Stephen Mallette gremlins-anatomy](https://www.slideshare.net/StephenMallette/gremlins-anatomy-88713465)的分析逻辑和用例。**


2.2. 用例

2.2.1. 图结构

```

gremlin> graph = TinkerGraph.open()

==>tinkergraph[vertices:0 edges:0]

gremlin> g = graph.traversal()

==>graphtraversalsource[tinkergraph[vertices:0 edges:0], standard]

gremlin>g.addV().property('name','alice').as('a').

  addV().property('name','bobby').as('b').

  addV().property('name','cindy').as('c').

  addV().property('name','david').as('d').

  addV().property('name','eliza').as('e').

  addE('rates').from('a').to('b').property('tag','ruby').property('value',9).

  addE('rates').from('b').to('c').property('tag','ruby').property('value',8).

  addE('rates').from('c').to('d').property('tag','ruby').property('value',7).

  addE('rates').from('d').to('e').property('tag','ruby').property('value',6).

  addE('rates').from('e').to('a').property('tag','java').property('value',10).

  iterate()

gremlin> graph

==>tinkergraph[vertices:5 edges:5]

```


2.2.2. 查询语句

```

gremlin>g.V().has('name','alice').as('v').

   repeat(outE().as('e').inV().as('v')).

     until(has('name','alice')).

   store('a').

     by('name').

   store('a').

     by(select(all, 'v').unfold().values('name').fold()).

   store('a').

     by(select(all, 'e').unfold().

        store('x').

          by(union(values('value'), select('x').count(local)).fold()).

        cap('x').

        store('a').by(unfold().limit(local, 1).fold()).unfold().

        sack(assign).by(constant(1d)).

        sack(div).by(union(constant(1d),tail(local, 1)).sum()).

        sack(mult).by(limit(local, 1)).

        sack().sum()).

   cap('a')

==>[alice,[alice,bobby,cindy,david,eliza,alice],[9,8,7,6,10],18.833333333333332]

```

好长,好复杂!

看我如何抽丝剥茧,一步步验证结果。

2.3. 调试过程

1. 拆分查询

按执行步骤,拆分成小的查询,如下图:  


* 执行第一部分步骤

```

gremlin> g.V().has('name','alice').as('v').

......1> repeat(outE().as('e').inV().as('v')).

......2> until(has('name','alice'))

==>v[0]

```

2. 澄清结果

这里通过valueMap()输出节点信息。

```

gremlin> g.V().has('name','alice').as('v').

......1> repeat(outE().as('e').inV().as('v')).

......2> until(has('name','alice')).valueMap()

==>[name:[alice]]

```

3. 验证假设

根据执行语句的语义推导查询过程,如下:


使用path(), 验证推导过程

```

g.V().has('name','alice').as('v').

......1> repeat(outE().as('e').inV().as('v')).

......2> until(has('name','alice')).path().next()

==>v[0]

==>e[10][0-rates->2]

==>v[2]

==>e[11][2-rates->4]

==>v[4]

==>e[12][4-rates->6]

==>v[6]

==>e[13][6-rates->8]

==>v[8]

==>e[14][8-rates->0]

==>v[0]

```

* 输出结果与推导结果一致,扩大查询语句, 回到步骤1;

* 如不一致或不理解结果, 缩小步骤范围, 可以采用此步骤的上一层查询步骤,回到步骤1;

```

gremlin> g.V().has('name','alice').as('v').

......1> repeat(outE().as('e').inV().as('v')).

......2> until(has('name','alice')).

......3> store('a').by('name')

==>v[0]

```

* 如此循环直到完全理解整个查询。 

大家可以自己去细细的剥下笋,**此处略去3000字。**

3. 总结

* 在分析的过程,采用划分查询语句的方法,分步理解,采用漏斗式的方法,逐步扩大对语句的理解;

* 对每步的查询结果,可以采用利用valueMap(), path(), select(), as(), cap() 等函数输出和验证结果;

* 对于不清楚结果的步骤或与期望值不一致,缩小查询步骤,可以采用输出步骤的前一步骤作为输出点,进行输出和验证;

* 对于上一层数据的结果明确的情况下,可以采用inject()方式注入上层输出,继续后续的输出和验证;

* 要注意步骤最后的函数,对整个输出结果的影响。

4. 参考

  * [Introduction to Gremlin](https://tinkerpop.apache.org/gremlin.html)

  * [Gremlin’s Anatomy](https://tinkerpop.apache.org/docs/3.4.10/tutorials/gremlins-anatomy/)

  * [TinkerPop Documentation](http://tinkerpop.apache.org/docs/current/reference/#intro)

  * [Stephen Mallette gremlins-anatomy](https://www.slideshare.net/StephenMallette/gremlins-anatomy-88713465)

  * [Practical Gremlin - Why Graph?](http://kelvinlawrence.net/book/Gremlin-Graph-Guide.html#whygraph)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容