基於 Nebula Graph 構建百億關係知識圖譜實踐

NebulaGraph 發表於 2022-06-27
本文首發於 Nebula Graph Community 公眾號

一、專案背景

微瀾是一款用於查詢技術、行業、企業、科研機構、學科及其關係的知識圖譜應用,其中包含著百億級的關係和數十億級的實體,為了使這套業務能夠完美執行起來,經過調研,我們使用 Nebula Graph 作為承載我們知識圖譜業務的主要資料庫,隨著 Nebula Graph 的產品迭代,我們最終選擇使用 v2.5.1 版本的 Nebula Graph 作為最終版本。

二、為什麼選擇 Nebula Graph?

在開源圖資料庫領域,無疑存在著很多選擇,但為了支撐如此大規模資料的知識圖譜服務,Nebula Graph 對比其他的圖資料庫具有以下幾個優點,這也是我們選擇 Nebula Graph 的原因:

  1. 對於記憶體的佔用較小

在我們的業務場景下,我們的 QPS 比較低且沒有很高的波動,同時相比起其他的圖資料庫,Nebula Graph 具有更小的閒時記憶體佔用,所以我們可以通過使用記憶體配置更低的機器去執行 Nebula Graph 服務,這無疑為我們節省了成本。

  1. 使用 multi-raft 一致性協議

multi-raft 相比於傳統的 raft,不僅增加了系統的可用性,而且效能比傳統的 raft 要高。共識演算法的效能主要在於其是否允許空洞和粒度切分,在應用層無論 KV 資料庫還是 SQL ,能成功利用好這兩個特性,效能肯定不會差。由於 raft 的序列提交極其依賴狀態機的效能,這樣就導致即使在 KV 上,一個 key 的 op 慢,顯著會拖慢其他 key。所以,一個一致性協議的效能高低的關鍵,一定是在於狀態機如何讓可以並行地儘量並行,縱使 multi-raft 的粒度切分比較粗(相比於 Paxos),但對於不允許空洞的 raft 協議來說,還是有巨大的提升。

  1. 儲存端使用 RocksDB 作為儲存引擎

RocksDB 作為一款儲存引擎/嵌入式資料庫,在各種資料庫中作為儲存端得到了廣泛地使用。更關鍵的是 Nebula Graph 可以通過調整 RocksDB 的原生引數來改善資料庫效能。

  1. 寫入速度快

我們的業務需要頻繁地大量寫入,Nebula Graph 即使在具有大量長文字內容的 vertex 的情況下(叢集內3 臺機器、3 份資料,16 執行緒插入)插入速度也能達到 2 萬/s 的插入速度,而無屬性邊的插入速度在相同條件下可以達到 35 萬/s。

三、使用 Nebula Graph 時我們遇到了哪些問題?

在我們的知識圖譜業務中,很多場景需要向使用者展示經過分頁的一度關係,同時我們的資料中存在一些超級節點,但根據我們的業務場景,超級節點一定會是使用者訪問可能性最高的節點,所以這不能被簡單歸類到長尾問題上;又因為我們的使用者量並不大,所以快取必然不會經常被撞到,我們需要一套解決方案來使使用者的查詢延遲更小。

舉例:業務場景為查詢這個技術的下游技術,同時要根據我們設定的排序鍵進行排序,此排序鍵是區域性排序鍵。比如,某個機構在某一領域排名特別高,但是在全域性或者其他領域比較一般,這種場景下我們必須把排序屬性設定在邊上,並且對於全域性排序項進行擬合與標準化,使得每個維度的資料的方差都為 1,均值都為 0,以便進行區域性的排序,同時還要支援分頁操作方便使用者查詢。

語句如下:

MATCH (v1:technology)-[e:technologyLeaf]->(v2:technology) WHERE id(v2) == "foobar" \ 
RETURN id(v1), v1.name, e.sort_value AS sort ORDER BY sort | LIMIT 0,20;

此節點有 13 萬鄰接邊,這種情況下即使對 sort_value 屬性加了索引,查詢耗時還是將近兩秒。這個速度顯然無法接受。

