理论背景
早先的系统里提及的计算下推一般指谓词下推(Predicate pushdown)。最早提及暂不可考,但理论源自于关系代数理论。故90~10年前阶段的研究或实现基本都是只提及Predicate Pushdown,这种影响一直影响到了201x年开源盛行:
- Hive和SparkSQL等主流的数仓计算引擎仅提供了PredicatePushdown的接口
- MySQL截止到2020年8.0.x版本也只有通用支持不完善的cond_push和ICP
- postgres_fdw插件开始支持将聚合推到远端存储去完成,但不关心远端存储的实现
谓词下推是符合当前关系型数据库的经典火山模型的一个优化,对于一个带有通用目标的数仓引擎/DB计算引擎,火山模型(或带块优化的火山模型)依然是框架实现的首选。
但以Oracle为代表的商业数据库一体化解决方案以及云数据库的崛起指出了另一条道路:定制化计算型存储。计算下推的涵盖范围由此从基本的谓词+投影下推延伸到了数据库所支持的一切可能计算的下推。
计算下推目标问题
Resource reduction (IO/CPU)
- Case 1: total reduction
- Case 2: tradeoff. Maybe CPU reduces, IO increases.
- Decompression offload : (IO snappy + CPU decompress) VS. (CSS decompress + IO raw)
Performance improvement
- usually brought by resource reduction (especially network IO)
谓词下推
P : X→ {true, false} called a predicate on X .
Sargable (Search argument able) Sargable是相对具体实现而言的,通俗可以理解为某个谓词在当前数据库引擎实现里是Sargable的话,那么他就是可以下推优化的。一般情况下,仅where predicate 可下推,join predicate不能下推。主要是join关系一般没办法下推,除非在一些特殊案例下。
在一些Optimizer优化中,我们还会见到上推/上提的概念,通常是子查询的上提优化的一些变种说法。这里需区分我们所说的计算下推指的是将计算下推到存储层,是执行器层面的问题,而不是优化器逻辑计划中的不同query block的predicates move-around。其中join predicate pushdown通常就是这类问题之一。JPPD包括如下优化思路:
- JOIN 左表的join 列(一般是数值型索引列)有足够的统计信息可以支撑优化。 (see SparkSQL)
- 将外表的带索引join列信息通过join predicate推入视图子查询。 (see Oracle JPPD)
投影下推
投影下推的IO减少与存储方式有着强相关关系,一般作用于行存储中,因为列存储天生带了投影,所以再下推一般意义不大。(但对某些行列存储场景可能适用)
根据相关论文测试结果,在HDD,在投影列大小占行大小的约85%以上时,列存储的查询性能会不如行存储。因为此时列存储需要打开更多的磁盘文件,每一次切换都带来一次磁盘寻道开销,而行存储则享受完全的顺序扫描。但在现如今的云数据库基本都是使用NVMe SSD,新的结果有待测试。
JOIN操作下推
一般在一些Nosql里如下存储形式的数据比较适合JOIN操作下推:
Join field partitionable:
- parent-child in Object format storage (document storage)
- join predicates on partition key (shards or partition storage)
Broadcast Join:
- Load small table into a filter (Hash/BloomFilter) and push it down
此处概念需区别于join predicate pushdown (可能某些资料也会缩写成join pushdown) 。(见谓词下推)
聚合下推
行列下推作用的只是一行数据或者关联的几个block的数据,而聚合下推作用的则是整个table的数据(不考虑表连接的情况下),因此通常聚合查询下推的做法不再仅仅是把查询条件下放,而会额外增加一些存储层索引或预计算等。涉及到的索引有如下几种分类方法:
-
强索引: 对用户所需的字段查询建立了直接的索引
-
弱索引: 仅对部分列(通常为主键或分区键)建立索引,或只建立分区/shard级别的部分索引
-
全局索引:存储层有独立的全局索引机制如支持聚合计算
-
局部索引:每个分区维护自己的索引,查询时类似map-reduce,先在局部查询,然后在引擎聚合再次计算最终结果
-
StorageIndex: 来自于Oracle的概念,狭义指存储单位级别的一些弱索引,比如block级别的max/min/count等
-
持久化索引:索引是固化到磁盘的。(大部分的索引形态)
-
内存索引: 索引是临时存在于内存的,比如一些内存形态的临时索引优化
最后需注意:对于一些多层计算的架构(比如AWS Redshift里的计算下推),把部分计算从上一层放到下一层(但还是计算引擎),也都是计算下推的范畴。但这类下推相对于下推到存储来说,挑战是不一样的。
同时,更广义计算下推不再限制于SQL计算下推,比如LSM数据库的compaction offloading等,广义下推概念在云数据库领域上和数据库存储融合概念两者边界逐渐模糊。