導讀: 作為一種基礎的資料結構,圖資料的應用場景無處不在,如社交、風控、搜廣推、生物資訊學中的蛋白質分析等。如何高效地對海量的圖資料進行儲存、查詢、計算及分析,是當前業界熱門的方向。本文將介紹位元組跳動自研的圖資料庫ByteGraph及其在位元組內部的應用和挑戰。
本文將圍繞以下五點展開:
- 瞭解圖資料庫
- 適用場景介紹舉例
- 資料模型和查詢語言
- ByteGraph架構與實現
- 關鍵問題分析
--
01 瞭解圖資料庫
目前,位元組內部有如下表三款自研的圖資料產品。
1. 對比圖資料庫與關聯式資料庫
圖模型的基本元素包括點、邊和屬性。舉例:張三的好友所在的公司有多少名員工?傳統關係型資料庫需要多表join,而圖作為半結構化資料,在圖上進行遍歷和屬性的過濾會更加高效。
2. 什麼是圖資料庫?
近五年來,圖資料庫在領域內熱度上升趨勢非常明顯,各個大廠與開源社群都推出了自己的圖資料庫。使用者規模比較大、有一定影響力的查詢語言包括Cypher、Apache開源專案的Gremlin等。從叢集規模來看,過往有單機資料庫,現在大多圖資料庫都具備分散式能力,這就需要考慮資料的防丟失問題、主副本之間的一致性、多臺機器資料上的shard問題。
部分圖資料庫把圖資料庫與圖計算引擎二者合併在一起,目前位元組內部採用的暫時分離的兩套系統。
--
02 適用場景介紹舉例
1. ByteGraph適用的業務資料模型
ByteGraph初始立項是在2018年,主要目的是對頭條的使用者行為及好友關係進行儲存來替換Mysql;2019年6月承接對抖音使用者關係的資料儲存任務,接著在位元組內部各種微服務重承接了相關業務。
2. 已上線業務場景分類
目前有1.5萬臺物理機,服務於600+業務叢集。
--
03 資料模型和查詢語言
1. 有向屬性圖建模
目前來看,圖資料庫通常有兩大類,一種是屬性圖,另一種是RDF圖。屬性圖在節點和邊上有屬性表,從某種角度上講,它仍帶有關聯式資料庫的基本特性,類似表結構的形式,實際是採用Key-Value形式來儲存的,如使用者A關注了使用者B,使用者C點讚了某個視訊等,則會把關注的時間、點贊時間、評論的內容等以不同的有向邊儲存在屬性圖中,用圖來描述業務邏輯。
2. Gremlin查詢語言介面
選用Gremlin語言是考慮到之後方便對圖計算、圖資料庫二者進行融合,本身是圖靈完備的圖遍歷語言,相較於Cypher等類SQL語言,對於善用Python的資料分析師更容易上手。
舉例:寫一條使用者A所有一跳好友中滿足粉絲數量大於100的子集。首先定位使用者A在圖中的點,其次求一跳查詢中的所有鄰居,判斷入度鄰居整體數量是否大於100,拉取滿足條件的所有使用者。
--
04 ByteGraph架構與實現
1. ByteGraph整體架構
ByteGraph整體架構分為查詢引擎層(Graph Query Engine,下文簡稱GQ)、儲存引擎層(Graph Storage Engine,下文簡稱GS)和磁碟儲存層三層,整體上計算和儲存分離,每層由多個程式例項組成叢集。
2. ByteGraph讀寫流程
拿“讀流程”舉例,請求獲取使用者A的一跳鄰居。首先一個查詢進來後,從client端隨機挑選一個查詢層響應,對應到GQ2上,獲取對應的資料存放的位置是哪一臺機器,接著把請求給到GS1,檢查資料是否在該層以及是否為最新資料,如果不在則去KV store把所需資料拉取至GS1 快取中。
3. ByteGraph實現:GQ
GQ同MySQL的SQL層一樣,負責查詢的解析和處理,其中的“處理”可以分為下述三個步驟:
- Parser階段:利用遞迴下降解析器將查詢語言解析為一個查詢語法樹。
- 生成查詢計劃:將Parser階段得到的查詢語法樹按照查詢優化策略(RBO&CBO)轉換為執行計劃。
- 執行查詢計劃:理解GS資料分Partition的邏輯,找到相應資料並下推部分運算元,保證網路開銷不會太大,最後合併查詢結果,完成查詢計劃。
RBO主要基於Gremlin開源實現中的自帶優化規則、針對位元組應用中的運算元下推、自定義的運算元優化(fusion)三大規則。CBO本質上是對每個點的出入度做統計,把代價用方程量化表示。
對於不同支援場景使用不同策略,圖分割槽演算法的選擇與workload強相關,圖分割槽演算法能有效減少網路通訊次數。
- Brute force雜湊分割槽:即根據起點和邊的型別進行一致性雜湊分割槽,可以大部分查詢場景需求,尤其是一度查詢場景。
- 知識圖譜場景:點、邊型別極多,但每種型別邊數量相對較少,此時根據邊型別進行雜湊分割槽,將同種邊型別資料分佈在一個分割槽內。
- 社交場景:更容易出現大V,利用facebook於2016年提出的social hash演算法,通過離線計算儘量將有關聯的資料放置在同一分片內,降低延遲。
4. ByteGraph實現:GS
- 儲存結構
單個Partition定義為一個起點+一種特定的邊型別扇出的一跳鄰居。在GS中,將一個Partition按照排序鍵(可顯式設定或系統預設維護)組織成Btree。每棵Btree都有獨立的WAL序列,獨立維護自增logid。這種設計有利於支援GNN場景,做分散式取樣。
Edge Page、Meta Page分別是位於Btree中的葉子結點、非葉子結點(充當index作用),分別用於儲存圖中的邊資料和指向子節點的Key。Meta page長度是固定的,但是一個meta page會放多少edge page是可配的,通常配置為2000一片。如上圖,Partition在磁碟中將每個page都儲存為一個獨立的鍵值對(下文簡稱KV対)。meta page的key是起點+邊型別,edge page的key存在meta page中實現對特定edge page的查詢。
單機記憶體引擎整體採用hash map的結構,partition和page按需載入到記憶體中,根據LRU策略(Least Recent Used),swap到磁碟;某個page被修改後,WAL同步寫到磁碟,page會插入到dirty連結串列中,考慮當前機器狀態,非同步寫回。
- 日誌管理:單個起點+邊型別組成一棵Btree,每個結點是一個KV對。
每棵Btree單一寫者,防止併發寫入導致不完整;每棵樹都有獨立的WAL日誌流,且寫入請求處理流程中只寫入WAL,並修改記憶體中資料,compaction時再將資料落盤,解決由於每個KV對可能由多條邊組成而導致的寫放大。即使記憶體資料丟失,仍可通過更新後的logid在磁碟上進行WAL的查詢並寫入。
- 快取實現:根據不同場景及當下cpu的開銷有不同策略。
圖原生快取:相對於Memcached等直接快取二進位制資料而言,能更好的理解圖的語義,並支援一度查詢中的部分計算下推功能。
高效能LRU Cache:支援快取逐出,且逐出的頻率和觸發閾值可調;採用numa aware和cpu cacheline aware設計,提高效能;支援Intel AEP等新硬體。
Write-through cache:支援多種與底層儲存同步資料的模式,可以每次寫入或定時落盤;支援定期與底層儲存校驗資料,防止資料過舊;支援負快取等常見優化策略。
快取與儲存分離:當資料規模不變、請求流量增大的情況下,快取與儲存分離的模式可以快速擴容快取以提高服務能力。
--
05 關鍵問題分析
1. 索引
-
區域性索引:給定一個起點和邊型別,對邊上的屬性構建索引
特點:邊上元素皆可做索引項,能夠加速查詢,提高屬性過濾和排序效能;但會額外維護一份索引資料,與對應的原資料使用同一條日誌流,保證一致性。 -
全域性索引:目前只支援點的屬性全域性索引,即指定一個屬性值查詢出對應的點。
資料儲存在不同機器上,索引資料的一致性使用分散式事務解決。
2. 熱點讀寫
- 熱點讀
場景舉例:某熱點視訊被頻繁重新整理,檢視其點贊數量。
應用機制:GQ層採用多個bgdb併發處理同一熱點的讀請求,單節點快取命中讀效能可達20萬以上;GS層採用copy on write(即先拷貝,再寫入並替換)保證讀寫、讀讀均可併發。
- 熱點寫
場景舉例:某熱點視訊短時間內被瘋狂轉發、點贊。
問題溯源:單機cpu使用率被拉高,磁碟寫入iops有上限,當客戶端寫入qps>磁碟iops時,就會發生請求排隊。
應對機制:採用group commit機制,即將多個寫入請求組合至一個batch寫入KV,再批量返回,降低磁碟層iops的上限。
3. 輕重查詢資源分配
將輕重查詢的資源池分離,輕查詢走light執行緒池,負責數量多的小查詢;重查詢則走heavy執行緒池,負責數量少的重查詢。當heavy執行緒池空閒時,輕查詢也可走。
4. 高可用
都會網路雙機房,如國內的兩個機房,延遲較低。follow一寫多讀策略,備機房把寫流量轉入主機房,只有主機房會把WAL更新到KV儲存上。
廣域網容災部署,如新加坡和美國的兩臺機器,延遲較高。follow了mysql的思想,每次寫入在本地寫入成功後,會被轉化為binlog,再傳送給其他單元;並通過hybrid logical clock保證各單元對於一條邊的操作順序一致性。
5. 離線線上資料流融合
匯入存量資料、寫入線上資料,將二者整合在公司內部資料平臺進行離線資料分析,具體流程如圖。
今天的分享就到這裡,謝謝大家。
本文首發於微信公眾號“DataFunTalk”。