初识分布式图数据库 Nebula Graph 2.0 Query Engine

简介: 本文主要介绍图书库 Nebula Graph Query 层的整体结构,并通过一条 nGQL 语句来介绍其通过 Query 层的四个主要模块的流程

初识 Nebula Graph 2.0 Query Engine

摘要:本文主要介绍 Query 层的整体结构,并通过一条 nGQL 语句来介绍其通过 Query 层的四个主要模块的流程。

一、概述

分布式图数据库 Nebula Graph 2.0 版本相比 1.0 有较大改动,最明显的变化便是,在 1.0 版本中 Query、Storage 和 Meta 模块代码不作区分放在同一个代码仓中,而 Nebula Graph 2.0 开始在架构上先解耦成三个代码仓:nebula-graph、nebula-common 和 nebula-storage,其中 nebula-common 中主要是表达式的定义、函数定义和一些公共接口、nebula-graph 主要负责 Query 模块、nebula-storage 主要负责 Storage 和 Meta 模块。

本文主要介绍 Query 层的整体结构,并通过一条 nGQL 语句来介绍其通过 Query 层的四个主要模块的流程,由于 Nebula Graph 2.0 仍处于开发中,版本变化比较频繁,本文主要针对 2.0 的 nebula-graph 仓中 master 分支的 aea5befd179585c510fb83452cb82276a7756529 版本。

二、框架

Query 层主要框架如下所示:

初识 Nebula Graph 2.0 Query Engine

主要分为 4 个子模块

  • Parser:词法语法解析模块
  • Validator:语句校验模块
  • Planner:执行计划和优化器模块
  • Executor:执行算子模块

三、代码结构

下面讲下 nebula-graph 的代码层次结构,如下所示

|--src
    |--context     // 校验期和执行期上下文
    |--daemons 
    |--executor    // 执行算子
    |--mock
    |--optimizer   // 优化规则
    |--parser      // 词法语法分析                         
    |--planner     // 执行计划结构    
    |--scheduler   // 调度器
    |--service
    |--util        // 基础组件
    |--validator   // 语句校验    
    |--vistor

四、一个案例聊 Query

自 Nebula Graph v2.0 起,nGQL 的语法规则已经支持起始点的类型为 string ,正在兼容 1.0 的 int 类型。举个例子:

GO FROM "Tim" OVER like WHERE like.likeness > 8.0 YIELD like._dst

上面的一条 nGQL 语句在 Nebula Graph 的 Query 层的数据流如下所示:

初识 Nebula Graph 2.0 Query Engine

主要流程如下:

第一阶段:生成 AST

第一阶段:首先经过 Flex 和 Bison 组成的词法语法解析器模块 Parser 生成对应的 AST, 结构如下: 

初识 Nebula Graph 2.0 Query Engine

在此阶段 Parser 会拦截掉不符合语法规则的语句。举个例子,GO "Tim" FROM OVER like YIELD like._dst 这种语法使用错误的语句会在语法解析阶段直接被拦截。

第二阶段:校验

第二阶段:Validator 在 AST 上进行一系列的校验工作,主要工作如下:

  • 元数据信息的校验

在解析 OVER 、 WHERE 和 YIELD 语句时,会查找 Schema,校验 edge、tag 的信息是否存在。或者在 INSERT 数据时校验插入数据类型和 Schema 中的是否一致

  • 上下文引用校验

遇到多语句时,例如:$var = GO FROM "Tim" OVER like YIELD like._dst AS ID; GO FROM $var.ID OVER serve YIELD serve._dst ,Validator 会校验 $var.ID` 首先检查变量 `var` 是否定义,其次再检查属性 `ID` 是否属于变量 `var`, 如果是将 `$var.ID 替换为 $var1.ID` 或者 `$var.IID, 则会校验失败。

  • 类型推断校验

推断表达式的结果属于什么类型,并根据具体的子句,校验类型是否正确。比如 WHERE 子句要求结果是 bool,null 或者 empty 。

  • *'' 展开**

例如,若输入语句为 GO FROM "Tim" OVER * YIELD like._dst, like.likeness, serve._dst,则在校验 OVER 子句时需要查询 Schema 将 * 展开为所有的边,假如 Schema 中只有 like 和 serve 两条边时,该语句会展开为:GO FROM "Tim" OVER serve, like YIELD like._dst, like.likeness, serve._dst

  • 输入输出校验

遇到 PIPE 语句时,例如:GO FROM "Tim" OVER like YIELD like._dst AS ID | GO FROM $-.ID OVER serve YIELD serve._dst`,Validator 会校验 `$-.ID 由于 ID 在上一条语句中已经定义,则该子句合法,如果是将$-.ID 换为 `$-.a` 而此时 a 未定义,因此该子句非法。

第三阶段:生成可执行计划

第三阶段:经过 Validator 之后会生成一个可执行计划,其中执行计划的数据结构在 src/planner 目录下,其逻辑结构如下:

初识 Nebula Graph 2.0 Query Engine

Query 执行流

