NebulaGraph源码优化--NGQL查询过滤下推 (https://github.com/zhaojunnana/nebula)
总结:NGQL查询,一度一度扩散的多跳查询,GetNeighbors的Filter都已下推到存储层
一、NGQL每跳一步Filter下推优化
1.下一跳仅依赖上一跳的ID,去掉InnerJoin
profile go from 'player101' over follow yield follow._dst as fid | go from $-.fid over serve yield serve._dst as tid;
优化前:
优化后:
2.存在InnerJoin的常量Filter下推到存储层
profile go from 'player101' over follow yield follow._dst as fid | go from $-.fid over follow where follow.degree>70 yield follow._dst as tid;
优化前:
优化后:
3.存在InnerJoin的变量($-.)Filter下推到存储层
profile go from 'player101' over follow yield follow._dst as fid, follow.degree as num | go from $-.fid over follow where follow.degree<$-.num and follow.degree>70 yield follow._dst as tid;
优化前:
优化后:
4.存在LeftJoin的Filter下推到存储层
profile go from 'player101' over follow where follow.degree>90 and $$.player.age>35 yield follow._dst as fid, follow.degree;
优化前:
优化后:
二、NGQL语法{ SAMPLE <sample_list> | <limit_by_list_clause> }中,添加FLATSAMPLE <sample_list>语法
FLATSAMPLE意为拍平ids,每个id取样<sample_list>个
源码改动
NGQL下推部分源码改动主要涉及到两方面:存储层FilterNode支持变量过滤;引擎层执行计划GoPlanner构建GetNeighbors时,进行Filter的判断、剪枝和变量下推
1.存储层改造
Graphd变量封装在req中传到存储层,存储层遍历buildPlan的存储层执行计划,将变量放到FilterNode中。
当Filter迭代器执行到check时,filterExp_是InputExpressionh会用传过来的数据进行过滤。
2.引擎层改造
构建GetNeighbors执行计划中,如果存在filter,剪枝放入GetNeighbors的filter中;判断filter是否有
InputExpression,有就把变量传到存储层。
剪枝的逻辑:
1. kDstProperty类型的过滤条件裁掉;
2. 若裁掉的条件父节点为逻辑OR(kLogicalOr),剪掉整个OR;
3. 若裁掉的条件父节点为逻辑AND(kLogicalAnd),剪掉当前分支。
剪枝部分可以看成一道二叉树的算法题:
一颗完全二叉树共存在四类节点,非叶子节点包括OR和AND,叶子节点包括普通节点和DST节点。
DST节点直接剪掉;OR节点下存在剪枝,则剪掉整个OR;AND节点下子节点存在剪枝,保留未被剪掉的分支。如图:
执行器部分,如果需要变量下推,则在构建Vid的DataSet中,加入其他变量。
问题、可优化和性能提升
1.问题
在未优化前,业务中会出现超级节点。当查询到这些超级节点时,会依赖属性过滤获取部分数据,由于
大部分情况下Filter都未能下推,导致大量数据需要加载到引擎层才能过滤,耗时长久,占据大量资源导
致数据库整体性能受到影响;后面采用 limit [1000] 这种方式强行限制存储层的数据量,但是Filter要在
引擎层才能做,导致传输回来的1000条数据中很少有符合条件的;limit的数量过多,又会出现一开始的
问题。因此,Filter必须要在存储层做才能支持业务场景,提升数据库性能。
2.可优化
当前改造的源码,只是能做到Filter下推,整体上还很粗略。
1. 当前只做了GetNeighbors的Filter下推,GetVertices也是可以做的
2. 正常逻辑来说,下推应该再优化执行计划阶段做,但由于很多情况很难考虑全面,实现难度会增大很多,所以把这部分放到构建阶段
3. 未改变整体的执行计划,这就会导致在GetNeighbors Filter过的数据,还是会经过引擎层的Filter,增加耗时,后续执行计划可以尝试精简合并下
3.性能提升
性能提升方面没办法找一个通用的标准。以我们的一个业务来说,由于本身就是多跳查询,当再遇到
较大节点或者超级节点的话,优化前可能需要3-5s的查询时间,优化后基本能在300ms-500ms的范围;
正常查询节点优化前基本在300ms左右,优化后基本能控制在200ms内。查询性能优化,最根本的解决
办法还是建模时尽量减少超级节点的形成,但数据的不可控性使我们很难做到,所以只能提高对数据库的要求。