InfluxDB 2.0 使用 Flux 作为它的查询语言,分析一下下面这个查询语句(简称 Query A)是如何被执行的:
(bucket:
InfluxDB 作为数据库同样设计了逻辑查询计划和物理查询计划的创建和优化环节,整体流程:
- 解析查询语句,生成 flux.Spec 对象
- 将 Spec 对象转化成逻辑查询计划并优化
- 将逻辑查询计划转换成物理查询计划并优化
代码如下:
func
flux.Spec 对象
flux.Spec 定义如下:
// Spec specifies a query.
flux.Spec 对象表示的是一个有向无环图(DAG),Query A 的结构大致如下:
from -> range -> filter -> window -> quantile
其中 Operations 表示查询语句当中的算子,Edges 则表示它们之间的关联,sorted 表示节点拓扑排序的顺序,用于后面创建查询计划。
逻辑查询优化
InfluxDB 的逻辑查询优化算法(准确的说是 flux 的)比较简单,深度遍历算子,如果遍历到的算子有配置优化规则,就执行优化规则,将节点转换成新节点,使用新的节点更换旧的节点。然后继续遍历。代码如下:
func
第 5 ~ 14 行,创建遍历是要用到的 stack
第 17 ~ 43 行,深度遍历节点,其中:
- 第 24 行 matchRules 方法尝试对节点进行优化,并使用新的节点替换掉原来的节点,如果没有优化,返回的 newNode 和 node 是同一个对象
- 第 28 行,如果节点被优化了,使用 anyChanged 标记查询计划发生变化,完成此次遍历之后会重新遍历一遍,直到不再有节点被优化
目前 InfluxDB 还没有添加什么实际的逻辑查询优化规则,所以返回的逻辑查询计划其实没有发生变化。唯一不同的是逻辑查询阶段,会为 Query A 增加一个 yield 算子,感觉这个算子和关系型数据库的 projection 算子有些类似, 最终 yield 算子负责将数据推送给应用层。
最终得到的逻辑查询计划:
from -> range -> filter -> window -> quantile -> yield
物理查询优化
物理查询计划的创建过程很简单,而且和优化流程混合在了一起:
func
第 2 行,调用和逻辑查询计划一样的查询优化器,只不过针对物理查询计划的优化规则比较多,将逻辑算子转换成对应类型的物理算子也充当了一种查询优化规则... 以此来完成逻辑查询计划到物理查询计划的转换。
InfluxDB 配置的物理查询计划优化策略如下:
// query/stdlib/influxdata/influxdb/rules.go
从代码上来看主要是一些算子下推的优化策略,应该不是很复杂。完成物理查询优化之后得到的最终查询计划:
ReadRange -> window -> quantile -> yield
逻辑查询优化中的 Range 和 Filter 算子都和 From 合并到一起了。