执行流:该执行计划是一个有向无环图,其中节点间的依赖关系在 Validator 中每个模块的 toPlan() 函数中确定,在这个例子中 Project 依赖 Filter, Filter 依赖 GetNeighbor,依次类推直到 Start 节点为止。

在执行阶段执行器会对每个节点生成一个对应的算子,并且从根节点(这个例子中是 Project 节点)开始调度,此时发现此节点依赖其他节点,就先递归调用依赖的节点,一直找到没有任何依赖的节点(此时为 Start 节点),然后开始执行,执行此节点后,继续执行此节点被依赖的其他节点(此时为 GetNeighbor 节点),一直到根节点为止。

Query 数据流

数据流:每个节点的输入输出也是在 toPlan() 中确定的, 虽然执行的时候会按照执行计划的先后关系执行,但是每个节点的输入并不完全依赖上个节点,可以自行定义,因为所有节点的输入、输出其实是存储在一个哈希表中的,其中 key 是在建立每个节点的时候自己定义的名称,假如哈希表的名字为 ResultMap,在建立 Filter 这个节点时,定义该节点从 ResultMap["GN1"] 中取数据,然后将结果放入 ResultMap["Filter2"] 中,依次类推,将每个节点的输入输出都确定好,该哈希表定义在 nebula-graph 仓下 src/context/ExecutionContext.cpp 中,因为执行计划并不是真正地执行,所以对应哈希表中每个 key 的 value 值都为空(除了开始节点,此时会将起始数据放入该节点的输入变量中),其值会在 Excutor 阶段被计算并填充。

这个例子比较简单,最后会放一个复杂点的例子以便更好地理解执行计划。

第四阶段:执行计划优化

第四阶段:执行计划优化。如果 etc/nebula-graphd.conf 配置文件中 enable_optimizer 设置为 true ,则会对执行计划的优化,例如上边的例子,当开启优化时:

初识 Nebula Graph 2.0 Query Engine

此时会将 Filter 节点融入到 GetNeighbor 节点中,在执行阶段当 GetNeighbor 算子调用 Storage 层的接口获取一个点的邻边的时候,Storage 层内部会直接将不符合条件的边过滤掉,这样就可以极大的减少数据量的传输,俗称过滤下推。

在执行计划中,每个节点直接依赖另外一个节点。为了探索等价的变换和重用计划中相同的部分,会将节点的这种直接依赖关系转换为 OptGroupNode 与 OptGroup 的依赖。每个 OptGroup 中可以包含等价的 OptGroupNode 的集合,每个 OptGroupNode 都包含执行计划中的一个节点,同时 OptGroupNode 依赖的不再是 OptGroupNode 而是 OptGroup,这样从该 OptGroupNode 出发可以根据其依赖 OptGroup 中的不同的 OptGroupNode 拓展出很多等价的执行计划。同时 OptGroup 还可以被不同的 OptGroupNode 共用,节省存储的空间。

目前我们实现的所有优化规则认为是 RBO(rule-based optimization),即认为应用规则后的计划一定比应用前的计划要优。CBO(cost-based optimization) 目前正在同步开发。整个优化的过程是一个"自底向上"的探索过程,即对于每个规则而言,都会由执行计划的根节点(此例中是 Project 节点)开始,一步步向下找到最底层的节点,然后由该节点开始一步步向上探索每个 OptGroup 中的 OptGroupNode 是否匹配该规则,直到整个 Plan 都不能再应用该规则为止,再执行下一个规则的探索。

本例中的优化如下图所示:

初识 Nebula Graph 2.0 Query Engine

例如,当搜索到 Filter 节点时,发现 Filter 节点的子节点是 GetNeighbors,和规则中事先定义的模式匹配成功,启动转换,将 Filter 节点融入到 GetNeighbors 节点中,然后移除掉 Filter 节点,继续匹配下一个规则。

优化的代码在 nebula-graph 仓下 src/optimizer/ 目录下。

第五阶段:执行

第五阶段:最后 Scheduler 会根据执行计划生成对应的执行算子,从叶子节点开始执行,一直到根节点结束。其结构如下:

初识 Nebula Graph 2.0 Query Engine

其中每一个执行计划节点都一一对应一个执行算子节点,其输入输出在执行计划期间已经确定,每个算子只需要拿到输入变量中的值然后进行计算,最后将计算结果放入对应的输出变量中即可,所以只需要从开始节点一步步执行,最后一个算子的结果会作为最终结果返回给用户。

五、实例

下面执行一个最短路径的实例看看执行计划的具体结构,打开 nebula-console, 输入下面语句
FIND SHORTEST PATH FROM "YAO MING" TO "Tim Duncan" OVER like, serve UPTO 5 STEPS
,在这条语句前加 EXPLAIN 关键字就可以得到该语句生成的执行计划详细信息:

初识 Nebula Graph 2.0 Query Engine

上图从左到右依次显示执行计划中每个节点的唯一 ID、节点的名称、该节点所依赖的节点 ID、profiling data(执行 profile 命令时的信息)、该节点的详细信息(包括输入输出变量名称,输出结果的列名,节点的参数信息)。

