玩转 Databend UDF

引言

Databend 作为新一代云原生数据仓库,提供了六百多个内置函数,满足了大部分用户的需求。然而,随着业务的增长,需求也变的日新月异,内置的函数可能无法服务用户变化的需求。在这种场景下, Databend 提供了多种用户自定义函数(UDF)实现方式,满足不同场景下的数据处理需求。

本文将深入探讨三种 UDF 形态:Lambda UDF、UDF Script 和 External UDF Server,并通过具体的案例展示它们的实现方式,最后进行性能对比分析。

Lambda UDF:纯 SQL 定义的函数语法糖

Lambda UDF 是 Databend 中最简单的 UDF 形式,完全通过 SQL 语句定义和执行表达式,适合简单的数据转换和计算。

我们可以在 SQL 中定义一个闭包函数,然后进行调用。

Lambda UDF 示例:

🐳 root@default:) CREATE FUNCTION plus_3 AS (a,b,c) -> a + b + c;

🐳 root@default:) select plus_3(1,2,3);

╭─────────────────╮
│ plus_3(1, 2, 3) │
│      UInt8      │
├─────────────────┤
│               6 │
╰─────────────────╯

🐳 root@default:) CREATE FUNCTION age AS (d) -> date_diff(year, d, now());

🐳 root@default:) select age('1992-01-01'::Date);

╭─────────────────────────╮
│ age('1992-01-01'::DATE) │
│          Int64          │
├─────────────────────────┤
│                      33 │
╰─────────────────────────╯

它的特点:

  • 纯 SQL 实现,无需外部语言支持
  • 无法支持递归调用
  • 执行性能受表达式定义影响

UDF Script:多语言扩展能力

Databend 引擎中支持内嵌的多语言执行器, 可以执行通过 Python、JavaScript 和 WASM 编写 UDF Script,适合复杂业务逻辑实现。

  • Python UDF

Databend 使用了 pyo3 引擎来执行 Python script, 我们可以通过如下方式定义一个简单的 Python UDF。

CREATE FUNCTION fib_python ( Int32 ) RETURNS Int32 LANGUAGE python HANDLER = 'fib' AS $$
def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a
$$;

select plus_3(1,2,3);
╭─────────────────╮
│ plus_3(1, 2, 3) │
│      UInt8      │
├─────────────────┤
│               6 │
╰─────────────────╯
  • Javascript UDF

Databend 使用了 rquickjs 引擎来执行 Javascript, 我们可以通过如下方式定义一个简单的 Javascript UDF。

CREATE OR REPLACE FUNCTION fib_js ( Int32 ) RETURNS Int32 LANGUAGE JAVASCRIPT HANDLER = 'fib' AS $$
export function fib(n) {
    let a = 0, b = 1;
    for (let i = 0; i < n; i++) {
        [a, b] = [b, a + b];
    }
    return a
}
$$;

🐳 root@default:) select fib_js(10);

╭─────────────────╮
│    fib_js(10)   │
│ Nullable(Int32) │
├─────────────────┤
│              55 │
╰─────────────────╯

特点:

  • 支持完整语言特性,实现复杂逻辑

  • Python UDF 特别适合数据科学和AI集成

  • JavaScript UDF 适合轻量级数据处理,同时兼顾沙箱安全性

  • UDF script 属于 Databend 企业特性

  • WASM UDF 你可以使用 rust 代码来实现 UDF, 然后编译到 Wasm target 中,示例教程。

  1. 创建一个项目,Cargo.toml 包含 arrow-udf 依赖
[package]
name = "arrow-udf-example"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
arrow-udf = "0.8"
  1. 使用 #[function] 宏来定义你的函数
use arrow_udf::function;

#[function("fib(int) -> int")]
fn fib(n: i32) -> i32 {
    let (mut a, mut b) = (0, 1);
    for _ in 0..n {
        let c = a + b;
        a = b;
        b = c;
    }
    a
}

然后编译到 wasm32-wasip1

cargo build --release --target wasm32-wasip1  

将生成的 result.wasm 文件传到 databend stage 中,并定义 wasm udf 函数

🐳 root@default:) create stage s_udf;
🐳 root@default:) put fs:///tmp/arrow_udf_example.wasm @s_udf/;

