如何用 Wasm 為資料庫增加 UDF 功能

語言: CN / TW / HK

作者: arcosx 百度高階研發工程師,負責機器學習平臺研發

前言

UDF (User-defined function) 意為使用者提供或定義的函式。在資料庫領域,UDF 代表一種機制:通過新增一個函式來擴充套件資料庫服務的功能。

WebAssembly (Wasm) 是一個可移植、體積小、載入快並且相容 Web 的全新格式。近年來 Wasm 不僅僅在前端領域廣泛應用,也開始在服務端大展身手。其在服務端提供了接近原生程式碼的效能,但是又不損失安全性。

筆者在參加 NebulaGraph 舉辦的 Nebula Hackathon 2021 中將上述兩種技術詞彙結合起來,為其實現了第一個生產級使用者自定義函式引擎,榮獲該項賽事的最佳專案獎。本文是筆者在比賽結束後的總結與回顧,旨在交流與分享 Wasm 在服務端作為拓展機制的潛能,拋磚引玉。

需求分析

User-defined function 廣泛應用

當下,有非常多的主流資料庫或資料處理系統都存在 UDF 實現,簡要列舉如下:

  • 關係型資料庫領域,絕大部分關係型資料庫,例如 MySQL,Microsoft SQL Server 等支援並且有其標準。關係型資料庫標準中,將 UDF 分為標量函式 (Scalar Function) 和表值函式 (Table Function) 兩類。呼叫標量函式只返回一個單一的值,而表值函式則返回包含多行多列的表格。
  • 非關係型資料庫領域,主流 NoSQL ,例如 MongoDB、Cassandra 也支援其各具特色的 UDF,例如 MongoDB 的 UDF 將 Javascript 的函式嵌入進去Cassandra 支援上傳 jar 包作為 UDF,這些特色的嵌入方式與這些資料庫的實現技術密切相關。
  • 大資料計算引擎:批資料計算引擎 Spark ,流資料計算引擎 Flink 支援。這些大資料計算引擎為抽象計算模型,降低使用者使用門檻,都會設計的一套符合標準 SQL 語義的開發語言,這其中也包含 UDF 函式。在 Flink 引擎中,使用者可以使用 UDF 來在查詢語句裡呼叫難以用其他方式表達的頻繁使用或自定義的邏輯
  • 雲產品,非常多的雲廠商都在發力這個方向,例如 Google BigQuery 允許使用者從 SQL 查詢呼叫以 JavaScript 編寫的程式碼,阿里雲的 MaxCompute 可以直接將 Java 或 Python 程式碼作為 UDF 嵌入 SQL。
  • 圖資料庫領域,事實上的圖資料庫查詢語句標準 openCypher 存在 UDF 的定義,各大圖資料庫也都支援,例如 Tiger Graph 支援強繫結 C++ 語言的查詢 UDF  ,Neo4j 支援外掛 Java 指令碼式的 UDF

綜上,不難得出,UDF 函式作為一種有效的拓展機制已經在各個資料系統間廣泛應用。

而在 Nebula Hackathon 2021 比賽過程中,考慮到 Nebula Graph 在當時的最新版本 (2.6.1) 不支援 UDF,筆者的比賽初衷即是基於 Nebula 優秀的能力底座,為 Nebula 實現超越業界現有實現的 UDF 引擎。

Wasm-based plugin 珠玉在前

Wasm 作為前端領域誕生的技術,早已在前端廣泛應用,例如 Ebay 基於 Wasm 為 Web 端提供條形碼掃描功能。AutoCAD Web 通過 Wasm 將其 Native 應用移植到 Web 平臺。

Wasm 在服務端的應用方興未艾,有非常多的應用嘗試在服務端使用 Wasm 技術,例如 YoMo 將 WasmEdge 嵌入到實時資料流中,然後通過 WasmEdge 進行資料的 AI 推理,也是將 Wasm 作為拓展機制使用。此外,也可以將 WasmEdge 部署在騰訊雲函式上,不用運維,自動伸縮,按使用付費。

有一些開源專案選擇將 Wasm 執行時嵌入程式內作為程式的拓展機制。

  • Istio 專案中的 Envoy 元件基於 Wasm 新增動態可擴充套件性代理過濾,大大簡化了 Envoy 二次開發和功能增強的複雜度12。
  • MOSN 專案採用 Wasm 技術,給 MOSN 實現了一個安全隔離的沙箱環境,讓擴充套件程式能夠執行在隔離沙箱之中。

必須一提的是,在 TiDB 2020 Hackathon 中 ' or 0=0 or ' 團隊基於 WASM 實現了 TiDB 的使用者自定義函式是筆者已知的最早將 Wasm 和資料庫結合的案例,也是筆者在 Nebula Hackathon 2021 上直接的靈感來源。

