天天看點

十的次方 - 第一部分

這篇文章最初由Stephen Mallette和Daniel Kuppitz在Aurelius發表。

“不,不!先開始冒險吧,”獅鹫不耐煩地說道,“解釋起來需要太多的時間。” - 劉易斯卡羅爾 - 愛麗絲夢遊仙境

設想使用Titan的好處往往很簡單。對擁有數十億條邊的分布圖進行複雜的圖分析像是有待進行的冒險。就像劉易斯卡羅爾的故事中的獅鹫一樣,我們對立刻進行這場冒險有着強烈的欲望。很明顯但又有些遺憾的是,Titan的優勢直到其中存有一些資料時才能顯現。考慮下面的解釋 ; 他們是将資料大量加載到Titan的政策,使冒險成為可能。

十的次方 - 第一部分

各種不同的變量可能會影響将資料加載到圖中的方法,但為決策提供最重要指導的屬性是大小。就本文而言,“大小”是指要加載到圖中的估計邊數。用于加載資料的政策傾向于以10的幂次改變,其中用于加載100萬條邊的政策與用于1000萬條邊的不同。

鑒于批量加載政策分類的整潔和令人難忘的方式,這篇由兩部分組成的文章概述了每個政策從100萬或更少的最小值開始,并繼續保持10到10億或更多的權限。第一部分将重點介紹100萬和1000萬條邊緣,涉及一些Gremlin的常見操作。第二部分将重點關注1億和10億個邊緣,将涉及到Faunus的基本使用。

100萬

十的次方 - 第一部分

在數百萬以及更少的邊的範圍内,确實沒有特别的加載政策可以遵循,因為圖可以完全載入記憶體,加載時間也相當快。這種規模的即便發生錯誤,其解決成本也不會太高,因為問題通常很容易診斷,并且我們可以重新加載圖,而不必等待太多時間。

正如之前發表的一篇名為Polyglot Persistence and Query with Gremlin的部落格文章所解釋的,Gremlin REPL是一個處理任何類型資料的靈活環境。很明顯,它提供了像Titan這樣的圖形資料庫的通路,但是在同一個REPL會話中,也可以連接配接到關系資料庫,接觸到Web服務,讀取檔案等。有了這個功能,編寫Gremlin腳本可以通過REPL執行的操作可能是将資料導入圖的最輕量級和直接的方式。

十的次方 - 第一部分

維基選票網站(包含了維基百科從2008年1月成立之初至今所有的維基百科投票資料,網絡中的頂點代表了維基的使用者,其中由箭頭線連接配接的頂點i至j代表了使用者i給使用者j的投票)。在其基本制表符分隔的資料結構中,包含7,115個頂點和103,689條邊,這是我們示範的合适的大小。

在開始我們的示例之前,我們需要下載下傳并解壓最新版本的Titan(titan-all包)。接着,我們在Titan的根目錄下載下傳并解壓維基百科選票網站資料集:

$ curl -L -O http://snap.stanford.edu/data/wiki-Vote.txt.gz
$ gunzip wiki-Vote.txt.gz           

複制

解壓後将在Titan根目錄下得到

wiki-Vote.txt

檔案。下面的Gremlin腳本示範了如何将該檔案加載到Titan中(由BerkleyDB支援):

g = TitanFactory.open('/tmp/1m')
g.makeKey('userId').dataType(String.class).indexed(Vertex.class).unique().make()
g.makeLabel('votesFor').make()
g.commit()
getOrCreate = { id ->
defp = g.V('userId', id)
if(p.hasNext()) ? p.next() : g.addVertex([userId:id])
}
newFile('wiki-Vote.txt').eachLine{
if(!it.startsWith("#")){
(fromVertex, toVertex) = it.split('\t').collect(getOrCreate)
fromVertex.addEdge('votesFor', toVertex)
}
}
g.commit()           

複制