╭─────────────────────────────────────────────────╮
│             file            │  status │   size  │
│            String           │  String │  UInt64 │
├─────────────────────────────┼─────────┼─────────┤
│ /tmp/arrow_udf_example.wasm │ SUCCESS │ 1279392 │
╰─────────────────────────────────────────────────╯

🐳 root@default:) CREATE OR REPLACE FUNCTION fib_wasm (INT) RETURNS INT LANGUAGE wasm HANDLER = 'fib' AS $$@s_udf/arrow_udf_example.wasm$$;


🐳 root@default:) select fib_wasm(10::Int32); 
╭─────────────────────╮
│ fib_wasm(10::Int32) │
│   Nullable(Int32)   │
├─────────────────────┤
│                  55 │
╰─────────────────────╯

External UDF Server:灵活解耦的外部 UDF 服务

通过 Arrow Flight 协议与外部 UDF Server 通信,适合将已有的服务和 Databend 互联互通。

Example:

  1. 启动 UDF Server(Python 示例):
from databend_udf import udf, UDFServer
import logging

logging.basicConfig(level=logging.INFO)

@udf(
    input_types=["INT"],
    result_type="INT",
    skip_null=True,
)
def fib(n: int) -> int:
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

if __name__ == "__main__":
    udf_server = UDFServer(
        location="0.0.0.0:8815"
    )
    udf_server.add_function(fib)
    udf_server.serve()
  1. 在 Databend 中注册外部函数:
🐳 root@default:) CREATE OR REPLACE FUNCTION fib_server (INT) RETURNS INT LANGUAGE python HANDLER = 'fib' ADDRESS = 'http://0.0.0.0:8815';
  1. 调用示例:
🐳 root@default:) select fib_server(10);

╭─────────────────╮
│  fib_server(10) │
│ Nullable(Int32) │
├─────────────────┤
│              55 │
╰─────────────────╯

特点:

  • 需要可靠的网络交互
  • 支持灵活的参数配置:支持批量处理(默认 65536 行/批)
  • 可横向扩展 UDF 服务节点来提高性能
  • 适合与现有微服务架构集成, 与 Databend 服务解耦, 可以与任何支持 Arrow Flight 协议的语言交互

性能对比分析

在单机内存环境下(Databend v1.3.0,16GB RAM),计算 fib(x) 的性能对比:

性能测试环境: Intel(R) Core(TM) i9-12900KF 24C archlinux

SQL:

select fib((n % 10) ::Int32) from range(1, 1000000) t(n) ignore_result;
UDF类型平均每行数据执行耗时(us)适用场景
Lambda UDF-简单转换、快速原型
Python UDF0.18复杂逻辑、AI 集成
JavaScript UDF2.68轻量级数据处理
WASM UDF0.11高性能处理
External UDF23.2大规模数据处理

*注:External UDF 耗时包含网络通信,实际处理时间会更短。


性能优化建议:

  1. 简单逻辑优先使用 Lambda UDF
  2. 复杂计算考虑 Python/JavaScript UDF
  3. 高并发场景使用 External UDF 并部署多个服务节点

UDF 选型指南

根据您的业务需求选择合适的 UDF 类型:

对比维度Lambda UDFUDF ScriptExternal UDF Server
​​开发效率​​⭐⭐⭐⭐ (纯SQL实现,无需编译)⭐⭐⭐ (需编写脚本)⭐ (需独立服务部署)
​​执行性能​​⭐⭐⭐⭐ (原生性能)⭐⭐⭐ (Python/JS运行时开销, wasm性能最佳)⭐⭐ (支持批量处理,动态扩容,网络开销)
​​复杂逻辑​​⭐ (仅限简单表达式)⭐⭐⭐ (支持完整编程语言)⭐⭐ (需服务化拆分逻辑)
​​系统集成​​⭐ (仅限数据库内部)⭐⭐ (需适配语言运行时)⭐⭐⭐⭐ (Arrow Flight协议集成)
实现​​(n)->CASE WHEN n<=1 THEN n ELSE ...Python/JS实现完整算法逻辑通过Arrow Flight RPC服务暴露计算接口
​​适用场景​​快速原型/简单转换AI集成/复杂数据处理高并发/分布

结语:扩展您的数据能力

Databend 的多形态 UDF 支持为数据处理提供了极大的灵活性。无论您需要快速实现简单转换,还是集成复杂业务逻辑,或是构建分布式计算管道,都能找到合适的解决方案。

