WebAssembly 用户定义函数
ClickHouse 支持创建用 WebAssembly 编写的用户定义函数 (UDF) 。这使你可以将使用 Rust、C、C++ 等语言编写的自定义逻辑编译为 WebAssembly 模块并执行。
概述
WebAssembly 模块是一个已编译的二进制文件,其中包含一个或多个可以从 ClickHouse 调用的函数。 可以将模块视为一个库或共享对象 (shared object) ,它被加载一次并可多次复用。
包含 UDF 的 WebAssembly 模块可以使用任何能编译为 WebAssembly 的语言编写,例如 Rust、C 或 C++。
编译为 WebAssembly 的代码 (“guest” 代码) 并由 ClickHouse (“host”) 执行时,会运行在一个沙箱环境中,只能访问专用的内存空间。
Guest 代码会导出 ClickHouse 可以调用的函数——包括实现自定义逻辑的函数 (用于定义 UDF) ,以及内存管理和 ClickHouse 与 WebAssembly 代码之间数据交换所需的辅助函数。
代码应被编译为“freestanding” WebAssembly (即 wasm32-unknown-unknown) ,且不得依赖任何操作系统或标准库。同时,仅支持默认的 32 位 WebAssembly 目标 (不支持 wasm64 扩展) 。
模块必须遵循一种受支持的通信协议 (ABI) ,以便与 ClickHouse 交互。
编译完成后,可以通过将模块的二进制代码插入到 system.webassembly_modules 表中,将其加载到 ClickHouse 中。
之后,可以使用 CREATE FUNCTION ... LANGUAGE WASM 语句创建引用该模块导出函数的 UDF。
先决条件
在 ClickHouse 配置中启用 WebAssembly 支持:
可用的引擎实现:
快速开始
本示例演示了通过实现 Collatz 猜想 计算器来创建 WebAssembly UDF 的完整工作流程。
我们将使用 WebAssembly 文本格式 (WAT) 来编写代码,它是 WebAssembly 的人类可读表示形式,因此在这个阶段不需要使用任何编程语言。
ClickHouse 要求模块为二进制格式,因此我们将使用转译器将 WAT 转换为 WASM。
要执行此转换,可以使用 WebAssembly Binary Toolkit (WABT) 中的 wat2wasm,或使用 wasm-tools 中的 parse 命令。
在上面的代码片段中,我们使用 FORMAT RawBlob 将二进制 WASM 代码通过管道直接传入 ClickHouse 客户端,并插入到 system.webassembly_modules 表中。
然后,我们定义一个引用该模块导出 steps 函数的 UDF:
请注意,我们在 :: 之后指定的是模块中的函数名称,因为它与 UDF 的名称不同。
现在我们可以在查询中使用 collatz_steps 函数了:
number 列被显式转换为 UInt32,因为 WebAssembly 函数要求类型与 CREATE FUNCTION 语句中指定的签名精确匹配。
在结果中,我们得到了从 1 到 100 各个数的 Collatz 步数序列,对应于 OEIS 中的序列 A006577。
通过 system 系统表管理 WASM 模块
WebAssembly 模块存储在 system.webassembly_modules 表中,其结构如下:
- 列
nameString — 模块名称。必须非空且仅包含单词字符。codeString — 原始二进制 WASM 代码。只写,读取时返回空字符串。hashUInt256 — 模块二进制文件的 SHA256 (如果模块存在于磁盘但尚未加载,则为零) 。
模块管理是通过对该表执行标准 SQL 操作来完成的:
插入模块
(可选) 提供完整性哈希:
如果提供的哈希值与模块代码计算得到的 SHA256 不匹配,插入操作将失败。在从 S3 或 HTTP 等外部来源加载模块时,这会非常有用。
列出模块
删除模块
删除操作通过执行 DELETE FROM system.webassembly_modules WHERE name = '...' 语句来完成。
每条语句仅支持按精确名称删除一个模块。
如果有任何现有的 UDF 引用了该模块,则删除操作会失败,因此必须先删除这些 UDF。
创建 WebAssembly UDF
语法:
参数:
function_name: ClickHouse 中的函数名。可以与模块中导出的函数名不同。FROM 'module_name' :: 'source_function_name': 已加载 WASM 模块的名称,以及在该 WASM 模块中要使用的函数名 (默认值为function_name)ARGUMENTS: 参数名称和类型列表 (名称可选,并用于支持命名字段的序列化格式)ABI: Application Binary Interface (应用二进制接口) 版本ROW_DIRECT: 直接类型映射,逐行处理BUFFERED_V1: 采用基于块 (block) 的处理并进行序列化
SHA256_HASH: 用于校验的期望模块哈希 (如果省略则自动填充) ,可用于确保在不同副本上加载的是正确的 WASM 模块。SETTINGS: 每个函数的设置serialization_formatString — 当 ABI 需要时使用的序列化格式。默认值:MsgPack。
ABI 版本
要与 ClickHouse 交互,WebAssembly 模块必须遵循受支持的 ABI (Application Binary Interfaces,应用二进制接口) 之一。
ROW_DIRECT:直接类型映射 (仅支持原始类型Int32、UInt32、Int64、UInt64、Float32、Float64)BUFFERED_V1:通过序列化处理的复杂类型
ABI ROW_DIRECT
针对每一行直接调用导出的 WASM 函数。
- 参数和返回类型必须是数值类型
Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128。 - 此 ABI 不支持字符串。
- 函数签名必须与 WASM 导出签名匹配 (
i32/i64/f32/f64/v128) 。 - 模块不需要导出任何辅助函数。
例如具有如下签名的函数:
可以按如下方式创建:
WebAssembly 不区分有符号和无符号参数,而是通过不同的指令来解释这些值。因此,参数的位宽必须完全一致,而是否带符号则由函数内部的操作来决定。
ABI BUFFERED_V1
此 ABI 为实验性特性,在未来版本中可能发生变化。
通过在 WASM 内存中进行 (反) 序列化,一次性处理整个数据块。支持任意参数和返回类型。
序列化后的数据会被复制到 WASM 内存中,作为指向缓冲区的指针传递给 UDF 函数 (缓冲区由数据指针和数据大小组成) ,同时还会传递输入中的行数。因此,WASM 运行时中的用户自定义函数始终接收两个 i32 参数,并返回一个 i32 值。
Guest 代码对数据进行处理,并返回一个指向结果缓冲区的指针,其中包含序列化后的结果数据。
Guest 代码必须提供两个函数,用于创建和销毁这些缓冲区。
C 示例定义:
使用 Rust 开发 UDF 的说明
针对 Rust 程序,我们提供了辅助 crate clickhouse-wasm-udf,用于简化在 ClickHouse 中开发 WebAssembly UDF 的流程。该 crate 提供了用于内存管理的函数,因此无需手动实现 clickhouse_create_buffer 和 clickhouse_destroy_buffer 函数,只需将该 crate 添加为依赖即可。此外,还提供了 #[clickhouse_wasm_udf] 宏,用于将常规 Rust 函数封装为所需的 ABI 格式。
借助该 crate,可以像下面这样编写 UDF:
这些宏会生成用于接收和返回缓冲区结构的包装函数,并使用 serde 自动处理序列化/反序列化。
模块可用的 Host API
模块可以导入并使用以下宿主函数:
clickhouse_server_version() -> i64— 以整数形式返回 ClickHouse 服务器版本 (例如 v25.11.1.1 对应 25011001) 。clickhouse_throw(ptr: i32, size: i32)— 使用提供的消息抛出错误。接受指向错误消息字符串所在内存位置的指针以及字符串的长度。clickhouse_log(ptr: i32, size: i32)— 将消息记录到 ClickHouse 服务器文本日志中。clickhouse_random(ptr: i32, size: i32)— 使用随机字节填充内存。
设置
以下查询级别设置用于控制 WebAssembly UDF 的执行:
-
webassembly_udf_max_fuel— 每个 WebAssembly UDF 实例执行可使用的 fuel 上限。每条 WebAssembly 指令都会消耗一定数量的 fuel。设置为 0 表示不限制。 -
webassembly_udf_max_memory— 每个 WebAssembly UDF 实例的内存限制 (以字节为单位) 。 -
webassembly_udf_max_input_block_size— 在单个数据块中传递给 WebAssembly UDF 的最大行数。设置为 0 表示一次性处理所有行。 -
webassembly_udf_max_instances— 每个函数可并行运行的 WebAssembly UDF 实例最大数量。
示例用法: