如何实现一个数据库的 UDF?图数据库 NebulaGraph UDF 功能背后的设计与思考
大家好,我是来自 BOSS直聘的赵俊南,主要负责安全方面的图存储相关工作。作为一个从 v1.x 用到 v3.x 版本的忠实用户,在见证 NebulaGraph 发展的同时,也和它一起成长。
BOSS直聘和 NebulaGraph
关于 NebulaGraph 在 BOSS直聘的应用场景,大家可以看看之前文洲老师的文章(图数据库 NebulaGraph 在 BOSS直聘的应用),从那时候文洲老师构建的行为图发展到了安全场景的业务主图、算法推理图、职位相似度图谱等业务,现在更是支持了数仓同学的数据血缘及搜索同学的实时搜索召回场景,单图的规模达到了数千亿。
在图计算方面,BOSS 直聘基于 LPA 和 Louvain 的单度团、多维团,以及基础的离线特征,在安全生产环境中广泛应用图技术。相信未来图在 BOSS直聘还会有更为宽广的舞台。
UDF 的萌生
随着 NebulaGraph 在 BOSS直聘业务上的广泛应用,相对应的对内部技术人员的要求也越来越高。如果技术人员仅仅停留在使用层面,就无法满足从功能到性能很多需求。所以,学习源码成为了必然。
而后迁移 Neo4j->NebulaGraph 过程中,发现业务对 Neo4j 的 UDF 包有所依赖,我本萌生了实现 NebulaGraph UDF 功能的念头。
UDF 设计和实现原理

上图是一条完整 nGQL 语句的执行过程,而 UDF 实现原理同 nGQL 的执行流程相关,大致如下:
graphd 接收到语句 -> Bison 词法解析(切词) -> Flex 语法解析创建 Sentence -> Validator 校验并生成AstContext(抽象语法树) -> toPlan 生成执行计划 Planner -> Optimizer 优化器优化 -> Executor 执行器执行。
在词法语法解析阶段,Function 会被单独解析出来。FunctionManager 作为原生的内置函数管理者,负责函数的定义、加载、调用等操作,从而管理函数的整个生命周期。调用语句通过 FunctionManager 查找到的函数最终会被执行器调用执行。
NebulaGraph 的 UDF 实现基于函数的调用执行流程,增加了 FunctionUdfManager:
static std::unordered_map<std::string, Value::Type> udfFunReturnType_;
static std::unordered_map<std::string, std::vector<std::vector<nebula::Value::Type>>>
udfFunInputType_;
std::unordered_map<std::string, FunctionManager::FunctionAttributes> udfFunctions_;
class FunctionUdfManager {
public:
typedef GraphFunction *(create_f)();
typedef void(destroy_f)(GraphFunction *);
static StatusOr<Value::Type> getUdfReturnType(const std::string functionName,
const std::vector<Value::Type> &argsType);
static StatusOr<const FunctionManager::FunctionAttributes> loadUdfFunction(
std::string functionName, size_t arity);
static FunctionUdfManager &instance();
FunctionUdfManager();
private:
static create_f *getGraphFunctionClass(void *func_handle);
static destroy_f *deleteGraphFunctionClass(void *func_handle);
void addSoUdfFunction(char *funName, const char *soPath, size_t i, size_t i1, bool b);
void initAndLoadSoFunction();
};
它主要做以下几件事:
- 和 FunctionManager 一起初始化,initAndLoadSoFunction 开启定时扫描,扫描
--udf_path路径下文件; - loadUdfFunction加载
.so文件,实例化函数方法,以函数名为 key 保存在 Map 中; - 在启用 UDF 功能的情况下,FunctionManager 未查找函数时,查找并调用 FunctionUdfManager Map 中的函数。
实现比较简单,可以说是取巧了,有需要的话 UDAF 也可用类似方式实现。
UDF 使用方法
下面来讲讲 NebulaGraph UDF 的具体使用,如果你是用 NebulaGraph v3.5.0+ 版本的话,就可以按照以下方式使用 UDF 功能了。如果你是 v3.4.x 及以下版本,UDF 功能是暂不支持的,你也可以 cherry-pick 这个 pr 自行编译使用 UDF 功能。
第一步,在 graphd 配置文件中开启 UDF 功能并指定包目录
# enable udf, c++ only
--enable_udf=true
# set the directory where the .so of udf are stored
--udf_path=/home/foobar/dev/nebula/udf/
第二步,编写自定义函数代码,继承 GraphFunction。GraphFunction 的结构如下:
class GraphFunction;
extern "C" GraphFunction *create();
extern "C" void destroy(GraphFunction *function);
class GraphFunction {
public:
virtual ~GraphFunction() = default;
virtual char *name() = 0;
virtual std::vector<std::vector<nebula::Value::Type>> inputType() = 0;
virtual nebula::Value::Type returnType() = 0;
virtual size_t minArity() = 0;
virtual size_t maxArity() = 0;
virtual bool isPure() = 0;
virtual nebula::Value body(
const std::vector<std::reference_wrapper<const nebula::Value>> &args) = 0;
};
- create、destroy 是函数的创建销毁方法;
- name 调用时的函数名;
- inputType、returnType 输入输出类型;
- minArity、maxArity 参数数量;
- isPure 函数是否有状态;
- body 函数的实现。
第三步,编写好的函数打包成(.so)文件,放到配置文件 --udf_path 配置的对应目录下,graphd 服务会定时(5 分钟)扫描该路径下的包,加载到函数库中。之后,就可以在自己的语句中调用对应的函数了。
️ 注意:由于 graphd 只扫描本地路径下的函数包,想让多个 graphd 都生效,必须都在本地路径下有相应的包。
这里要 cue 下思为老师,感谢他补充的完整使用文档和编译环境:https://github.com/vesoft-inc/nebula/pull/4804 。
UDF 尚未解决的问题
虽然目前 UDF 是能用,但是它还存在部分优化问题。比如:
- so 包位置只支持扫描本地;
- 函数只在 graphd 层,无法下推到存储;
- 使用麻烦,需要用户编码。
当然这些问题和一开始的设计息息相关:开发 UDF 之初,其实是想兼容 C++ 的 so 包和 Java 的 jar 包,但测试了 C++ Jni 调用 Java 的性能,发现基本上无法用于大规模的生产。
下图便是当时的性能测试:

