日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本文首發于 Nebula Graph Community 公眾號

在上次的 nebula-storage on nLive 直播中,來自 Nebula 存儲團隊的負責人王玉玨(四王)同大家分享了 nebula storage 這塊的設計思考,也解答了一些來自社區小伙伴的提問。本文整理自該場直播,按照問題涉及的分類進行順序調整,并非完全按照直播的時間先后排序。

Nebula 的存儲架構

一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 

整個 Storage 主要分三層,最下面是 Store Engine,也 就是 RocksDB,中間是 raft 一致性協議層,最上層 storage service 提供對外的 rpc 接口,比如取點屬性,或者邊屬性,或者是去從某個點去找它的鄰居之類的接口。當我們通過語句CREATE SPACE IF NOT EXISTS my_space_2 (partition_num=15, replica_factor=1, vid_type=FIXED_STRING(30)); 創建 space 時,根據填寫的參數將 space 劃分為多個邏輯單元成為 partition,各個 partition 會落到不同機器上,同一個 Partition 的多個副本會組成一個邏輯單元,并通過 raft 共識算法 raft 保證一致。

Nebula 的存儲數據格式

一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 


一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 

這里著重講述為何 v2.x 會有這些數據格式的改動:在 v1.x 版本中,Nebula VID 主要是 int 類型,所以大家可以看到上圖 v1.x 中不管是點還是邊,它的 VID 是定長的、占 8 個字節。2.x 版本開始,為了支持 string 類型 VID,VertexID 就變成不定長的 n 個字節。所以大家創建 Space 的時候需要指定 VID 的長度,這個是最主要的改動,其他的話還有一些小的改動,去掉了時間戳。整體來說,目前的存儲格式更貼近圖的使用場景——從某個點開始找它的鄰居,以 v2.x 這樣 VertexID + EdgeType 存儲格式來保存邊的話,可以迅速地找到某個點出邊。

同時,v2.x 也做了 key(Nebula 底層是 KV 存儲的)編碼格式上的改變,簡單來說就是把點和邊分開。這樣的話,取某一個點所有 tag 時通過一次 prefix 就可以直接掃到,避免了像 v1.x 那樣掃描點的過程中夾雜多個邊的問題。

底層的數據存儲

針對用戶提出的“Nebula 底層如何存儲數據”的問題,四王了進行了回復:Nebula 的存儲層使用 KV 進行存儲點邊數據。對于一個點而言,key 里面存儲 VID 和它的 tag 類型。點的 value 中,會根據 這個 tag 的 schema,將 schema 中的各個屬性進行編碼并存在 value 中。比如,player 這個 tag 可能會有一個 age 這樣一個整型年齡字段,使用存儲的時候會把 age 字段的值,按某種編碼保存在 value 中。再來說下邊,邊的存儲 key 會多幾個字段,主要是邊的起點 ID、邊類型、ranking 及終點類型,通過這四元組確定唯一的邊。邊的 value 和點的 value 類似,根據邊的 Schema 字段定義,將各個字段進行編碼存儲。這里要說一下,Nebula 中存儲邊是存儲兩份:Nebula 中的邊是有向邊,存儲層會存儲正向邊和反向邊,這樣的好處在于使用 GO FROM 進行遍歷查找那些點指向點 A 或者點 A 指向哪些點可以快速通過雙向查找實現。

一般來說,圖存儲分為切邊和切點兩種方式,像上面說的 Nebula 其實采用了切邊方式:一條邊存儲兩份 KV。

用戶提問:為什么采用切邊方式,切點和切邊各自有啥利弊?

切邊的話,每一份邊存兩份,數據總量會比切點大很多,因為圖數據邊的數量是遠大于點的數量,造成邊的大量冗余,相對好處是對起點和它的邊進行映射時會映射到同一個 partition 上,這樣進行一些從單個點觸發的 query 時會很快速得到結果。切點的話,由于點可能被分在多個機器上,更新數據時得考慮數據的一致性問題,一般在圖計算里面切點的使用會更廣泛。

你問我答

下面內容收集于之前活動預告的 AMA 環節,以及直播時彈幕中提出的問題。

問題目錄

  • 邊的 value 存儲邊屬性嗎?
  • 強 Schema 的設計原因
  • 存一份邊的設計
  • 圖空間如何做物理隔離
  • Meta 如何存儲 Schema
  • 存儲未來規劃
  • VID 遍歷點和邊的原理
  • 數據預校驗
  • Nebula 監測
  • Nebula 的事務
  • 數據膨脹問題
  • 磁盤容量本身不均怎么處理
  • Nebula 的 RocksDB “魔改”

