作者: arcosx 百度高级研发工程师,负责机器学习平台研发
UDF (User-defined function) 意为用户提供或定义的函数。在数据库领域,UDF 代表一种机制:通过添加一个函数来扩展数据库服务的功能。
WebAssembly (Wasm) 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。近年来 Wasm 不仅仅在前端领域广泛应用,也开始在服务端大展身手。其在服务端提供了接近原生代码的性能,但是又不损失安全性。
笔者在参加 NebulaGraph 举办的 Nebula Hackathon 2021 中将上述两种技术词汇结合起来,为其实现了第一个生产级用户自定义函数引擎,荣获该项赛事的最佳项目奖。本文是笔者在比赛结束后的总结与回顾,旨在交流与分享 Wasm 在服务端作为拓展机制的潜能,抛砖引玉。
当下,有非常多的主流数据库或数据处理系统都存在 UDF 实现,简要列举如下:
综上,不难得出,UDF 函数作为一种有效的拓展机制已经在各个数据系统间广泛应用。
而在 Nebula Hackathon 2021 比赛过程中,考虑到 Nebula Graph 在当时的最新版本 (2.6.1) 不支持 UDF,笔者的比赛初衷即是基于 Nebula 优秀的能力底座,为 Nebula 实现超越业界现有实现的 UDF 引擎。
Wasm 作为前端领域诞生的技术,早已在前端广泛应用,例如 Ebay 基于 Wasm 为 Web 端提供条形码扫描功能。AutoCAD Web 通过 Wasm 将其 Native 应用移植到 Web 平台。
Wasm 在服务端的应用方兴未艾,有非常多的应用尝试在服务端使用 Wasm 技术,例如 YoMo 将 WasmEdge 嵌入到实时数据流中,然后通过 WasmEdge 进行数据的 AI 推理,也是将 Wasm 作为拓展机制使用。此外,也可以将 WasmEdge 部署在腾讯云函数上,不用运维,自动伸缩,按使用付费。
有一些开源项目选择将 Wasm 运行时嵌入程序内作为程序的拓展机制。
必须一提的是,在 TiDB 2020 Hackathon 中 ' or 0=0 or ' 团队基于 WASM 实现了 TiDB 的用户自定义函数是笔者已知的最早将 Wasm 和数据库结合的案例,也是笔者在 Nebula Hackathon 2021 上直接的灵感来源。
珠玉在前,可以看到 WASM 在服务端作为拓展机制的潜能无限。
在讨论具体实现之前,笔者先结合图数据库本身的业务属性以及商业模式,来谈谈需求。
首先要回归本质,UDF 作为一种拓展机制, 笔者认为在讨论 UDF 设计的时候不可以忽略拓展机制需要具备的需求。
此外,作为一个以图数据库为底座的 UDF,也应该具备以下特质。
虽然在前文已经详述了笔者的最终方案是使用 Wasm,笔者认为仍有必要对 Wasm 的使用进行探讨。
下面的图片展示了多种 UDF 方案的实现对比。笔者可以在前文列举过很多数据系统的 UDF 实现,他们都是内部实现 DSL(例如Flink)、外挂 so(例如MySQL)、内嵌脚本这几个实现方式,有其一定的局限性。
接下来我们从是否具备完备的 C++ SDK、性能体积、社区活跃度、是否好嵌入以及文档详细程度上选择具体 Wasm 虚拟机,最终我们选择了 WasmEdge,Wasmtime。
编译工具链:作用是快速转换已有代码成 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 特性管理等。
下图是上文所述的函数创建,调用,删除的主要流程图。
碍于篇幅,本文不对模块内具体实现进行介绍,感兴趣的读者可以阅读开源代码来了解细节。
下面是比赛时候的2个 Demo 视频,充分展示了整个流程,可以看到 Wasm 作为拓展的能力。Demo 基于 WasmEdge Runtime,可以在开源代码中找到。
这个 Demo 展示的是一个非常简单的字符串拼接,这个函数并不是 Nebula 图数据库自有的。
视频 demo 见:https://www.bilibili.com/video/BV1JP4y1u7LJ/
传入一个字符串 world
,返回拼接后的字符串 hello world
。需要注意的是,这个拓展尝试传递字符串到Wasm虚拟机中,而 WebAssembly 规范并不支持字符串。我们这里使用 wasm_bindgen 工具编译 Rust 程序为 Wasm程序,通过将字符串映射到虚拟机的内存地址上来完成。这一部分的技巧可以参阅 WasmEdge 文档或 Demo 的开源代码。
这个 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 拓展机制应该具有特质的期望。
没有实现更加动态的函数以及运行时管理以及运行机制
从 Wasm 函数内部调用 Nebula 图数据库的接口没有实现
性能情况的考察
缺乏更加多样复杂的功能
从技术角度考虑,面对越来越多的拓展场景,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
|