珠玉在前,可以看到 WASM 在服務端作為拓展機制的潛能無限。

業務需求

在討論具體實現之前,筆者先結合圖資料庫本身的業務屬性以及商業模式,來談談需求。

  • 複雜邏輯自舉減少資料互動:Nebula 圖資料庫本身不支援某些複雜函式(例如 JSON Parse),或者是某些包含業務系統的邏輯處理(如 Join、 Filter)。通過 UDF 業務方可以實現一些通用邏輯的下沉,可以避免業務系統反覆與資料庫互動,減少網路通訊,增強效能。
  • 公有云平臺增強可拓展能力:和現在非常多雲廠商提供的雲資料庫產品一樣,Nebula 圖資料庫存在名為 Nebula Graph Cloud 全託管資料庫即服務(DBaaS)產品。作為公有云平臺,為了統一管理運維的需要,一般都是部署二進位制軟體標品,不會對不同使用者做定製。通過 UDF 可以讓使用者自己去完成自己的邏輯,但是這個邏輯需要能夠有限控制在一個合理的空間內,既能滿足使用者的需求,也不會造成安全問題,比如使用者不能穿透資料庫到資料庫所在物理機,或者是通過資料庫發起一些網路攻擊,但是他卻可以看到自己的資料庫一些內部指標,甚至把自己的業務邏輯嵌入到資料庫內。
  • 私有化場景降低運維需求:作為開源軟體,在 Nebula 圖資料庫賦能諸多企業業務騰飛的同時,不可避免會陷入到私有化維護的場景。深度使用 Nebula 的客戶必然也有自己的拓展需求,這方面的拓展需求會花費 Nebula 的研發成本(不是所有的客戶都具備 Nebula 的研發能力)去維護一些偏離主分支的程式碼,而且有些客戶有自己的私密需求。Nebula 圖資料庫必須具備非常靈活的拓展機制來減少陷入維護深淵。在雲原生趨勢下,對廠商和客戶擴充套件語言應該是包容的,不應該將使用者的技術棧繫結到 Nebula 圖資料庫的原生語言 C++ 上。UDF 引擎允許多語言編寫程式碼打包成 Wasm 檔案,嵌入 Nebula 圖資料庫中執行。可以說,在私有化場景下,Nebula 圖資料庫僅需保證核心元件穩定,廠商私有程式碼不合併到 Nebula 中,應該具備按需擴充套件能力即可。

實現分析

痛點與期望

首先要回歸本質,UDF 作為一種拓展機制, 筆者認為在討論 UDF 設計的時候不可以忽略拓展機制需要具備的需求。

  • 安全隔離需求:程式碼的風險管控,執行時的程式能力限制、呼叫資源限制 。通過隔離沙箱避免 UDF 程式碼給 Nebula 執行造成的安全風險,如使用者編寫的 UDF 出現了問題,並不會影響 Nebula 本身的執行。
  • 彈性效率:支援動態下發、安裝/解除安裝這些基礎能力
  • 效能體積:接近原生程式碼 (C++) 的效能、均衡、功能和複雜度與大小成正比。
  • Write once, run anywhere,執行平臺相容 x86、ARM 等架構。

此外,作為一個以圖資料庫為底座的 UDF,也應該具備以下特質。

  • 可拓展性,可使用多種語言 (C/C++、Rust、Python、TypeScript) 編寫函式邏輯,亦可引用其他 UDF 實現組合複用。
  • 複用現有基礎設施:多語言支援的能力,允許模組化程式設計,封裝函式組合呼叫,甚至直接複用社群 UDF。
  • 圖資料結合:支援圖資料中獨有的資料結構(如頂點、邊)作為函式內部可用的資料結構
  • 利用雲的計算資源:利用現代雲端計算基礎設施拓展計算能力,可對接雲上函式計算 (AWS Lamda /阿里雲 Function Compute),釋放海量算力。
  • 更為複雜的功能:超越傳統的 UDF 簡單等資料聚合、字串拼接需求,引入複雜計算邏輯,比如機器學習推理、ID-Mapping 等

實現討論

雖然在前文已經詳述了筆者的最終方案是使用 Wasm,筆者認為仍有必要對 Wasm 的使用進行探討。

下面的圖片展示了多種 UDF 方案的實現對比。筆者可以在前文列舉過很多資料系統的 UDF 實現,他們都是內部實現 DSL(例如Flink)、外掛 so(例如MySQL)、內嵌指令碼這幾個實現方式,有其一定的侷限性。

接下來我們從是否具備完備的 C++ SDK、效能體積、社群活躍度、是否好嵌入以及文件詳細程度上選擇具體 Wasm 虛擬機器,最終我們選擇了 WasmEdgeWasmtime

具體實現

總架構圖

單模組介紹