邊的 value 存儲邊屬性嗎?

和上面底層存儲里講的那樣,創建 Edge 的 schema 時候會指定邊類型上的屬性,這些屬性會作為底層 RocksDB key 的 value 存儲起來,這個 value 的占位是定長的,和下面這個問題類似:

強 Schema 的設計原因

強 schema 是因為技術原因還是產品原因? 因為考慮到 string 類型是變長的,每行長度本身就不固定,感覺跟無 schema 無區別。 如果非定長,那查詢時怎么知道該查詢到哪里呢? 是有標志位么?

其實本質上原因是用強 Schema 的好處是快,先說下常見的簡單數據類型,比如:int 和 double,這樣的數據類型長度是固定的,我們會直接在 value 相應的位置進行編碼。再說下 string 類型,在 Nebula 中有兩種 string :一種是定長 string,長度是固定,和前面的簡單數據類型一樣,在 value 的固定位置進行編碼。另外一種是變長的 string,通常來說大家都會比較傾向于變長 string(靈活),非定長 string 會以指針形式存儲。

舉個例子,schema 中有個屬性是變長 string 類型,我們不會和簡單數據類型一樣直接編碼保存,而是在相應位置保存一個 offset 指針,實際指向 value 中的第 100 個字節,然后在 100 這個位置才保存這個變長 string。所以讀取變長 string 的時候,我們需要在 value 中讀兩次,第一次獲取 offset,第二次才能真正把 string 讀出來。通過這樣的形式,把所有類型的屬性都轉化成"定長",這樣設計的好處是,根據要讀取的屬性和它前面所有字段的占用字節大小,可以直接計算出要讀取的字段在 value 中存儲的位置,并把它讀出來。讀取過程中,不需要讀取無關的字段,避免了弱 schema 需要對整個 value 進行解碼的問題。

像 Neo4j 這種圖數據庫,一般是 No Schema,這樣寫入的時候會比較靈活,但序列化和反序列化時都會消耗一些 CPU,并且讀取的時候需要重新解碼。

追問:如果有變長 string,會不會導致每行數據長度不一樣

可能 value 長度會不一樣,因為本身是變長嘛。

追問:如果每行長度不一樣,為什么要強 schema? Nebula 底層存儲用的 RocksDB,以 block 的形式組織,每個 block 可能是 4K 大小,讀取的時候也是按 block 大小進行讀取,而每個 block 中的各個 value 長度可能是不一樣的。強 schema 的好處在于讀單條數據的時候會快。

存一份邊的設計

Nebula 存邊是存儲了兩份,可以只存儲一份邊嗎?存一份邊反向查詢是否存在問題?

其實這是一個比較好的問題,其實在 Nebula 最早期設計中是只存一份邊的屬性,這適用于部分業務場景。舉個例子,你不需要任何的反向遍歷,這種情況下是完全不需要存反向邊。目前來說,存反向邊最大的意義是方便于我們做反向查詢。其實在 Nebula 比較早的版本中,準確說它是只存了反向邊的 key,邊類型的屬性值是沒有存,屬性值只存在正向邊上。它可能帶來一些問題,雙向遍歷或者反向查詢時,整個代碼邏輯包括處理流程都會比較復雜。 如果只存一份邊,反向查詢的確存在問題。

圖空間如何做物理隔離

大家在用 Nebula 時,首先會建圖空間 CREATE SPACE,在建圖空間時,系統會分配一個唯一圖空間 ID 叫 spaceId,通過 DESCRIBE SPACE 可以獲取 spaceId。然后 Storage 發現某臺機器要保存 space 部分數據時,會先單獨建一個額外的目錄,再建單獨 RocksDB 在這個上面起 Rocks 的 instance(實例)用來保存數據,通過這樣方式進行物理隔離。這樣設計的話,有個弊端:雖然 rocksdb 的 instance,或者說整個 space 目錄是互相隔離,但有可能存在同一塊盤上,目前資源隔離還做的不夠好。

Meta 如何存儲 Schema

我們以 CREATE TAG 為例子,當我們建 tag 時,首先會往 meta 發一個請求,讓它把這個信息寫進去。寫入形式非常簡單,先獲取 tagId,再保存 tag name。底層 RocksDB 存儲的 key 便是 tagId 或者是 tag name,value 是它每一個字段里面的定義,比如說,第一個字段是年齡,類型是整型 int;第二個字段是名字,類型是 string。schema 把所有字段的類型和名字全部存在 value 里,以某種序列化形式寫到 RocksDB 中。

