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

一、概述

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

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

二、框架

Query 层主要框架如下所示:

主要分为 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 层的数据流如下所示:

主要流程如下:

第一阶段:生成 AST

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

在此阶段 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 子句要求结果是 boolnull 或者 empty

  • '*' 展开

例如,若输入语句为 GO FROM "Tim" OVER * YIELD like._dst, like.likeness, serve._dst,则在校验 OVER 子句时需要查询 Schema 将 * 展开为所有的边,假如 Schema 中只有 likeserve 两条边时,该语句会展开为: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 目录下,其逻辑结构如下:

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 ,则会对执行计划的优化,例如上边的例子,当开启优化时:

此时会将 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 都不能再应用该规则为止,再执行下一个规则的探索。

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

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

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

第五阶段:执行

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

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

五、实例

下面执行一个最短路径的实例看看执行计划的具体结构,打开 nebula-console, 输入下面语句

FIND SHORTEST PATH FROM "YAO MING" TO "Tim Duncan" OVER like, serve UPTO 5 STEPS

,在这条语句前加 EXPLAIN 关键字就可以得到该语句生成的执行计划详细信息:

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

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

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

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

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

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

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

推荐阅读

初识分布式图数据库 Nebula Graph 2.0 Query Engine的更多相关文章

  1. 分布式图数据库 Nebula Graph 的 Index 实践

    导读 索引是数据库系统中不可或缺的一个功能,数据库索引好比是书的目录,能加快数据库的查询速度,其实质是数据库管理系统中一个排序的数据结构.不同的数据库系统有不同的排序结构,目前常见的索引实现类型如 B ...

  2. 分布式图数据库 Nebula Graph 中的集群快照实践

    1 概述 1.1 需求背景 图数据库 Nebula Graph 在生产环境中将拥有庞大的数据量和高频率的业务处理,在实际的运行中将不可避免的发生人为的.硬件或业务处理错误的问题,某些严重错误将导致集群 ...

  3. 图数据库 Nebula Graph TTL 特性

    导读 身处在现在这个大数据时代,我们处理的数据量需以 TB.PB, 甚至 EB 来计算,怎么处理庞大的数据集是从事数据库领域人员的共同问题.解决这个问题的核心在于,数据库中存储的数据是否都是有效的.有 ...

  4. 图数据库 Nebula Graph 的安装部署

    Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,还能够实现服务高可 ...

  5. 图数据库 Nebula Graph 是什么

    图数据库(英语:Graph Database)是一个使用图结构进行语义查询的数据库.该系统的关键概念是图,形式上是点 (Node 或者 Vertex) 和边 (Edge 或者 Relationship ...

  6. 图数据库 Nebula Graph 在 Boss 直聘的应用

    本文首发于 Nebula Graph 官方博客:https://nebula-graph.com.cn/posts/nebula-graph-risk-control-boss-zhipin/ 摘要: ...

  7. 图数据库 Nebula Graph 的数据模型和系统架构设计

    Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,而且能够提供极高的 ...

  8. 分布式图数据库 Nebula RC2 发布:增强了 CSV Importer 功能

    Nebula Graph 是开源的分布式图数据库,可应用于知识图谱.社交推荐.风控.IoT 等场景. 本次 RC2 主要新增 GO FROM ... REVERSELY 和 GROUP BY 等语句, ...

  9. GraphX 在图数据库 Nebula Graph 的图计算实践

    不同来源的异构数据间存在着千丝万缕的关联,这种数据之间隐藏的关联关系和网络结构特性对于数据分析至关重要,图计算就是以图作为数据模型来表达问题并予以解决的过程. 一.背景 随着网络信息技术的飞速发展,数 ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:QTreeView树形视图的expandsOnDoubleClick属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTreeView树形视图的expandsOnDoubleClick属性用于控制鼠标双击是否展开或折 ...

  2. 【老孟Flutter】41个酷炫的 Loading 组件库

    老孟导读:目前 loading 库中包含41个动画组件,还会继续添加,同时也欢迎大家提交自己的 loading 动画组件或者直接微信发给我也可以. Github 地址:https://github.c ...

  3. Android10_原理机制系列_事件传递机制

    前言和概述 Android的输入设备,最常用的就是 触摸屏和按键 了.当然还有其他方式,比如游戏手柄,比如支持OTG设备,则可以链接鼠标.键盘等. 那么这些设备的操作 是如何传递到系统 并 控制界面的 ...

  4. 差分约束系统——POJ1275

    之前做过差分,但是没做过差分约束系统. 正好在学军机房听课讲到这道题,就顺带学了一下. 其实...就是列不等式组然后建图 作为蒟蒻,当然是不会加二分优化的啦...但是poj上还是94ms跑过了qwq ...

  5. easyui form提交时验证必填,打开时不显示必填提示

    给textbox添加required:true属性后,打开页面时整个表单都是红的,需要将其设置为提交时再验证. 解决方法:通过textbox的novalidate属性来控制是否开启验证 <inp ...

  6. linux下postgresql安装

    1.下载地址 https://www.enterprisedb.com/download-postgresql-binaries 2.创建用户并修改密码 [root@node01 ~]# userad ...

  7. 除了MD5加密,试一下签名吧

    需求 MD5加密,简单密码用彩虹表很容易破解. 密码不能被暴力破解,可以根据定制字符,时间戳等防止破解 解决方案 签名 密码加密 源码 const crypto = require('crypto') ...

  8. Unity 自动烘培Cpu占用过高电脑卡死

    起因 打开Unity项目   发现右下方一直  

  9. Docker(五):Docker安装Elasticsearch

    查找ElasticSearch镜像 镜像仓库 https://hub.docker.com/ 下拉镜像 docker pull elasticsearch:7.7.0 查看镜像 docker imag ...

  10. Spring MVC例子

    学习Spring MVC的一个例子,参考书籍<Servlet.JSP.Spring MVC学习指南>,简单总结一下. 代码下载:https://github.com/PeiranZhang ...