我們最後選擇使用螞蟻金服開源的 OceanBase 資料庫來輔助我們實現業務,資料模型如下:

technologydownstream

technology_iddownstream_idsort_value
foobarid11.0
foobarid20.5
foobarid30.0

technology

idnamesort_value
id1aaa0.3
id2bbb0.2
id3ccc0.1

查詢語句如下:

SELECT technology.name FROM technology INNER JOIN (SELECT technologydownstream.downstream_id FROM technologydownstream 
WHERE technologydownstream.technology_id = 'foobar' ORDER BY technologydownstream.sort_value DESC LIMIT 0,20) AS t 
WHERE t.downstream_id=technology.id; 

此語句耗時 80 毫秒。這裡是整個架構設計

基於 Nebula Graph 構建百億關係知識圖譜實踐

四、使用 Nebula Graph 時我們如何調優?

前面講過 Nebula Graph 的一個很大的優勢就是可以使用原生 RocksDB 引數進行調優,減少學習成本,關於調優項的具體含義以及部分調優策略我們分享如下:

RocksDB 引數含義
max_total_wal_size一旦 wal 的檔案超過了 max_total_wal_size 會強制建立新的 wal 檔案,預設值為 0時,max_total_wal_size = write_buffer_size max_write_buffer_number 4
delete_obsolete_files_period_micros刪除過期檔案的週期,過期的檔案包含 sst 檔案和 wal 檔案, 預設是 6 小時
max_background_jobs最大的後臺執行緒數目 = max_background_flushes + max_background_compactions
stats_dump_period_sec如果非空,則每隔 stats_dump_period_sec 秒會列印 rocksdb.stats 資訊到 LOG 檔案
compaction_readahead_size壓縮過程中預讀取硬碟的資料量。如果在非 SSD 磁碟上執行 RocksDB,為了效能考慮則應將其設定為至少 2 MB。如果是非零,同時會強制new_table_reader_for_compaction_inputs=true
writable_file_max_buffer_sizeWritableFileWriter 使用的最大緩衝區大小 RocksDB 的寫快取,對於 Direct IO 模式的話,調優該引數很重要。
bytes_per_sync每次sync的資料量,累計到 bytes_per_sync 會主動 Flush 到磁碟,這個選項是應用到 sst 檔案,wal 檔案使用 wal_bytes_per_sync
wal_bytes_per_syncwal 檔案每次寫滿 wal_bytes_per_sync 檔案大小時,會通過呼叫 sync_file_range 來重新整理檔案,預設值為 0 表示不生效
delayed_write_rate如果發生 Write Stall, 寫入的速度將被限制在 delayed_write_rate 以下
avoid_flush_during_shutdown預設情況下,DB 關閉時會重新整理所有的 memtable,如果設定了該選項那麼將不會強制重新整理,可能造成資料丟失
max_open_filesRocksDB 可以開啟檔案的控制程式碼數量(主要是 sst檔案),這樣下次訪問的時候就可以直接使用,而不需要重新在開啟。當快取的檔案控制程式碼超過 max_open_files 之後,一些控制程式碼就會被 close 掉,要注意控制程式碼 close 的時候相應 sst 的 index cache 和 filter cache 也會一起釋放掉,因為 index block 和 filter block 快取在堆上,數量上限由 max_open_files 選項控制。依據 sst 檔案的 index_block 的組織方式判斷,一般來說 index_block 比 data_block 大 1 到 2 個數量級,所以每次讀取資料必須要先載入 index_block,此時 index 資料放在堆上,並不會主動淘汰資料;如果大量的隨機讀的話,會導致嚴重的讀放大,另外可能導致 RocksDB 不明原因的佔據大量的實體記憶體,所以此值的調整非常重要,需要根據自己的 workload 在效能和記憶體佔用上做取捨。如果此值為 -1,RocksDB 將一直快取所有開啟的控制程式碼,但這個會造成比較大量的記憶體開銷
stats_persist_period_sec如果非空,則每隔 stats_persist_period_sec 自動將統計資訊儲存到隱藏列族___ rocksdb_stats_history___。
stats_history_buffer_size如果不為零,則定期獲取統計資訊快照並儲存在記憶體中,統計資訊快照的記憶體大小上限為 stats_history_buffer_size
strict_bytes_per_syncRocksDB 把資料寫入到硬碟時為了效能考慮,預設沒有同步 Flush,因此異常情況下存在丟失資料的可能,為了對丟失資料數量的可控,需要一些引數來設定重新整理的動作。如果此引數為 true,那麼 RocksDB 將嚴格的按照 wal_bytes_per_sync 和 bytes_per_sync 的設定刷盤,即每次都重新整理完整的一個檔案,如果此引數為 false 則每次只重新整理部分資料:也就是說如果對可能的資料丟失不怎麼 care,就可以設定 false,不過還是推薦為 true
enable_rocksdb_prefix_filtering是否開啟 prefix_bloom_filter,開了之後會根據寫入 key 的前 rocksdb_filtering_prefix_length 位在 memtable 構造 bloom filter
enable_rocksdb_whole_key_filtering在 memtable 建立 bloomfilter,其中對映的 key 是 memtable 的完整 key 名,所以這個配置和 enable_rocksdb_prefix_filtering 衝突,如果 enable_rocksdb_prefix_filtering 為 true,則這個配置不生效
rocksdb_filtering_prefix_length見 enable_rocksdb_prefix_filtering
num_compaction_threads後臺併發 compaction 執行緒的最大數量,實際是執行緒池的最大執行緒數,compaction 的執行緒池預設為低優先順序
rate_limit用於記錄在程式碼裡通過 NewGenericRateLimiter 建立速率控制器的引數,這樣重啟的時候可以通過這些引數構建 rate_limiter。rate_limiter 是 RocksDB 用來控制 Compaction 和 Flush 寫入速率的工具,因為過快的寫會影響資料的讀取,我們可以這樣設定:rate_limit = {"id":"GenericRateLimiter"; "mode":"kWritesOnly"; "clock":"PosixClock"; "rate_bytes_per_sec":"200"; "fairness":"10"; "refill_period_us":"1000000"; "auto_tuned":"false";}
write_buffer_sizememtable 的最大 size,如果超過了這個值,RocksDB 就會將其變成 immutable memtable,並建立另一個新的 memtable
max_write_buffer_number最大 memtable 的個數,包含 mem 和 imm。如果滿了,RocksDB 就會停止後續的寫入,通常這都是寫入太快但是 Flush 不及時造成的
level0_file_num_compaction_triggerLeveled Compaction 專用觸發引數,當 L0 的檔案數量達到 level0_file_num_compaction_trigger 的值時,則觸發 L0 和 L1 的合併。此值越大,寫放大越小,讀放大越大。當此值很大時,則接近 Universal Compaction 狀態
level0_slowdown_writes_trigger當 level0 的檔案數大於該值,會降低寫入速度。調整此引數與level0_stop_writes_trigger 引數是為了解決過多的 L0 檔案導致的 Write Stall 問題
level0_stop_writes_trigger當 level0 的檔案數大於該值,會拒絕寫入。調整此引數與level0_slowdown_writes_trigger 引數是為了解決過多的 L0 檔案導致的 Write Stall 問題
target_file_size_baseL1 檔案的 SST 大小。增加此值會減少整個 DB 的 size,如需調整可以使target_file_size_base = max_bytes_for_level_base / 10,也就是 level 1 會有 10 個 SST 檔案即可
target_file_size_multiplier使得 L1 上層(L2...L6)的檔案的 SST 的 size 都會比當前層大 target_file_size_multiplier 倍
max_bytes_for_level_baseL1 層的最大容量(所有 SST 檔案大小之和),超過該容量就會觸發 Compaction
max_bytes_for_level_multiplier每一層相比上一層的檔案總大小的遞增引數
disable_auto_compactions是否禁用自動 Compaction

交流圖資料庫技術?加入 Nebula 交流群請先填寫下你的 Nebula 名片,Nebula 小助手會拉你進群~~

相關文章