参考文档:

关于 Databend

Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式湖仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。

👨‍💻‍ Databend Cloud:databend.cn

📖 Databend 文档:docs.databend.cn

💻 Wechat:Databend

✨ GitHub:github.com/databendlab...

### 旋转 Motion UDF 实现或使用方法 在 ANSYS FLUENT 中,`DEFINE_GRID_MOTION` 宏是实现动态网格运动的核心工具。通过该宏,可以定义包括旋转在内的各种网格运动模式。以下详细说明如何使用 `DEFINE_GRID_MOTION` 实现旋转运动。 #### 1. 宏的基本结构 `DEFINE_GRID_MOTION` 宏的定义形式如下: ```c DEFINE_GRID_MOTION(motion_name, domain, dt, time, dtime) { // 参数说明: // motion_name: UDF 名称 // domain: 指向 Domain 的指针 // dt: 存储动网格信息的结构体指针 // time: 当前时刻 // dtime: 时间步长 Thread *t = DT_THREAD(dt); // 获取线程指针 Node *v; // 节点指针 face_t f; // 面指针 } ``` 此宏用于控制网格节点的运动[^3]。 #### 2. 实现旋转运动的代码示例 以下是一个简单的旋转运动 UDF 示例,假设旋转中心为 `(cx, cy, cz)`,旋转轴为 `(ax, ay, az)`,旋转角速度为 `omega`(单位:弧度/秒)。 ```c #include "udf.h" DEFINE_GRID_MOTION(rotation_motion, domain, dt, time, dtime) { real cx = 0.0; // 旋转中心 x 坐标 real cy = 0.0; // 旋转中心 y 坐标 real cz = 0.0; // 旋转中心 z 坐标 real ax = 0.0; // 旋转轴 x 分量 real ay = 0.0; // 旋转轴 y 分量 real az = 1.0; // 旋转轴 z 分量 real omega = 1.0; // 角速度(弧度/秒) real t[ND_ND]; // 当前节点坐标 real r[ND_ND]; // 从旋转中心到当前节点的向量 real theta; // 当前时间对应的旋转角度 real cos_theta; // 余弦值 real sin_theta; // 正弦值 real temp[ND_ND]; // 临时变量 real new_t[ND_ND]; // 新的节点坐标 Thread *t = DT_THREAD(dt); begin_node_loop(n, t) { NV_D(t, =, NODE_X(n), NODE_Y(n), NODE_Z(n)); // 获取当前节点坐标 NV_S(r, -, t, cx, cy, cz); // 计算从旋转中心到节点的向量 theta = omega * time; // 计算当前时间对应的旋转角度 cos_theta = cos(theta); sin_theta = sin(theta); NV_D(temp, =, ax, ay, az); // 旋转轴向量 NV_D(temp, *, r, DOT, temp); // 计算旋转轴与向量 r 的点积 NV_D(temp, *, temp, , -1.0 + cos_theta); // 计算旋转公式中的第一项 NV_CROSS(new_t, ax, ay, az, r); // 计算旋转公式中的第二项 NV_D(new_t, *=, new_t, , sin_theta); NV_D(new_t, +=, new_t, , temp); // 合并两项 NV_D(new_t, +=, new_t, , cx, cy, cz); // 添加旋转中心 NODE_X(n) = new_t[0]; NODE_Y(n) = new_t[1]; NODE_Z(n) = new_t[2]; // 更新节点坐标 } end_node_loop(n, t) } ``` #### 3. 代码解释 - **旋转中心和轴**:用户需要指定旋转中心 `(cx, cy, cz)` 和旋转轴 `(ax, ay, az)`。 - **角速度**:`omega` 决定了旋转的速度。 - **数学公式**:基于刚体旋转公式,结合旋转矩阵实现节点坐标的更新[^4]。 - **节点循环**:通过 `begin_node_loop` 和 `end_node_loop` 遍历所有节点,并逐一更新其位置。 #### 4. 编译与加载 - 将上述代码保存为 `.c` 文件,例如 `rotation_motion.c`。 - 使用 FLUENT 的编译功能将其编译为共享库文件。 - 在 Fluent 中加载共享库,并将 UDF 指定给相应的动态区域[^2]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值