WebAssembly 用户自定义函数
概述
wasm32-unknown-unknown) ,不依赖任何操作系统或标准库。此外,仅支持默认的 32 位 WebAssembly 目标 (不支持 wasm64 扩展) 。
该模块必须遵循一种受支持的通信协议 (ABI) 才能与 ClickHouse 交互。
编译完成后,需要将模块的二进制代码插入 system.webassembly_modules 表中,以将其加载到 ClickHouse。
之后,你可以使用 CREATE FUNCTION ... LANGUAGE WASM 语句创建引用该模块导出函数的 UDF。
前置条件
快速入门
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 相对应。
通过系统表管理 WASM 模块
system.webassembly_modules 表中,其结构如下:
- 列
nameString — 模块名称。不能为空,且只能包含词字符。codeString — 原始 WASM 二进制代码。仅可写,读取时返回空字符串。hashUInt256 — 模块二进制文件的 SHA256 (如果文件已存在于磁盘上但尚未加载,则为零) 。
插入模块
在 cluster 中分发模块
system.webassembly_modules 是按实例划分的表——INSERT 只会写入处理该 connection 的副本。INSERT statement 没有 ON CLUSTER 这种形式,因此后续执行 CREATE FUNCTION ... ON CLUSTER 时,会在没有该模块的副本上失败:
insert 操作发送到每个节点,请写入 cluster 表函数,而不是本地的 system.webassembly_modules 表:
这种方式依赖底层分布式写入路径访问每个分片中的所有副本,而这只有在集群配置为
internal_replication=false 时才会发生。启用 internal_replication=true 时 (对于使用 ReplicatedMergeTree 自行完成复制的集群,这是默认设置) ,insert 只会发送到每个分片中一个健康的副本,而 system.webassembly_modules 不会通过这条路径进行复制,因此某些副本仍然会缺少该模块。在这种配置下,你需要分别向每个副本执行 insert,例如遍历 system.clusters,并通过每台主机的 remote(...) 写入,或者将二进制文件复制到每台主机上的 user_scripts/wasm/ 目录中。你可以通过 SELECT cluster, shard_num, internal_replication FROM system.clusters 查看集群的 internal_replication。CREATE FUNCTION ... ON CLUSTER 将成功执行:
clusterAllReplicas 验证该模块是否已在所有节点上加载:
system.webassembly_modules 执行插入时,对于相同的 (name, hash) 组合是幂等的,因此重新运行分发式插入是安全的,也是在某个副本被替换后修复状态的合理方式。请注意,新加入的服务器不会自动补收已有模块——你必须针对更新后的集群重新运行插入,或者将该 binary 放到新 host 上的 user_scripts/wasm/ 目录中。
列出模块
删除模块
DELETE FROM system.webassembly_modules WHERE name = '...' 语句删除模块。
该谓词必须是精确匹配的 name = 'literal',或用于删除所有名称匹配该模式的模块的 name LIKE 'pattern';不接受其他形态。
创建 WebAssembly UDF
function_name:ClickHouse 中的函数名称。可能与模块中导出的函数名不同。FROM 'module_name' :: 'source_function_name':已加载的 WASM 模块名称,以及要使用的 WASM 模块中的函数名称 (默认为 function_name)ARGUMENTS:参数名称和类型列表 (名称为可选项,用于支持命名字段的序列化格式)ABI:应用程序二进制接口版本ROW_DIRECT:直接类型映射,按行处理BUFFERED_V1:基于块的处理,带序列化ASSEMBLYSCRIPT:适用于由 AssemblyScript 编译器生成的模块的按行处理。数值类型会映射到 AssemblyScript 基本类型;ClickHouseString会映射到 AssemblyScriptstring。
DETERMINISTIC:将该函数声明为确定性的——对于相同输入始终返回相同输出。指定后,当所有参数都是常量时,ClickHouse 可能会对调用进行常量折叠:函数会在查询分析阶段求值一次,结果会复用于每一行。SHA256_HASH:用于校验的预期模块哈希 (若省略则自动填充) ,可用于确保不同副本上加载的是正确的 WASM 模块。SETTINGS:函数级设置serialization_formatString —— ABI 要求的序列化格式。支持的值:MsgPack、JSONEachRow、CSV、TSV、TSVRaw、RowBinary和Buffers。默认值:MsgPack。像Buffers这样基于块的格式必须返回单列,且该列的类型必须与声明的函数签名匹配。webassembly_udf_enable_fuelBool —— 为该函数启用有限 fuel 预算。默认值:true。当为false时,查询级设置webassembly_udf_max_fuel会被该函数忽略。使用wasmtimeengine 时,禁用 fuel 限制可能会提升性能。但是,对于不受信任或有缺陷的 guest 代码,这可能会增加失控执行的风险。
ABI 版本
ROW_DIRECT:直接类型映射 (仅支持基本类型Int32、UInt32、Int64、UInt64、Float32、Float64)BUFFERED_V1:通过序列化处理复杂类型ASSEMBLYSCRIPT:与 AssemblyScript 模块按行互操作;支持数值类型和String。
ABI ROW_DIRECT
- 参数和返回值类型均为数值类型:
Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128。 - 此 ABI 不支持字符串。
- 签名必须与 WASM 导出一致 (
i32/i64/f32/f64/v128) 。 - 模块无需导出任何辅助函数。
ABI BUFFERED_V1
此 ABI 仍处于 Experimental 阶段,未来版本中可能会发生变化。
i32 参数,并返回单个 i32 值。
guest 侧代码处理数据后,会返回一个指向结果缓冲区的指针,其中包含序列化后的结果数据。
guest 侧代码必须提供两个函数,用于创建和销毁这些缓冲区。
ABI ASSEMBLYSCRIPT
-
数值类型:
Int8/UInt8、Int16/UInt16(在边界处扩展为i32) 、Int32/UInt32、Int64/UInt64、Float32、Float64 -
String— 映射为 AssemblyScriptstring(WASM 内存中的 UTF-16) 。ClickHouse 会自动处理 UTF-8 ↔ UTF-16 的转换。 - 不支持将自定义 AssemblyScript 类用作参数类型或返回类型——它们的运行时类 id 在不同编译结果之间并不稳定 (参见 AssemblyScript#2982) 。
__new、__pin 和 __unpin。标准的输入/输出字符串处理依赖这些。推荐的调用方式:
env.abort,用于处理运行时陷阱 (如内存不足、边界检查等) 。ClickHouse 会自动提供此导入:当触发 abort 时,当前查询会因 WASM_ERROR Exception 而失败,并包含已解码的 AssemblyScript 消息和源位置。
示例:
asc 编译并将生成的 .wasm 加载到 system.webassembly_modules 后,可按如下方式声明 UDF:
关于用 Rust 开发 UDF 的说明
clickhouse_create_buffer 和 clickhouse_destroy_buffer 函数,只需将该 crate 添加为依赖即可。此外,还提供了宏 #[clickhouse_wasm_udf],可将普通的 Rust 函数封装为所需的 ABI 格式。
借助这个 crate,你可以像这样编写 UDF:
serde 自动处理序列化和反序列化。
模块可用的宿主 API
clickhouse_server_version() -> i64— 以整数形式返回 ClickHouse server 版本 (例如,v25.11.1.1 对应 25011001) 。clickhouse_throw(ptr: i32, size: i32)— 使用给定的消息抛出错误。接受一个指向存放错误消息字符串的内存位置的指针,以及该字符串的大小。clickhouse_log(ptr: i32, size: i32)— 将消息写入 ClickHouse server 的文本日志。clickhouse_random(ptr: i32, size: i32)— 用随机字节填充内存。env.abort(message: i32, fileName: i32, line: i32, column: i32)— 为与 AssemblyScript 兼容的模块提供。调用它 (或触发会调用它的 AssemblyScript 运行时陷阱) 会以包含已解码消息和源代码位置的WASM_ERROR异常终止 UDF。未导入 `env.abort“ 的模块不受影响。
设置
-
webassembly_udf_max_fuel— 每个 WebAssembly UDF 实例每次执行的 fuel 上限。每条 WebAssembly 指令都会消耗一定数量的 fuel。在传递给 runtime 之前,该值会先按 1024 进行缩放,因此webassembly_udf_max_fuel = 1对应大约 1024 个 fuel 单位。设为 0 表示没有有限上限。仅适用于每函数设置webassembly_udf_enable_fuel为 true 的函数,默认即为如此。 -
webassembly_udf_max_memory— 每个 WebAssembly UDF 实例的内存上限 (以字节为单位) 。 -
webassembly_udf_max_input_block_size— 单个块中传递给 WebAssembly UDF 的最大行数。设为 0 表示一次处理所有行。 -
webassembly_udf_max_instances— 每个函数可并行运行的 WebAssembly UDF 实例最大数量。