編譯工具鏈:作用是快速轉換已有程式碼成 Wasm。這個模組比較雜亂,是我們開發除錯使用 Wasm 程式,面向比賽 Demo 使用的。

語法解析:基於 Flex 與 Bison 實現建立函式,刪除函式的 SQL 語句。實現語法可以見下圖,我們這裡考慮到 WebAssembly 文字格式(wat),Wasm 二進位制檔案兩種主流的格式。以及其載入方式,分為兩種,第一種是方便在終端裡直接輸入的 wat base64 編碼以及 Wasm 二進位制檔案 base64 編碼,執行大小在 KB 級別的程式可以直接引入,第二種是 MB 級別的 Wasm 程式二進位制檔案,支援通過 HTTP 地址引入以及本地檔案地址引入。

函式管理:負責對 Wasm 虛擬機器進行統一管理,提供函式的動態更新、載入、解除安裝功能。也可以說是 Nebula 其他系統與 Wasm 拓展元件互動的中間膠水。

虛擬機器:這裡主要是引入 Wasmtime、WasmEdge 兩個虛擬機器的 C++ SDK,通過呼叫 SDK 來實現 wat 程式碼編譯、Wasm 二進位制檔案編譯執行、沙箱例項管理、WASI 特性管理等。

流程介紹

下圖是上文所述的函式建立,呼叫,刪除的主要流程圖。

  • 當有函式建立時,將語法解析後得到的函式名稱(name),函式執行引數(params),函式返回引數(return type),函式本體(code or path)。對通過 base64 解碼、HTTP 下載、檔案讀取操作三種手段數本體進行載入,載入後呼叫虛擬機器介面進行編譯,得到編譯後的執行上下文(Run context)。將函式執行引數、返回引數、執行上下文進行組合拼接成一個可以後續直接虛擬機器使用的函式執行沙箱(Sandbox)。以函式名稱為 Key、函式執行沙箱為 Value 儲存在記憶體雜湊表(Mem Table) 中。
  • 在函式被呼叫時,以函式名稱為 Key 在記憶體雜湊表找到函式執行沙箱,對傳入引數進行引數校對,將Nebula 內的資料型別(C++ Data Struct)轉換成虛擬機器可以接受的引數(Wasm Data Struct),請求虛擬機器執行函式執行沙箱中的執行上下文,得到執行結果,將返回引數轉換成 Nebula 內的資料型別,返回上層系統。
  • 當有函式刪除時,即刪除記憶體雜湊表中的函式執行沙箱。

礙於篇幅,本文不對模組內具體實現進行介紹,感興趣的讀者可以閱讀開原始碼來了解細節。

Demo

下面是比賽時候的2個 Demo 視訊,充分展示了整個流程,可以看到 Wasm 作為拓展的能力。Demo 基於 WasmEdge Runtime,可以在開原始碼中找到。

基於 WasmEdge Runtime 的字串拼接程式

這個 Demo 展示的是一個非常簡單的字串拼接,這個函式並不是 Nebula 圖資料庫自有的。

視訊 demo 見:https://www.bilibili.com/video/BV1JP4y1u7LJ/

傳入一個字串 world,返回拼接後的字串 hello world。需要注意的是,這個拓展嘗試傳遞字串到Wasm虛擬機器中,而 WebAssembly 規範並不支援字串。我們這裡使用 wasm_bindgen 工具編譯 Rust 程式為 Wasm程式,通過將字串對映到虛擬機器的記憶體地址上來完成。這一部分的技巧可以參閱 WasmEdge 文件或 Demo 的開原始碼。

基於 WasmEdge Runtime 的飛書聊天機器人

這個 Demo 展示如何從資料庫傳送訊息到飛書機器人,首先我們通過飛書軟體獲取到聊天機器人的介面,這個介面是 HTTP Post 請求。接下來基於 WasmEdge 提供的 wasi socket 能力,將具有網路請求功能的UDF嵌入到資料庫中,執行函式,可以看到程式成功請求飛書成功輸出內容。

視訊 demo 見:https://www.bilibili.com/video/BV1XL4y1T72X/

之所以考慮這個演示程式是因為資料庫傳統 UDF 的功能主要是自定義資料聚合,儲存過程這些沒有脫離資料庫原本業務屬性與能力的功能。筆者認為展示一個在功能豐富性完全超越資料庫傳統 UDF 的 Wasm-based plugin 機制更有實際意義。如果業務系統能夠合理的在多個系統內部裝載 Wasm 的拓展作為 Hook(鉤子)機制,使用者就可以在不大量改動原業務系統上加入自己的 Wasm 拓展程式,可以實現非常豐富的自定義功能,我們可以考慮用這個來提高程式的可觀測性,例如在資料庫收到一個慢查詢語句時,觸發 Hook,將慢查詢情況通過內部 IM 傳送給資料庫管理員,相信這裡的想象力是非常多的。

缺陷與前景展望

缺陷

在比賽後,筆者也有非常多的遺憾,下面的缺陷討論實際上更像是筆者對一個完備的 Wasm 拓展機制應該具有特質的期望。

沒有實現更加動態的函式以及執行時管理以及執行機制

  • 由於是在圖資料庫內做的 UDF,優先考慮了以 SQL 形式來實現函式的增刪改。在筆者的最初設想中,整體的 Wasm UDF 功能應該是存在一個動態配置,這個配置可以限制沙箱例項個數、使用的虛擬機器、虛擬機器的許可權(例如 WASI 提供的網路,IO 許可權)等。
  • 執行機制上,在設計之初,筆者也考慮到將一部分具體函式執行放在雲上進行執行(例如 AWS Lamda),將結果回傳到資料庫,調研發現 WasmEdge Runtime 提供了網路能力。但是到具體實現時,需要考慮的點還是比較多的,比如如何協同這種“雲 UDF ”和本地 UDF 的互動、如何規劃函式執行回撥等,最終沒有完成這個工作。

從 Wasm 函式內部呼叫 Nebula 圖資料庫的介面沒有實現

  • 筆者的工作侷限在 Nebula 圖資料庫呼叫 Wasm 函式,這是最直接的 Wasm 使用方式,但是 Wasm 本身可以通過介面來呼叫 Host Function,筆者也在 WasmEdge C SDK 的 Doc 中找到了類似的方法。
  • 但是受制於 Wasm 的安全機制,任何對外部資源的訪問必須通過匯入模組來間接實現,需要將 Nebula 圖資料庫的內部 API 的入參出參上再加一層資料轉換傳入到 WASM 內部,需要統一抽象好外部的資料結構在 Wasm 程式內的對映。
  • 這些工作是富有挑戰的,也是現在將 Wasm UDF 落地的比較難的原因。如果可以做到這些,我們就可以不僅僅侷限在 UDF 這一個小的方向,可以在上層應用的多個程式上嵌入我們的 Wasm 沙箱,做到 Hook 的機制。

效能情況的考察

  • 沒有考察 Wasm 程式在處理大規模資料時候的效能情況。

缺乏更加多樣複雜的功能

  • 現有應用遷移適配需要一定工作,筆者嘗試將 jq (一個 C 編寫的 JSON 字串處理器)嵌入到圖資料庫上,來讓圖資料庫直接支援 JSON 型別字串的處理,但是嘗試過程中發現編譯較為繁瑣,且匯入到 VM 中執行不順利。
  • 在考察過 WasmEdge Runtime 支援使用 Tensorflow 做機器學習推理後,筆者設想在 Wasm 內部載入一個結構化資料的模型,讀入 Nebula 圖資料庫儲存的資料,跑一個簡單的聚類或者回歸,做一些和上層圖資料相關的推理服務,或者將一個完整的圖演算法編譯成 Wasm。這些功能都可以體驗 Wasm 作為一種拓展機制勝過傳統 UDF 的能力。

前景

從技術角度考慮,面對越來越多的拓展場景,Wasm 可以提供完整可靠的隔離環境,拓展程式碼無法對上層應用的執行造成安全風險。雖然V8、JVM、Lua等虛擬機器也具備相同的安全能力,但是效能體積、多語言支援、拓展性上沒辦法超越 Wasm。

雖然筆者在文中主要介紹的是圖資料庫 UDF 的實現,但是經驗同樣適用於任何想用 Wasm 做擴充套件的專案,得益於 WasmEdge、Wasmtime等虛擬機器已經遮蔽了非常多的底層執行細節,大部分工作集中在宿主機與 Wasm 擴充套件程式之間的互動、函式註冊刪除、函式呼叫、資料傳輸等,筆者認為想要非常迅速的接入 Wasm,體驗 Wasm 帶來的拓展體驗是非常容易的。

本文中的 Nebula 圖資料庫所代表的雲上資料服務或者說軟體服務,都可以說是一種 SaaS 平臺,如果 SaaS 平臺允許類似 UDF 這樣的使用者自定義程式碼直接上傳到平臺本身,開發者就不需要維護處理回撥的中間層來減少資料鏈路,也不用管理基礎設施,而且更好的複用 SaaS 平臺現有 API,安全高效能的同時也保證了更高的可拓展性。

關於 WasmEdge

WasmEdge 是輕量級、安全、高效能、實時的軟體容器與執行環境。目前是 CNCF 沙箱專案。WasmEdge 被應用在 SaaS、雲原生,service mesh、邊緣計算、汽車等領域。

 ✨ GitHub:https://github.com/WasmEdge/WasmEdge

 💻 官網:https://wasmedge.org/

 👨‍💻‍ Discord 群:https://discord.gg/JHxMj9EQbA

 👨‍💻‍ 文件:https://wasmedge.org/book/en