資料加載腳本要注意的關鍵部分如下:

  • g.makeKey(‘userId’)…

    - 首先在Titan中建立類型。在這種情況下,表中将隻包含存在于每個使用者頂點的

    userId

    。始終在類型建立結束時以及在将資料加載到圖形執行個體之前進行送出。
  • getOrCreate = { id ->...

    - 将頂點辨別符(即userId)作為參數并執行索引查找以确定頂點是否已存在的輔助函數。如果存在,則傳回頂點,但如果它不存在,則會建立該頂點。

    getOrCreate

    是一個常見的概念,并且它是執行此類任務時所必需的、高效的函數。
  • new File('wiki-Vote.txt').eachLine {

    - 逐行讀取源資料檔案,并對每個檔案執行提供的閉包。
  • if (!it.startsWith("#")){

    - 該檔案包含由#辨別開頭的注釋行。這些行應該被忽略。
  • (fromVertex, toVertex) = it.split('\t').collect(getOrCreate)

    - 每行由一對制表符分隔的值組成。此代碼将頁籤上的文本行分割以建立包含兩個

    userID

    值的清單。

    collect

    函數将處理

    getOrCreate

    所得到的值,然後将所得清單解構到的兩個頂點變量存入已經存在或以其它方式被新建立的圖中:

    fromVertex

    toVertex

  • fromVertex.addEdge('votesFor', toVertex)

    - 構造兩個頂點之間的邊。
  • g.commit()

    - 值得注意的是,這個加載是在單個事務的上下文中執行的。在處理100萬條邊或更多時,我們有必要在過程中執行中間送出。

要執行此腳本,請将其複制到Titan安裝目錄根目錄下的檔案中。請注意,該腳本将在檔案系統上生成Titan資料庫。開始Gremlin 。當REPL初始化後執行腳本如下:

load-1m.groovy/tmp/1mbin/gremlin.sh

$ bin/gremlin.sh
\,,,/
(o o)
-----oOOo-(_)-oOOo-----
gremlin> \. load-1m.groovy
==>titangraph[local:/tmp/1m]
==>userId
...
==>null
gremlin> g.V.count()
==>7115
gremlin> g.E.count()
==>103689           

複制

維基選票網站的圖表模式比較簡單。即使是100萬條邊的規模,複雜性也僅僅來自批量加載腳本。本節中的加載腳本提供了一個良好的架構,我們可以在其上實作更加複雜的加載。

1000萬

十的次方 - 第一部分

加載數千萬條邊的方法與上一節沒有太大差別。Gremlin腳本仍然是最直接的加載方法,但是需要考慮一些差異。這些差異中最重要的是

BatchGraph

的使用,它在指定的時間間隔處理事務的中間送出,并維護頂點緩存以便快速檢索。有關其使用限制的重要資訊,請參閱

BatchGraph

文檔。

該DocGraph資料集“展示了醫療保健提供者團隊如何提供護理”。該網絡中的頂點代表醫療服務提供者,它們由NPI number辨別。邊表示兩個提供者之間的共享互動,其中三個屬性進一步限定了該互動。資料根據時間視窗分成幾種尺寸。本節将利用“30天資訊視窗”,其中包含大約100萬個頂點和7300萬條邊。

十的次方 - 第一部分

從Titan的根目錄下載下傳并解包DocGraph資料集:

$ curl -L -O http://bit.ly/DocGraph2012-2013-Days30zip
$ unzip DocGraph2012-2013-Days30zip && rm DocGraph2012-2013-Days30zip
$ head -n3 DocGraph-2012-2013-Days30.csv
$ sort DocGraph-2012-2013-Days30.csv > DocGraph-2012-2013-Days30-sorted.csv           

複制

解壓縮存檔将在Titan目錄的根目錄下建立

DocGraph-2012-2013-Days30.csv

。與上一節中的情況不同,資料是按每條邊外頂點的NPI number預先分類的。對資料進行預先排序有助于提高

BatchGraph

的性能,因為緩存的寫入和重新整理次數會減少。下面的Gremlin腳本示範了如何将該檔案加載到Titan中(由BerkleyDB支援):

conf = newBaseConfiguration() {{
setProperty("storage.backend", "berkeleyje")
setProperty("storage.directory", "/tmp/10m")
setProperty("storage.batch-loading", true)
}}
g = TitanFactory.open(conf)
g.makeKey("npi").dataType(String.class).single().unique().indexed(Vertex.class).make()
sharedTransactionCount = g.makeKey("sharedTxCount").dataType(Integer.class).make()
patientTotal = g.makeKey("patientTotal").dataType(Integer.class).make()
sameDayTotal = g.makeKey("sameDayTotal").dataType(Integer.class).make()
g.makeLabel("shares").signature(sharedTransactionCount, patientTotal, sameDayTotal).make()
g.commit()
bg = newBatchGraph(g, VertexIDType.STRING, 10000)
bg.setVertexIdKey("npi")
c = 0L
newFile("DocGraph-2012-2013-Days30-sorted.csv").eachLine({ final String line ->
def(id1,
id2,
sharedTransactionCount,
patientTotal,
sameDayTotal) = line.split(',')*.trim()
defv1 = bg.getVertex(id1) ?: bg.addVertex(id1)
defv2 = bg.getVertex(id2) ?: bg.addVertex(id2)
defedge = bg.addEdge(null, v1, v2, "shares")
edge.setProperty("sharedTxCount", sharedTransactionCount asInteger)
edge.setProperty("patientTotal", patientTotal asInteger)
edge.setProperty("sameDayTotal", sameDayTotal asInteger)
if(++c%100000L == 0L) println"Processed ${c} edges"
})
bg.commit()           

複制

下面是對這個腳本的解析(它可以在Gremlin REPL中按照前一節提供的說明執行):

  • setProperty("storage.batch-loading", true)

    - 為Titan啟用“批量加載”将通過禁用一緻性檢查和鎖定來幫助提高性能。閱讀更多關于此選項以及可能影響批量加載的其他設定,請參閱Titan 文檔。
  • g.makeKey("npi")...

    - 正如前面的100萬邊緣規模的例子,首先應該建立并送出類型。
  • bg = new BatchGraph(g, VertexIDType.STRING, 10000)

    - 将

    TitanGraph

    執行個體包裝入

    BatchGraph

    ,定義辨別符的資料類型,在這種情況下,對于NPI number是

    STRING

    ,并将事務大小設定為

    10000

    。使用此設定,

    BatchGraph

    将自動将每10,000次突變事務送出到圖表。
  • bg.setVertexIdKey("npi")

    - 告訴

    BatchGraph

    頂點辨別符将被存儲在一個叫做

    npi

    的頂點屬性鍵中。
  • ...sameDayTotal) = line.split(',')*.trim()

    - 檔案中的每一行由一對逗号分隔的值組成。該行将逗号分隔的文本行建立一個清單,其中包含解構為五個變量的五個值。
  • def v1 = bg.getVertex(id1) ?: bg.addVertex(id1)

    -

    BatchGraph

    有助于簡化上一節中的

    getOrCreate

    功能。

    BatchGraph

    覆寫預設

    addVertex

    getVertex

    功能并允許通過NPI number進行規範和查找頂點。如果沒有找到頂點,

    getVertex

    将傳回

    null

    并添加頂點。
  • bg.commit()

    - 完成加載後,進行最後的

    commit

    調用以完成事務緩沖區中的所有剩餘元素。
十的次方 - 第一部分

DocGraph示例示範了加載數千萬條邊的關鍵政策,總結如下:盡可能預處理資料以減輕加載負擔并提高性能,使用

BatchGraph

以便專注于所加載的資料,而不是加載機制,例如手動批量送出,處理辨別符和編寫

getOrCreate

方法。

在這個規模上要考慮的其他一些政策和想法包括:

  • 使用資料子集程式設計和測試加載腳本以縮短開發周期時間。
  • 使用第三方庫來提高工作效率并減少要編寫的代碼量(例如groovycsv)。
  • 如果資料可以組織起來的,并且條件允許的話,可以考慮一下使用gpars進行并行加載的方法。
  • 如果有傾向于從非JVM語言(如Python)加載資料,可以理清本文思路并在Gremlin中編寫加載腳本。通過這種方式,加載資料的過程可以快速完成,進而可以專注于針對Python應用程式開發的語言特定工具(例如Bulbs)。

千萬級規模圖的處理已經放在了“ Boutique Graph Data ” ,它對于許多應用程式來說是通用的,其中大多數應用程式可以直接啟用它。從某種意義上說,這可能是最接近“ 十的次方 ”的規模的應用之一。

結論

本文探讨了向Titan加載較少的資料的情況。在數百萬和數千萬條邊的規模上,我們通常需要Gremlin腳本和REPL來批量加載活動。對于那些剛剛開始使用TinkerPop和Titan的人來說,需要掌握最基本的堆棧知識。适應這種規模的操作非常有助于我們成功探讨本系列下一章将要探讨的數億甚至數十億規模的圖。

緻謝

Vadas Gintautas博士最初預見到需要更好地記錄批量裝載政策,并且這樣的政策似乎很好地将自己分成十的次方。

這篇文章最初由Stephen Mallette和Daniel Kuppitz在Aurelius發表。