因为实现实在是性能堪忧,于是就放弃了一开始的设计。
当然还有一些未来规划上的事情,主要是希望 NebulaGraph 开发团队一起合作完成:
- 个别的大查询语句和深度查询,容易把 storaged 的内存打满影响集群整体性能。是否可以考虑通过查询时间超时或内存监控自动 kill 对应的查询,释放掉内存。其实对于类似的语句,基本上已经很难拿到结果了,更多的可能是想降低语句带来的影响
- 集群的容错性,多副本情况下某个节点的非正常下线会影响整体集群,由于环境的复杂性具体定位分析也比较困难,盼望尽可能增强集群健壮性。
开发 UDF 的意外收获
前面说过,UDF 其实是阅读 NebulaGraph 源码的产物。这里我想谈谈我对源码阅读感受:整体的 NebulaGraph 源码给我最直观的感受就是层次、结构清晰,代码优雅。在配合官方博客提供的内核讲解系列文章,对我这种跨语言学习的选手难度都大大降低了。
希望 UDF 能帮你解决一些问题,以及我的分享能给你带来一丝启发。
谢谢你读完本文 (///▽///)
如果你想尝鲜图数据库 NebulaGraph,记得去 GitHub 下载、使用、(з)-☆ star 它 -> GitHub;和其他的 NebulaGraph 用户一起交流图数据库技术和应用技能,留下「你的名片」一起玩耍呀~
2023 年 NebulaGraph 技术社区年度征文活动正在进行中,来这里领取华为 Meta 60 Pro、Switch 游戏机、小米扫地机器人等等礼品哟~ 活动链接:https://discuss.nebula-graph.com.cn/t/topic/13970

如何实现一个数据库的 UDF?图数据库 NebulaGraph UDF 功能背后的设计与思考的更多相关文章
- Cayley图数据库的简介及使用
图数据库 在如今数据库群雄逐鹿的时代中,非关系型数据库(NoSQL)已经占据了半壁江山,而图数据库(Graph Database)更是攻城略地,成为其中的佼佼者. 所谓图数据库,它应用图理论( ...
- JanusGraph 图数据库安装小记 ——以 JanusGraph 0.3.0 为例
由于近期项目中有使用图数据的需求,经过对比,我们选择尝试使用 JanusGraph.本篇小记记录了我们安装 JanusGraph 以及需要一起集成的 Cassandra + Elasticsearch ...
- JanusGraph : 图和图数据库的简介
JanusGraph:图数据库系统简介 图(graph)是<数据结构>课中第一次接触到的一个概念,它是一种用来描述现实世界中个体和个体之间网络关系的数据结构. 为了在计算机中存储图,< ...
- neo4j(图数据库)是什么?
不多说,直接上干货! 作为一款强健的,可伸缩的高性能数据库,Neo4j最适合完整的企业部署或者用于一个轻量级项目中完整服务器的一个子集存在. 它包括如下几个显著特点: 完整的ACID支持 高可用性 轻 ...
- 图数据库 Nebula Graph 的数据模型和系统架构设计
Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,而且能够提供极高的 ...
- Nebula Graph 技术总监陈恒:图数据库怎么和深度学习框架进行结合?
引子 Nebula Graph 的技术总监在 09.24 - 09.30 期间同开源中国·高手问答的小伙伴们以「图数据库的设计和实践」为切入点展开讨论,包括:「图数据库的存储设计」.「图数据库的计算设 ...
- Nebula 架构剖析系列(零)图数据库的整体架构设计
Nebula Graph 是一个高性能的分布式开源图数据库,本文为大家介绍 Nebula Graph 的整体架构. 一个完整的 Nebula 部署集群包含三个服务,即 Query Service,S ...
- Nebula 架构剖析系列(一)图数据库的存储设计
摘要 在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分.每个数据库都有其独有的存储.计算方式,今天 ...
- COSCon'19 | 如何设计新一代的图数据库 Nebula
11 月 2 号 - 11 月 3 号,以"大爱无疆,开源无界"为主题的 2019 中国开源年会(COSCon'19)正式启动,大会以开源治理.国际接轨.社区发展和开源项目为切入点 ...
- 图数据库 Nebula Graph 的安装部署
Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,还能够实现服务高可 ...
随机推荐
- Galaxy 生信平台(一):安装
Galaxy Project( https://galaxyproject.org/)是在云计算背景下诞生的一个生物信息学可视化分析开源项目. 该项目由美国国家科学基金会(NSF).美国国家人类基因组 ...
- ISIS 综合实验;BGP 实验
目录 ISIS 综合实验 实验拓扑 实验需求 实验步骤 1.配置相应接口IP地址及环回口地址 2.配置 IS-IS,要求全网互通,R8的Loop X口暂不宣告 3. R1和R3直连,要求 R3 成为 ...
- 【技术积累】Python中的NumPy库【一】
NumPy库是什么 NumPy是Python科学计算的核心库之一,用来进行科学计算,数值分析等矩阵运算.主要提供了以下几种功能: 1.多维数组(ndarray)对象,可以进行快速的数值计算和数组操作: ...
- shell编程-提取IP地址
1.使用cut文本处理工具提取 [root@hadoop129 scripts]# ifconfig ens33 | grep netmask | cut -d " " -f 10 ...
- 解决google翻译出错问题
解决google翻译问题 一.为什么失效 因为google把google翻译的API给关闭了,导致翻译不了. 据网上说是服务器耗钱,但盈利不够导致的. 二.可修复的前提 国内还存有服务器可以用API ...
- 疑难杂记:Chirp信号相关的参数解释
图1 FMCW雷达信号参数 在德州仪器TI毫米波雷达中,开发板参数配置往往涉及如图1所示的信号参数. 宏观上看,信号参数包括\(ADC\)采样时间.脉冲重复周期(\(Chirp\)扫频周期)和帧时间( ...
- Python潮流周刊#7:我讨厌用 asyncio
你好,我是猫哥.这里记录每周值得分享的 Python 及通用技术内容,部分为英文,已在小标题注明.(标题取自其中一则分享,不代表全部内容都是该主题,特此声明.) 首发于我的博客:https://pyt ...
- SpringBoot+MyBatisPlus实现读写分离
前言 随着业务量的不断增长,数据库的读写压力也越来越大.为了解决这个问题,我们可以采用读写分离的方案来分担数据库的读写负载.本文将介绍如何使用 Spring Boot + MyBatis Plus + ...
- 力扣 (LeetCode)算法入门——Day1
704. 二分查找 题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1. ...
- 【SpringBoot】整合Redis
1.前言 最近公司在做项目,用到了redis,,发现自己一点都不会,然后就乘闲暇时间,自己学习一些redis相关的知识,在这里分享给像我一样的初学者. 2.我的项目结构: 2.1 pom.xml &l ...