如果想要可视化一点可以在这条语句前加 EXPLAIN format="dot",这时候 nebula-console 会生成 dot 格式的数据,然后打开 Graphviz Online 这个网站将生成的 dot 数据粘贴上去,就可以看到如下结构,该结构对应着执行阶段各个算子的执行流程。

初识 Nebula Graph 2.0 Query Engine

因为最短路径使用了双向广度搜索算法分别从"YAO MING" 和 "Tim Duncan" 两边同时扩展,所以中间的 GetNeighbors、BFSShortest、 Project、 Dedup 分别有两个算子,通过 PassThrough 算子连接输入,由 ConjunctPath 算子拼接路径。然后由 LOOP 算子控制向外扩展的步数,可以看到 DataCollect 算子的输入其实是从 ConjuctPath 算子的输出变量中取值的。

各个算子的信息在 nebula-graph 仓下的 src/executor 目录下。

作者有话说:Hi,我是明泉,是图数据 Nebula Graph 研发工程师,主要工作和数据库查询引擎相关,希望本次的经验分享能给大家带来帮助,如有不当之处也希望能帮忙纠正,谢谢~

喜欢这篇文章?来来来,给我们的 GitHub 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]

交流图数据库技术?交个朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你进交流群~~

推荐阅读

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
SQL 关系型数据库 MySQL
乐观锁在分布式数据库中如何与事务隔离级别结合使用
乐观锁在分布式数据库中如何与事务隔离级别结合使用
365 5
|
SQL 关系型数据库 MySQL
乐观锁在分布式数据库中如何与事务隔离级别结合使用
乐观锁在分布式数据库中如何与事务隔离级别结合使用
|
存储 SQL 分布式数据库
OceanBase 入门:分布式数据库的基础概念
【8月更文第31天】在当今的大数据时代,随着业务规模的不断扩大,传统的单机数据库已经难以满足高并发、大数据量的应用需求。分布式数据库应运而生,成为解决这一问题的有效方案之一。本文将介绍一款由阿里巴巴集团自主研发的分布式数据库——OceanBase,并通过一些基础概念和实际代码示例来帮助读者理解其工作原理。
1554 0
|
存储 监控 分布式数据库
ClickHouse分布式数据库动态伸缩(弹性扩缩容)的实现
实现ClickHouse数据库的动态伸缩需要持续的维护和精细的操作。从集群配置到数据迁移,再到监控和自动化,每一步都要仔细管理以确保服务的可靠性和性能。这些活动可以显著提高应用的响应性和成本效率,帮助业务根据实际需求灵活调整资源分配。
704 10
|
存储 关系型数据库 分布式数据库
【赵渝强老师】基于PostgreSQL的分布式数据库:Citus
Citus 是基于 PostgreSQL 的开源分布式数据库,采用 shared nothing 架构,具备良好的扩展性。它以插件形式集成,部署简单,适用于处理大规模数据和高并发场景。本文介绍了 Citus 的基础概念、安装配置步骤及其在单机环境下的集群搭建方法。
1150 2
|
SQL 存储 分布式数据库
分布式存储数据恢复—hbase和hive数据库数据恢复案例
分布式存储数据恢复环境: 16台某品牌R730xd服务器节点,每台服务器节点上有数台虚拟机。 虚拟机上部署Hbase和Hive数据库。 分布式存储故障: 数据库底层文件被误删除,数据库不能使用。要求恢复hbase和hive数据库。
718 12
|
SQL 运维 关系型数据库
体验用分布式数据库突破资源瓶颈,完成任务领智能台灯!
体验用分布式数据库突破资源瓶颈,完成任务领智能台灯!
|
SQL 数据建模 BI
【YashanDB 知识库】用 yasldr 配置 Bulkload 模式作单线程迁移 300G 的业务数据到分布式数据库,迁移任务频繁出错
问题描述 详细版本:YashanDB Server Enterprise Edition Release 23.2.4.100 x86_64 6db1237 影响范围: 离线数据迁移场景,影响业务数据入库。 外场将部分 NewCIS 的报表业务放到分布式数据库,验证 SQL 性能水平。 操作系统环境配置: 125G 内存 32C CPU 2T 的 HDD 磁盘 问题出现的步骤/操作: 1、部署崖山分布式数据库 1mm 1cn 3dn 单线启动 yasldr 数据迁移任务,设置 32 线程的 bulk load 模式 2、观察 yasldr.log 是否出现如下错
|
容灾 关系型数据库 分布式数据库
PolarDB分布式版:与云融合的分布式数据库发展新阶段
PolarDB分布式版标志着分布式数据库与云融合的新阶段。它经历了三个发展阶段:从简单的分布式中间件,到一体化分布式架构,再到云原生分布式数据库。PolarDB充分利用云资源的弹性、高性价比、高可用性和隔离能力,解决了大规模数据扩展性问题,并支持多租户场景和复杂事务处理。零售中台的建设背景包括国家数字化转型战略及解决信息孤岛问题,采用分布式数据库提升高可用性和性能,满足海量订单处理需求。展望未来,零售中台将重点提升容灾能力、优化资源利用并引入AI技术,以实现更智能的服务和更高的业务连续性。
649 9