這里說下,meta 和 storage 兩個 service 底層都是 RocksDB 采用 kv 存儲,只不過提供了不一樣的接口,比如說,meta 提供的接口,可能就是保存某個 tag,以及 tag 上有哪些屬性;或者是機器或者 space 之類的元信息,包括像用戶權限、配置信息都是存在 meta 里。storage 也是 kv 存儲,不過存儲的數據是點邊數據,提供的接口是取點、取邊、取某個點所有出邊之類的圖操作。整體上,meta 和 storage 在 kv 存儲層代碼是一模一樣,只不過往上暴露的對外接口是不一樣的。

最后,storage 和 meta 是分開存儲的,二者不是一個進程且存的目錄在啟動的時指定的也不一樣。

追問:meta 機器掛了,該怎么辦?

是這樣,通常來說 Nebula 建議 meta 以三副本方式部署。這樣的話,只掛一臺機器是沒有問題的。如果單副本部署 meta 掛了的話,是無法對 schema 進行任何操作,包括不能創建 space。因為 storage 和 graph 是不強依賴 meta 的,只有在啟動時會從 meta 獲取信息,之后都是定期地獲取 meta 存儲的信息,所以如果你在整個集群跑的過程中,meta 掛了而又不做 schema 修改的話,對 graph 和 storage 是不會有任何影響的。

存儲未來規劃

Nebula 后面在存儲層有什么規劃嗎?性能,可用性,穩定性方面

性能這塊,Nebula 底層采用了 RocksDB,而它的性能主要取決于使用方式,和調參的熟練程度,坦白來說,即便是 Facebook 內部員工來調參也是一門玄學。再者,剛才介紹了 Nebula 的底層 key 存儲,比如說 VID 或者是 EdgeType 在底層存儲的相對位置某種程度上決定了部分 Query 會有性能影響。從拋開 RocksDB 本身來說,其實還有很多性能上的事情可做:一是寫點或者寫邊時,有些索引需要處理,這會帶來額外性能開銷。此外,Compaction 和實際業務 workload 也會對性能有很大影響。

穩定性上,Nebula 底層采用 raft 協議,這是保證 Nebula Graph 不丟數據一個非常關鍵的點。因為只有這層穩定了,再往下面的 RocksDB 寫入數據才不會出現數據不一致或者數據丟失的情況發生。此外,Nebula 本身是按照通用型數據庫來設計的,會遇到一些通用型數據庫共同面臨的問題,比如說 DDL 改變;而本身 Nebula 是一款分布式圖數據庫,也會面臨分布式系統所遇到的問題,像網絡隔離、網絡中斷、各種超時或者因為某些原因節點掛了。上面這些問題的話,都需要有應對機制,比如 Nebula 目前支持動態擴縮容,整個流程非常復雜,需要在 meta 上、以及掛掉的節點、剩余“活著”的節點進行數據遷移工作。在這個過程中,中間任何一步失敗都要做 Failover 處理。

可用性方面,我們后續會引入主備架構。在有些場景下所涉及的數據量會比較少,不太需要存三副本,單機存儲即可。這種全部數據就在單機上的情況,可以減去不必要的 RPC 調用,直接換成本地調用,性能可能會有很大的提升。因為,Nebula 部署一共起 3 個服務:meta、graph 和 storage,如果是單機部署的話,graph + storage 可以放在同一臺機器上,原先 graph 需要通過 RPC 調用 storage 接口去獲取數據再回到 graph 進行運算。假如你的查詢語句是多跳查詢,從 graph 發送請求到 storage 這樣的調用鏈路反復執行多次,這會導致網絡開銷、序列化和反序列化的這些損耗提高。 當 2 個進程(storaged 和 graphd)合在一起,便沒有了 RPC 調用,所以性能會有個大提升。此外,這種單機情況下 CPU 利用率會很高,這也是目前 Nebula 存儲團隊在做的事情,會在下一個大版本同大家見面。

VID 遍歷點和邊的原理

可以依據 VID 遍歷點和邊?

一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 


一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 

