圖數(shù)據(jù)庫的性能和 schema 的設(shè)計息息相關(guān),但是 Nebula Graph 官方本身對圖 schema 的設(shè)計其實(shí)沒有一個定論,唯一的共識就是面向性能去做 schema 設(shè)計。?
背景知識
先來講解下存儲背景,再講 schema 設(shè)計中會遇到的問題,最后講下實(shí)踐過程中我們能達(dá)成一致的最佳實(shí)踐。
在使用圖數(shù)據(jù)庫之前,先了解下圖數(shù)據(jù)庫這個 NoSQL 數(shù)據(jù)庫同關(guān)系型數(shù)據(jù)庫不一樣的地方。
關(guān)系型數(shù)據(jù)庫存儲結(jié)構(gòu)
以上圖為例,存一個 ID 作為一個主鍵,然后它有個特征 k,我們對 k 創(chuàng)建索引進(jìn)行查詢,對于左下角這份列表數(shù)據(jù),內(nèi)存中存儲的話,會以一個 B+ 樹進(jìn)行存儲(上圖右側(cè)):一個主索引 ID 和一個從索引 k。舉個例子,現(xiàn)在我們要查詢 k=3 的數(shù)據(jù),它就先查詢 ID=100 然后經(jīng)過回表后回到具體的值。
這體現(xiàn)了關(guān)系型數(shù)據(jù)庫的一個特點(diǎn),如果你要查詢速度快,那就需要創(chuàng)建一個索引。假如你不創(chuàng)建索引,那數(shù)據(jù)庫就會掃全表。
我們再來看下寫過程。數(shù)據(jù)一般先寫到內(nèi)存 Mem(這是一個常規(guī)的優(yōu)化減小磁盤壓力),寫到一定程度再同步到磁盤中,這個過程我們叫原位刷盤,刷盤就是說找到這個地方的數(shù)據(jù),然后修改掉數(shù)據(jù),即原位修改。
如果你之前熟悉 MySQL 或者是其他關(guān)系型數(shù)據(jù)庫,這套原理應(yīng)該是比較熟悉的。
而相對應(yīng)的,用傳統(tǒng)的數(shù)據(jù)庫來實(shí)現(xiàn)圖功能的話,代價比較大,下圖便展示了它的實(shí)現(xiàn)弊端:
現(xiàn)在有個場景,現(xiàn)在我們有某個人(上圖 Person 表),我們要找朋友的朋友(上圖的 PersonFriend 表),在關(guān)系型數(shù)據(jù)庫中便是兩級索引,先查 Person 表索引找到 Person ID,再查 PersonFriend 表通過 ID 找到對應(yīng)的人,就是一個 JOIN 查詢過程。如果這里使用的是 B+ 樹,那么程序復(fù)雜度便是 O(logn);如果是這里的多級大小表,在笛卡爾積上即 O(n*m),都加索引有一定程度優(yōu)化,但查詢這種多級關(guān)系的話,到了一定程度會遇到系統(tǒng)“爆炸”,無法進(jìn)行相關(guān)查詢。
LSM 存儲模型
本文主題是圖的高性能設(shè)計,主要基于 Nebula Graph 來講解。這里部分存儲細(xì)節(jié)同 Neo4j 會略有不同。
Nebula Graph 存儲模型采用了 LSM 存儲模型,同上面我們講的原位修改不同,LSM 模型是先寫內(nèi)存,寫到一定程度之后再寫入到對應(yīng)磁盤中,每次都是增量順序?qū)?。LSM 模型是一個多級模型,第一層是 L0,第二層是 L1,一般默認(rèn)是 7 層。
這里引用網(wǎng)圖來講解下 LSM 層級結(jié)構(gòu):
上圖的 L0 層其實(shí)有重復(fù)數(shù)據(jù),像上圖的 1-68 的 key 在 L0 層的 2-37,以及 23-48,其實(shí)這兩步數(shù)據(jù)是存在重疊的;但 L1 層的數(shù)據(jù)就不存在重疊情況了,1-12、15-25…要最大地發(fā)揮圖性能的話,先得了解它的寫入過程。LSM 模型的寫是順序?qū)?,即不會進(jìn)行上文說到的原位修改。不管是寫入新數(shù)據(jù)還是更新原來數(shù)據(jù),永遠(yuǎn)是在后面插入新的數(shù)據(jù)(參考上圖右側(cè)深藍(lán)色數(shù)據(jù))。這樣設(shè)計的好處在于,寫入數(shù)據(jù)就不需要找之前的數(shù)據(jù),一旦涉及查找數(shù)據(jù)就會慢,這樣設(shè)計提升了寫速度。
但這也會帶來一個問題:我們寫入重復(fù)的數(shù)據(jù),或是寫入的數(shù)據(jù)越來越多,查詢會不會受影響呢?我們來看看 LSM 是如何查數(shù)據(jù)的。LSM 進(jìn)行數(shù)據(jù)查詢時,先查內(nèi)存,內(nèi)存里沒有數(shù)據(jù)再查不可變區(qū)域(Immutable Memtable),沒有的話,再往下一級級地查(參考上圖左側(cè)部分)。所以,重復(fù)的數(shù)據(jù)越多,或者磁盤數(shù)據(jù)越多,便會越慢。
所以為了保證寫入和查詢性能,無論我們設(shè)計屬性還是其他 schema,都要控制寫入量,也就是 LSM 的寫入不能是無限制追加,它有一個定時的合并操作,定期地將重復(fù)數(shù)據(jù)進(jìn)行合并,叫做 Compaction。
Compaction 過程也需要控制。合并數(shù)據(jù)能減小數(shù)據(jù)量,但同時 Compaction 會帶來磁盤壓力,磁盤壓力過大,讀操作速度也會變慢。綜合來看,Compaction 是一個寫入平衡的過程。
Nebula Graph 存儲結(jié)構(gòu)和索引
下面再來了解下 Nebula Graph?本身的存儲結(jié)構(gòu)和索引。
Nebula Graph 本身是分布式數(shù)據(jù)庫,因?yàn)楸阌诶斫膺@里剔除了相關(guān)的分布式結(jié)構(gòu)。簡單來了解下 Nebula Graph 的結(jié)構(gòu),上面提到過的 LSM 其實(shí)是 KV(key value)存儲,所以我們圖里存儲點(diǎn)、邊、索引在磁盤上都是 KV 結(jié)構(gòu)。我們可以看到上圖左側(cè)(紫色部分)有個 vid 帶著出邊(out)和入邊(in)以及相關(guān)屬性。再看下上圖右側(cè)部分(紫色部分),可以看到一條邊的兩個點(diǎn)是存儲在一起的,對應(yīng)的點(diǎn)屬性序列化保存。相當(dāng)于說,KV 結(jié)構(gòu)中的 key 便是我們的點(diǎn)的 vid,然后 value 便是屬性的序列化結(jié)構(gòu)。因?yàn)槭切蛄谢慕Y(jié)構(gòu),所以你的屬性名是什么便會存成什么,比如這里原始數(shù)據(jù) name 字段,它改命名為 family_name,實(shí)際存儲就是序列化后的 family_name,也就是屬性名越長,存儲量越大。除了屬性名之外,其實(shí)屬性值也會導(dǎo)致存儲量增大。舉個例子,現(xiàn)在有個人(點(diǎn)),他的生平介紹要不要放在屬性里進(jìn)行存儲?答案是:不應(yīng)該。因?yàn)槟愕纳浇榻B會很長,這就會導(dǎo)致 LSM 的存儲壓力會很大。無論是 Compaction 還是讀寫,都會有很大的壓力。類似比如存儲進(jìn)程實(shí)體,對應(yīng)的進(jìn)程描述文本也較大,會帶來較大存儲壓力。
再來說下我們的邊,Nebula Graph?中出邊和入邊保存在一個 KV 結(jié)構(gòu)中(參考上圖右側(cè)橙色部分)。Nebula Graph 中有個詞叫做前綴掃描,具體來說便是現(xiàn)在要查找某個 vid 對應(yīng)的邊,它是如何查找的呢?先按照 vid 來前綴掃描,在內(nèi)存中這個過程是個二分查找,所以 Nebula Graph 查詢快就是在這里。在 Neo4j 里面這種叫做“免索引鄰接”。像上面的朋友的朋友的場景,傳統(tǒng)數(shù)據(jù)庫是通過索引進(jìn)行查找的,而在這里直接掃描找尋某個人便可。在物理存儲這塊,點(diǎn)(人和相關(guān)的人)都是存儲在一起的,找到了某個人便找到了他的朋友。查詢上速度非常快,這也是原生圖數(shù)據(jù)庫帶來的好處。
除了上面的存儲結(jié)構(gòu),索引也是高性能 schema 設(shè)計的一個作用因素。像上圖的右側(cè)部分,上面的紫色部分存儲著點(diǎn),這里有 2 個點(diǎn):第一個點(diǎn)是 vid1,name 是 wen,age 是 20;另外一個是 vid2,name 是 wei,age 是 20。這里我們創(chuàng)建了 2 個索引,一個是針對 name,一個是針對 age。這兩個索引的存儲結(jié)構(gòu)參考上圖右側(cè)下方的白色部分,查找 name 為 wen 的數(shù)據(jù)時,按照上面我們科普過的會進(jìn)行二分查找,掃描到對應(yīng)的 name 索引的 wen 數(shù)據(jù),然后再從索引數(shù)據(jù)中找到對應(yīng)的點(diǎn)(vid1)數(shù)據(jù),再借助 vid 數(shù)據(jù)來找尋它的相關(guān)信息。這里 vid 找關(guān)聯(lián)數(shù)據(jù)的原理同上面的存儲結(jié)構(gòu)描述。
小結(jié)
小結(jié)下 Nebula Graph 存儲結(jié)構(gòu)和索引,在這里關(guān)系是一等公民,索引輔助查詢(并非用來提速),重要的是抽象關(guān)系。
schema 設(shè)計
進(jìn)入本文的重點(diǎn)——schema 的設(shè)計,schema 設(shè)計的三大基本原則:
尊重領(lǐng)域?qū)嶓w關(guān)系
以性能為目標(biāo)
考慮可視化分析
而三者并不沖突,上面三點(diǎn)其中某一點(diǎn)做得很好,另外兩點(diǎn)也會做的不錯。
Talking is cheap,下面我們來結(jié)合具體的例子來了解下三大原則。這些 case 圖主要引用自 Neo4j,但是對于 Nebula Graph 相關(guān)的 schema 設(shè)計也有參考意義。
實(shí)體和關(guān)系的選擇
上圖是 Neo4j 圖數(shù)據(jù)庫書籍中的示例圖。簡單描述下這個場景,Bob 和 Charlie 等人在發(fā)郵件。那你設(shè)計這么一個場景的 schema 是否很自然就會將發(fā)郵件變成關(guān)系邊?因?yàn)?Bob 同 Charlie 發(fā)郵件,不是很明顯就是發(fā)郵件關(guān)系嗎?那我們來回顧下上面說的三大原則第一點(diǎn):尊重領(lǐng)域?qū)嶓w關(guān)系。Bob 和 Charlie 建立聯(lián)系自然不是通過發(fā)郵件這個行為,而是通過郵件本身來建立聯(lián)系,所以這里便缺少了一個實(shí)體。在考慮可視化分析原則這邊,你要分析實(shí)體之間的關(guān)系,你思考它們是通過什么來建立的聯(lián)系。這時候就會發(fā)生之前提到過的發(fā)郵件設(shè)置為邊的情況(把郵件放置在邊上),單看 Bob 的話(左圖),我們可以清楚地看到發(fā)郵件這個動作。左圖上面部分,Bob Emailed Charlie。但如果這時候,要查看這個郵件抄送給了誰,還有這封郵件有哪些相關(guān)人,像左圖的 schema 就不能很好地進(jìn)行查詢。因?yàn)槿鄙倭?Email 這個實(shí)體。而上圖右側(cè)部分便能可以方便地找尋相關(guān)信息。
下面再來講下如何進(jìn)行實(shí)體和屬性選擇。
實(shí)體和屬性選擇
在這個部分,我將結(jié)合青藤云的情況來講一個我們的 case——進(jìn)程之間的父子關(guān)系。
如上圖左側(cè)所示,md5 為 1 的 pid 100 進(jìn)程起了一個 pid 102 的子進(jìn)程,這個子進(jìn)程的 md5 是 2。同時,md5 也為 1 的 pid 101 也起了進(jìn)程,pid 為 103、md5 為 3。按照我們之前的實(shí)現(xiàn)方法,是在 md5 上創(chuàng)建索引,繼而建立起跟 pid 102、pid 103 的聯(lián)系。但這種做法,上面講過性能并不高,免索引復(fù)雜是 O(1),而這種做法的復(fù)雜度是 O(logn)。所以說,我們這時候應(yīng)該基于 ProcessFile 進(jìn)程文件 md5 來建立關(guān)系(進(jìn)程間是基于 md5 聯(lián)系起來的):我們先抽取 md5 建立一個名叫 ProcessFile 的實(shí)體,屬性是 md5。如果我們要查詢指定進(jìn)程所關(guān)聯(lián)的進(jìn)程,很直觀地去找尋和這個 ProcessFile 關(guān)聯(lián)的進(jìn)程就可以分析出來我們要的結(jié)果。舉個例子,pid 102 的進(jìn)程是一個木馬,我想找尋是哪個父進(jìn)程釋放的它,或者是同它父進(jìn)程同 md5 文件的進(jìn)程,該怎么找?
上圖的展示了兩種形式,第一種(左側(cè))的話就需要找索引;第二種(右側(cè))通過 CREATE_PROCESS 就可以直接找到 pid 102 的父進(jìn)程 pid 100,再通過 PFILE_OF 關(guān)系你可以找到它同 md 文件的進(jìn)程 pid 101。
好的,簡單結(jié)合 schema 設(shè)計三大原則來回顧下這個 case:
屬性上創(chuàng)建索引會影響寫入,此外屬性放在 ProcessFile 還是放在 Process 中,存儲性能是不一樣的。這里主要涉及到寫入量,因?yàn)?Process 進(jìn)程是一直可以不停地啟動,但是 md5 文件可能本身并不多。如果是放在 Process 中,進(jìn)程起得越多,數(shù)據(jù)寫入量也就會越大,進(jìn)而查詢壓力也會增大查詢變慢。
可視化探索這塊主要和不定需求有關(guān)。因?yàn)橐婚_始我們設(shè)計 schema 的時候可能并沒有全方位考量,或者說像是一些安全、防作弊規(guī)則并未擬定,不知道它會是什么樣。而這時你要根據(jù)這種不確定來設(shè)計 schema,就需要將圖“釋放”給相關(guān)業(yè)務(wù)人員,讓他在圖里點(diǎn)擊,設(shè)計他的關(guān)系,所以相對應(yīng)的我們就不能通過索引來實(shí)現(xiàn)這種需求,因?yàn)闃I(yè)務(wù)人員可能沒有相關(guān)的技術(shù)背景。
添加屬性
上圖左邊描述文字截自 Nebula Graph v2.0 的官方文檔:
https://docs.nebula-graph.com.cn/2.0/3.ngql-guide/1.nGQL-overview/2.graph-modeling/#_3。
在合理設(shè)置邊屬性的第二部分提到,“為邊創(chuàng)建屬性時請勿使用長字符串”。這個和我們之前提到過的,屬性名和屬性值都應(yīng)該短,不應(yīng)該長是一個意思。像上圖右側(cè)部分,很明顯可以看到 vid 重復(fù)寫多次的話,每次寫就是重復(fù)的流量和存儲,這會大大增大內(nèi)存占用和磁盤容量。如果我們把 session_guid 變成 sid 會節(jié)約很多存儲。而后面的描述信息,也有兩種處理方式。第一種,直接刪除描述;第二種,將過長的描述存儲在外部,比如放置在 Elasticsearch,然后將 ES 存儲這塊內(nèi)容的 eid 存儲在上圖的 value 中。這樣也可以大大減少存儲量,提升寫入 / 查詢性能。
除了這點(diǎn)之外,我們還要注意合理設(shè)置分組標(biāo)簽。青藤云暫時沒遇到類似 case,所以這里講下這句話什么意思。簡單來說,就是寫入這邊需要做一個 tag 的區(qū)分,結(jié)合上文提到的二分查找,你就比較好理解了。舉個簡單例子,這里有個人,他的公司相關(guān)信息,或者年齡相關(guān)的信息,或者是個人喜好之類的信息用相關(guān)的 tag 區(qū)分開,這樣查詢時可以更快地找到對應(yīng)的信息。
最后回到文檔「合理設(shè)置邊屬性」中第一部分中的“深度圖遍歷的性能較低,為了減少遍歷深度,請使用點(diǎn)屬性代替邊。例如,模型 a 包括姓名、年齡、眼睛顏色三種屬性,建議您創(chuàng)建一個標(biāo)簽 person,然后為它添加姓名、年齡、眼睛顏色的屬性?!保凑展俜脚e的例子,固然是這樣的。但實(shí)際應(yīng)用中,并非一定要遵循這一原則——屬性用點(diǎn)屬性而不是用邊,該用實(shí)體的時候還是得用實(shí)體。所以我這里下面?zhèn)渥懥耍好枋鰧?shí)體本身特性。像實(shí)體本身的特性 age / status,邊的 time / count 這些屬性會變成相對應(yīng)的屬性,這樣能更好地描述本質(zhì)特性,也能起到比較好的輔助效果。
添加索引
借助之前我們的實(shí)踐經(jīng)驗(yàn),來講下索引這塊內(nèi)容。在 Nebula Graph 的官方文檔中提及了:盡量少用索引。那么問題來了,到底什么時候應(yīng)該用索引呢?我們先從原理上來解釋下索引。在上圖的例子中,value 中存儲了 2 樣?xùn)|西:一個是 status,狀態(tài);另外一個是 ip。右側(cè)的表格是對應(yīng)的 KV 存儲結(jié)構(gòu),key 是個點(diǎn)結(jié)構(gòu)。給點(diǎn)加索引之后,它便會變成左側(cè)表格的結(jié)構(gòu),idx-x-vid1。如果我們要查詢 status 等于 0 的這列值的時候,由于加了索引之后數(shù)據(jù)結(jié)構(gòu)是以 0(status)為前綴,vid 放在 0 后面;如果我們要查詢 ip 的話,存儲結(jié)構(gòu)則將 ip 變成前綴,vid 存儲在后面。這樣會產(chǎn)生何種問題呢?status 如果只有 1 和 0,現(xiàn)在你有 1 萬億的點(diǎn),這樣添加索引是沒有意義的。而且,因?yàn)?Nebula Graph 的查找是二分查找,復(fù)雜度收斂到 O(n),相當(dāng)于有多少數(shù)據(jù)就查多少數(shù)據(jù)。即便你添加了一個 limit,但是在 Nebula Graph 這邊(注:本次分享時,Nebula Graph 的最新版本為 v2.0.1)limit 并沒有下推,所以所有數(shù)據(jù)會先撈上來到計算層,在內(nèi)存中使用 limit 進(jìn)行數(shù)據(jù)過濾。
正是由于這種情況,所以在 v2.5 之前的 Nebula Graph 用戶會經(jīng)常在論壇反饋 OOM 問題,其實(shí)就是內(nèi)存爆炸。
所以說,索引應(yīng)該是盡可能和業(yè)務(wù)相關(guān)的標(biāo)識。
細(xì)粒度關(guān)系和通用關(guān)系
通過上面的 Neo4j 這個 case 我們來講解下顆粒度問題。
像上面的人有 2 個地址,一個是收件地址,另外一個是付款地址。如果此時,我們想找尋這個人的地址,如果沒有 ADDRESS 這個通用標(biāo)簽的話,DELIVERY_ADDRESS 和 BILLING_ADDRESS 這兩個關(guān)系都得查下。這時候如果用的是二分查找,如果這堆關(guān)系本身存儲在一起還好,可以一次性查找出來;但,如果關(guān)系不在一起,就需要分 2 次查詢,這會降低它的查詢速率。
因此,我們可以再創(chuàng)建一個通用標(biāo)簽,但是要注意的是,標(biāo)簽的建立是基于對某個業(yè)務(wù)有強(qiáng)需求。像上面的例子,需要知道用戶的所有地址,也要知道他的單獨(dú)地址,比如:收件地址。這種情況下,建立一個通用標(biāo)簽才是一個加速的方法,但注意要謹(jǐn)慎使用。同樣的,通用標(biāo)簽設(shè)計時,也需要考慮可視化的情況。
加速查詢
之前我們講過一個發(fā)郵件的例子,但是現(xiàn)在場景有所變化了,我現(xiàn)在不關(guān)心發(fā)郵件這個事情,我只關(guān)心人和人之間的關(guān)系,比如,wen 這個人的聯(lián)系關(guān)系,有誰和他聯(lián)系過,而這個聯(lián)系方式可能是 Email,也可能是手機(jī)(Phone),或者是微信。這時候我應(yīng)該如何設(shè)計 schema 呢?當(dāng)然之前的設(shè)計是可以沿用的,但為了加速查詢,滿足業(yè)務(wù)上的需求。這里加了 CONTACT 屬性,用來加速查找。
小結(jié) schema 設(shè)計
講到這里,我們總結(jié)下上面的例子,其實(shí)我們的例子都是圍繞著三大原則來展開的,即:性能、可視化、領(lǐng)域關(guān)系。
?
典型 schema 設(shè)計
下面來我們來講下有些典型場景下的 schema 設(shè)計。
時間設(shè)計
現(xiàn)在有個場景,有一堆發(fā)生過的事件,現(xiàn)在想查詢在某個月,或者是某個時間段內(nèi),發(fā)生了哪些事件,我們該如何設(shè)計 schema 呢?也許我們可以在時間屬性上創(chuàng)建個索引,把這個時間當(dāng)作索引來存儲,但這樣的話,查詢速度不會很快,尤其是數(shù)據(jù)量較大的情況下。那我們應(yīng)該怎么做呢?Neo4j 給了一種設(shè)計思路叫做時間樹,就是說時間本身是有層級關(guān)系的。如上圖所示,時間有個層級,想要查詢某個事件同時間段內(nèi)的其他事件,可以通過這個層級快速找到。
上圖右側(cè)則是一個時序關(guān)系,可以快速找尋某事件發(fā)生的時間前后有哪些事情發(fā)生,而在 Nebula Graph 中,你可以通過 rank 來實(shí)現(xiàn)時序圖功能。
上面的例子只是給大家一個參考,并不代表會應(yīng)用在青藤云實(shí)際業(yè)務(wù)中。
地址設(shè)計
上面這個是地址的設(shè)計,可能大家都會遇到。假如,現(xiàn)在我們要查詢北京朝陽太陽宮在發(fā)生事件 A 時,同一個地理位置有多少用戶 / IP 在這。傳統(tǒng)的設(shè)計方法中,添加屬性是無法滿足該業(yè)務(wù)需求的。那怎么實(shí)現(xiàn)呢?其實(shí)這些地址劃分可以作為實(shí)體,而且地址之間是有關(guān)系的。以上述的物流為例,上面的例子:中國-北京-朝陽-太陽宮,就可以通過集散中心-派送點(diǎn)-派送區(qū)域-派送段形式進(jìn)行查詢。如果你要查詢同一個街道或者是同個市,也可以按照這個關(guān)系快速進(jìn)行查詢。
像我們遇到的地址位置,或者是網(wǎng)絡(luò)層問題,都可以參考這種設(shè)計。之前在 BOSS 直聘(分享嘉賓曾就職 BOSS 直聘)中,我們就是參考了類似的實(shí)現(xiàn)來找尋某個區(qū)域的相關(guān)用戶。
?
圖最佳實(shí)踐
上面講述的內(nèi)容主要是圍繞 schema 設(shè)計,下面這塊當(dāng)作補(bǔ)充資料,主要講的是圖的最佳實(shí)踐。
命名規(guī)范
如果你要編寫一個比較長的語句,不知道你有沒有注意過,這個語句該如何快速區(qū)分哪些是實(shí)體,哪些是關(guān)系,哪些又是屬性。所以,這里就要提一下命名規(guī)范問題。一旦命名規(guī)范了,一條長查詢語句也可能快速辨別實(shí)體、關(guān)系、屬性。
你可以參考下面的命名規(guī)范:
實(shí)體采用駝峰方式,例如:User、Email、Process;
關(guān)系采用全部大寫,包含動詞和副詞,例如:HAS_IP;
屬性采用英文小寫簡寫,例如:title、sid、pid
圖計算
上圖給出了圖數(shù)據(jù)庫和圖計算的工作流,可以直觀地查看到二者的區(qū)別。圖數(shù)據(jù)庫的工作流相對簡單,拿我們常見的一個場景舉例,已知某個有問題的進(jìn)程 A,要溯源找尋它的源頭。對應(yīng)到圖這邊,圖數(shù)據(jù)庫的查詢一般會 GO / LOOKUP / MATCH / FETCH 錨定某個起始點(diǎn),比如這里的進(jìn)程 A,然后管道 / WITCH 進(jìn)行下一步的處理,最后用 RETURN / YIELD 來返回基本結(jié)構(gòu)。但,注意,這個基本結(jié)構(gòu)會進(jìn)行二次加工。剛設(shè)計 schema 的時候提到過,并不是所有的屬性都會設(shè)計進(jìn)去,只有和業(yè)務(wù)相關(guān)的核心屬性才會設(shè)計進(jìn)入。像請求接口之類的操作,都會在下一步過濾 / 擴(kuò)展處理時完成。
上面說的是圖的直接業(yè)務(wù)簡單查詢,但還有一種場景是用圖來進(jìn)行機(jī)器學(xué)習(xí),比如 GNN 和 GCN 用圖來做特征,這塊本文就不展開講述,流程和上面有所不同。
那,什么時候用圖數(shù)據(jù)庫,什么時候用圖計算呢?
如上圖所示,有限點(diǎn)的拓展就比較適合用圖數(shù)據(jù)庫,或者說 Nebula Graph 來實(shí)現(xiàn);而全局挖掘就比較適合用圖計算。從圖計算的流程上來看,簡單粗暴地講,圖計算就是把一批數(shù)據(jù)撈到內(nèi)存中,一次性計算完,然后“吐”出來,再進(jìn)行下一步的過濾和處理。至于它是如何計算的,圖計算里面配有計算引擎。
現(xiàn)在我們來問個問題,如果要找全圖點(diǎn)度 Top10 的點(diǎn),應(yīng)該用什么?
自然是圖計算,圖計算也就是 OLAP 主打的是吞吐,即一次性能處理多少數(shù)據(jù);而圖數(shù)據(jù)庫,主要是應(yīng)對 OLTP 場景,側(cè)重低延遲,就是查詢有多快,以及支持多大量的并發(fā)請求 QPS。
只要我們記住圖數(shù)據(jù)庫和圖計算各自的擅長場景,就比較好處理相關(guān)的業(yè)務(wù)。
大圖優(yōu)化
像傳統(tǒng)關(guān)系型數(shù)據(jù)庫中,業(yè)務(wù)無限膨脹的話,就需要做分庫分表。圖也是類似的,在大圖上做某些查詢時,你會發(fā)現(xiàn)性能很差,這時候你就需要進(jìn)行分圖處理。像上面說到過的關(guān)系細(xì)化和加速查詢,比如我現(xiàn)在只關(guān)心進(jìn)程關(guān)系,在特定業(yè)務(wù)場景下就需要將進(jìn)程關(guān)系單獨(dú)設(shè)計成一張圖。這就是圖的一個優(yōu)化手段。或者,你也可以進(jìn)行業(yè)務(wù)隔離。像現(xiàn)在的業(yè)務(wù)是針對推薦場景,剩下的安全場景是否要放置在同一個圖空間下呢?如果業(yè)務(wù)量不大的情況下,是可以的。但是如果是數(shù)據(jù)量大的話,還是需要同傳統(tǒng)數(shù)據(jù)庫一樣進(jìn)行業(yè)務(wù)隔離,什么業(yè)務(wù)進(jìn)入什么圖。
這里延伸一下,分圖場景下如何進(jìn)行多圖查詢呢?簡單來說就是進(jìn)程一張圖,網(wǎng)絡(luò)是一張圖,這時候要查詢進(jìn)程和網(wǎng)絡(luò)的關(guān)系。業(yè)界的話,管這個叫做查詢端融合。雖然你要查詢的數(shù)據(jù)是 2 張圖,但是我假裝你是在一張圖上進(jìn)行查詢。
編輯:黃飛
?
評論
查看更多