一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法

華為雲開發者社群發表於2021-05-06

1. Gremlin簡介

Gremlin是Apache TinkerPop 框架下的圖遍歷語言。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^*SE∗)。
  3. filter:允許或禁止遍歷器進行下一步(S→S∪∅)。
  4. sideEffect:允許遍歷器保持不變,但在過程中產生一些計算上的副作用(S↬S)。
  5. branch:拆分遍歷器並將其傳送到遍歷中的任意位置(S→{S1→E^*,…,S_n→E^*S1→E∗,…,Sn​→E∗}→E*)。
一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法
  • GraphTraversal中幾乎每個步驟都從MapStep,FlatMapStep,FilterStep,SideEffectStep或BranchStep擴充套件得到。
  • 舉例:找到makro認識的人
gremlin> g.V().has('name','marko').out('knows').values('name') 
==>vadas
==>josh
一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法

1.3. Gremlin是圖靈完備的(Turing Complete)

這也就時說任何複雜的問題,都可以用Gremlin描述。

下面就除錯和編寫複雜的gremlin查詢,給出指導思路和方法論。

2. 複雜Gremlin查詢的除錯

Gremlin的查詢都是由簡單的查詢組合成複雜的查詢。所以對於複雜Gremlin查詢可以分為以下三個步驟,並逐步迭代完成所有語句的驗證,此方法同樣適用編寫複雜的Gremlin查詢。

2.1. 迭代除錯步驟

一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法
  1. 拆分分析步驟,劃大為小,逐步求證;
  2. 輸出分步驟的結果,明確步驟的具體輸出內容;
  3. 對輸出結果進行推導和檢驗。依據結果擴大或縮小分析步驟,回到步驟1繼續,直到清楚所有結果。
  • 注: 此方法參照Stephen Mallette gremlins-anatomy的分析邏輯和用例。

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]
一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法

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. 除錯過程

2.3.1 拆分查詢

按執行步驟,拆分成小的查詢,如下圖:

一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法
  • 執行第一部分步驟
gremlin> g.V().has('name','alice').as('v').
......1> repeat(outE().as('e').inV().as('v')).
......2> until(has('name','alice'))
==>v[0]

2.3.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]]

2.3.3 驗證假設

根據執行語句的語義推導查詢過程,如下:

一文抽絲剝繭帶你掌握複雜Gremlin查詢的除錯方法

使用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
  • Gremlin’s Anatomy
  • TinkerPop Documentation
  • Stephen Mallette gremlins-anatomy
  • Practical Gremlin - Why Graph?

 本文分享自華為雲社群《複雜Gremlin查詢的除錯方法》,原文作者:Uncle_Tom。

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章