從上圖你可以看到存儲了個 Type 類型,在 v1.x 版本中無論點和邊 Type 類型都是一樣的,所以就會發生上面說到過的掃描點會夾雜多個邊的問題。在 v2.x 開始,將點和邊的 Type 進行區分,前綴 Type 值就不一樣了,給定一個 VID,無論是查所有 tag 還是所有邊,都只需要一次前綴查詢,且不會掃描額外數據。

數據預校驗

Nebula 是強 Schema 的,插入數據時如何去判斷這個字段是否符合定義?

是否符合定義的話,大概是這樣,創建 Schema 時會要求指定某個字段是 nullable 或者是有默認值,或者既不是 nullable 也不帶默認值。當我們插入一條數據的時候,插入語句會要求你“寫明”各個字段的值分別是什么。而這條插入 Query 發到存儲層后,存儲層會檢查是不是所有字段值都有設置,或者寫入值的字段是否有默認值或者是 nullable。然后程序會去查是不是所有的字段都可以填上值。如果不是的話,系統會報錯,告知用戶 Query 有問題無法寫入。如果沒有報錯,storage 就會對 value 進行編碼,然后通過 raft 最后寫到 RocksDB 里,整個流程大概是這樣的。

Nebula 監測

Nebula 可以針對 space來進行統計嗎?因為我記得好像針對機器。

這個是非常好的問題,目前答案是不能。這塊我們在規劃,這個問題的主要原因是 metrics 較少,目前我們支持的 metrics 只有 latency、qps 還有報錯的 qps 這三類。每個指標有對應的平均值、最大值、最小值,sum 和 count,以及 p99 之類參數。目前是機器級別的 metrics,后續的話會做兩個優化:一個增多 metrics;二是按 space 級別進行統計,對于每個空間來說,我們會提供諸如 fetch、go、lookup 之類語句的 qps。上面是 graph 這邊的 metrics,而 storage 這塊因為沒有強資源隔離能力,還是提供集群或者單個機器級別的 metrics 而不是 space 級別的。

Nebula 的事務

nebula 2.6.0 的邊事務是怎么實現的呢?

先說下邊事務的背景,背景是上面提到的 Nebula 是存了兩份邊 2 個 kv,這 2 個 kv 可能會存在不同的節點上,這會導致如果有臺機器掛了,其中有一條邊可能是沒有成功寫入。所謂邊事務或者叫 TOSS,它主要解決的問題就是當我們遇到其中有一臺機器宕機時,存儲層能夠保證這兩個邊(出邊和入邊)的最終一致。這個一致性級別是最終一致,沒有選擇強一致是因為研發過程中碰到一些報錯信息以及數據處理流程上的問題,最后選擇了最終一致性。

再來說下 TOSS 處理的整體流程,先往第一個要寫入數據的機器發正向邊信息,在機器上寫個標記,看標記有沒有寫成功,如果成功了進入到下一步,如果失敗直接報錯。第二步的話,把反向邊信息從第一臺機器發給第二臺機器,能讓存正向邊的機器向第二臺機器發送反向邊信息的原因是,Nebula 中正反向邊只有起點和終點調換了一個位置,所以存正向邊的機器是完全可以拼出反向邊。存反向邊的機器收到之后,會直接寫入邊,并將它的寫入結果成功與否告訴第一臺機器。第一臺機器收到這個寫入結果之后,假設它是成功的,它就會把之前第一步寫的標記刪掉,同時換成正常的邊,這時整個邊的正常寫入流程就完成了,這是一個鏈式的同步機制。

簡單說下失敗的流程,一開始第一臺機器寫失敗了直接就報錯;第一臺機器成功之后,第二臺機器寫失敗了,這種情況下機器一會有背景線程,會一直不斷嘗試修復第二臺機器的邊,保證和第一臺機器一樣。當中比較復雜的是,第一臺機器會根據第二臺機器返回的錯誤碼進行處理。目前來說,所有的流程都會直接把標記刪掉,直接換成正常的正向邊,同時寫些更額外的標記來表示現在需要恢復的失敗邊,讓它們最終保持一致。

追問:點沒有事務嗎?

是這樣,因為點是只存了一份,所以它是不需要事務的。一般來說,問這個問題的人是想強調點和邊之間的事務,像插入邊時看點是否存在,或者刪除點時刪除對應邊。目前 Nebula 的懸掛點的設計是出于性能上的考慮。如果要解決上面的問題的話,會引入完整的事務,但這樣性能會有個數量級的遞減。順便提下,剛說到 TOSS 是鏈式形式同步信息,上面也提到能這樣做的原因是因為第一個節點能完整拼出第二個節點的數據。但鏈式的話對完整的事務而言,性能下降會更嚴重,所以未來事務這塊的設計不會采納這種方式。

數據膨脹問題

首次導入數據是怎么存儲的,因為我發現首次導入數據磁盤占用會較多?

大家發現如果磁盤占用高,一般來說是 WAL 文件比較多。因為我們導入的數據量一般比較大,這會產生大量的 wal,在 Nebula 中默認的 wal ttl 是 4 個小時,在這 4 個小時中系統的 WAL 日志是完全不會刪除的,這就導致占用的磁盤空間會非常大。此外,RocksDB 中也會寫入一份數據,相比后續集群正常運行一段時間,這時候磁盤占用會很高。對應的解決方法也比較簡單,導入數據時調小 wal ttl 時間,比如只存半小時或者一個小時,這樣磁盤占用率就會減少。當然磁盤空間夠大你不做任何處理使用默認 4 小時也 ok。因為過了若干個小時后,有一個背景線程會不斷去檢查哪些 wal 可以刪掉了,比如說默認值 4 個小時之后,一旦發現時過期的 wal 系統便會刪掉。

除了初次導入會有個峰值之外,線上業務實時寫入數據量并不會很大,wal 文件也相對小。這里不建議手動刪 wal 文件,因為可能會出問題正常按照 ttl 來自動刪除就行。

compact 都做了什么事可以提高查詢,也減小了數據存儲占用? 可以看下 RocksDB 介紹和文章,簡單說下 Compaction 主要是多路歸并排序。RocksDB 是 LSM-Tree 樹結構,寫入是 append-only 只會追加地寫,這會導致數據存在一定的冗余。Compaction 就是用來降低這種冗余,以 sst 作為輸入,進行歸并排序和去除冗余數據,最后再輸出一些 sst。在這個輸入輸出過程中,Compaction 會檢查同一個 key 是否出現在 LSM 中的不同層,如果同一個 key 出現了多次會只保留最新的 key,老 key 刪掉,這樣提高了 sst 有序的程度,同時 sst 數量和 LSM-Tree 的層數可能會減小,這樣查詢時候需要讀取的 sst 數量就會減少,提高查詢效率。

磁盤容量本身不均怎么處理

不同大小的磁盤是否考慮按百分比占用,因為我使用兩塊不同大小的磁盤,一塊占滿之后導數就出現問題了

目前是不太好做,主要原因是存儲 partition 分布查找是按照輪循形式進行的,另外一個原因是 Nebula 進行 Hash 分片,各個數據盤數據存儲大小趨近。這會導致如果兩個數據盤大小不一致,一個盤先滿了后面的數據就寫入不進去。解決方法可以從系統層進行處理,直接把兩塊盤綁成同一塊盤,以同樣一個路徑掛載。

Nebula 的 RocksDB “魔改”

Nebula 的 RocksDB 存儲中,是通過列 column family 來區別 vertex 屬性嗎?

目前來說,其實我們完全沒有用 column family,只用了default column family。后續可能會用,但是不會用來區分 vertex 屬性,而是把不同 partition 數據分到不同 column family,這樣的好處是直接物理隔離。

Nebula 的魔改 wal 好像是全局 multi-raft 的 wal,但是在目錄上體現出來的好像每個圖空間都是單獨的 wal,這個原理是啥? 首先,Nebula 的確是 multi-raft,但沒有全局 wal 的概念。Nebula 的 wal 是針對 partition 級別的,每個 partition 有自己的 wal,并不存在 space 的 wal。至于為啥這么設計,相對來說現在實現方式比較容易,雖然會存在性能損耗,像多個 wal 的話磁盤寫入就是個隨機寫入。但是對 raft 而言,寫入瓶頸并不是在這而是系統的網絡開銷,用戶的復制操作 replication 開銷是最大的。


Nebula 社區首屆征文活動進行中! 獎品豐厚,全場景覆蓋:擼碼機械鍵盤??、手機無線充、健康小助手智能手環??,更有數據庫設計、知識圖譜實踐書籍 等你來領,還有 Nebula 精致周邊送不停~

歡迎對 Nebula 有興趣、喜鉆研的小伙伴來書寫自己和 Nebula 有趣的故事呀~

一文帶你了解 「圖數據庫」Nebula 的存儲設計和思考

 

交流圖數據庫技術?加入 Nebula 交流群請先填寫問卷系統,Nebula 小助手會拉你進群~~

分享到:
標簽:Nebula
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定