Optimizing Queries over Partitioned Tables in MPP Systems

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 随着互联网数据的爆炸性增长,传统数据库系统在单表数据容量方面承受了越来越大的压力。以前公司内部的数据库,存放的主要是来自公司业务或内部管理系统的信息,中小型公司甚至一个MySQL实例就搞定了。但现在数据源不仅更丰富,数据量也在指数级增长,从业务的角度,基于hash/range的分区表变得越来越有吸引力。

随着互联网数据的爆炸性增长,传统数据库系统在单表数据容量方面承受了越来越大的压力。以前公司内部的数据库,存放的主要是来自公司业务或内部管理系统的信息,中小型公司甚至一个MySQL实例就搞定了。但现在数据源不仅更丰富,数据量也在指数级增长,从业务的角度,基于hash/range的分区表变得越来越有吸引力。

为了能够对分区表有优异的处理能力,对于查询优化系统来说一个最基本的能力就是做partition pruning,将query中并不涉及的分区提前排除掉,可想而知这可以节省大量的IO/CPU/Network资源,带来成本的降低和性能的提升。

简单图示下什么叫partition pruning,例如下图的Orders表,在date列上按照month做了range分区,每个月单独作为一个partition。

image.png

SELECT avg(amount)
FROM orders
WHERE date BETWEEN '10-01-2013' AND '12-31-
2013';

如上SQL,由于单表谓词在parititon key上,在优化期间即可确定哪些可以分区可以避免访问,即静态pruning。

上例中的数据也可以改写为star schema的形态:

image.png

SELECT avg(amount)
FROM orders
WHERE date id IN
(SELECT date id
FROM date_dim
  WHERE year = 2013 AND 
month BETWEEN 10 AND 12);

这时Orders的值将需要根据date_dim表的动态输出决定,因此对Orders表的partition pruning只能在执行期完成,类似的例子还有动态绑定变量,称为动态pruning。

这篇paper主要介绍了一种基于Cascades框架规范且统一的分区消除方法,支持根据单表/join谓词,静态/动态的做partition pruning,并在Orca中做了实现。关于Cascades和Orca在之前的文章中已有过介绍,如果不熟悉的同学,可以参考如下2篇文章:

Cascades优化器框架

Orca

之所以要介绍这篇paper是由于PolarDB的并行优化框架也参考了Cascades,因此这里提到的方法可以很自然的应用于PolarDB对分区表的优化处理中,我们也在做这方面的规划。

基本算法

引入了3个新的算子来做pruning,且不区分是动态还是静态的:

  • PartitionSelector

PartitionSelector根据谓词生成相关partition ids,主要实现了做partition选择的功能,并将筛选后的ids传递给DynamicScan。

  • DynamicScan

物理scan算子,基于PartitionSelector传递的ids,在做table scan时跳过不需要的partition。

  • Sequence

是一个同步的概念,用于描述PartitionSelector->DynamicScan的生产消费关系,确定谁先执行。

三者的配合有多种方式,如下图:

image.png

(a)表示做full table scan,这时是没有filter的,PartitionSelector直接生成全量partition T1 -> T100给DynamicScan。

(b/c)表示在partition key(PK)上有单表条件(PK=5/PK<30),这时可以只选出目标分区给DynamicScan。

(d)表示了两表 R join T on R.A = T.PK,由于T表是partition table,其scan算子是DynamicScan。这里有所不同的是,对T的过滤需要基于R.A列的值,因此PartitionSelector要加在R的scan上方,用来获取R.A列的value用于过滤分区,并传递给右侧T的DynamicScan。另外可以看到这里不再有Sequence算子了,由于这里Join算子已经保障了R和T执行的先后顺序(先R后T),也就保证了PartitionSelector->DynamicScan的顺序,Sequence不再必要。

了解了这3个算子,其实基本思路就有了:

在原有的physical plan tree中,选择PartitionSelector的放置位置,应放置在可帮助消除分区的谓词(select/join)下方,并利用谓词中pkey相关的部分做分区消除,此外应尽量往下推,靠近对应的DynamicScan,在初期过滤掉更多的分区。

输入:已包含了DynamicScan(对应partition table)的physical operator tree,其中每个DynamicScan算子有其唯一编号partScanID。

输出:插入了PartitionSelector的新tree。

先描述下PartSpec的概念,用来描述partition select的行为,包含<partScanID, partKey, partPredicate>三元组,分别表示对应哪个DynamicScan,其分区列,分区选择谓词。

image.png

针对select(单表条件)的放置算法:

image.png

看起来有些乱,但其实非常简单,面对当前plan tree的selection node(过滤条件)时:

  • 如果其partScanId对应的DynamicScan不在子树中,留在node上方,作为终止位置。
  • 如果在子树中但selection node中没有和PKey相关的谓词,则只是推入op下方,进一步递归处理。
  • 如果在子树中且selection node中有PKey相关谓词,将相关谓词合入到PartSpec的partPredicate中,然后推入op下方进一步递归处理。

image.png

对单表条件做PartSelector放置

上图很好的说明了这个例子,左侧是初始plan tree,右侧上方是初始PartSelector的描述信息(可以从谓词中获取到)。由于select中没有date_id这个表不在子树中,编号为2的PartitionSelector保留在了Select算子上方,而编号为1的则推到了Select下方,并把谓词条件"month >= 10 and month <= 12"加入到了下推的PartSpec中。

针对Join的放置算法

image.png

面对当前以join node为根节点的子树时:

  • 如果其partScanId对应的DynamicScan不在子树中,留在join node上方,作为终止位置。
  • 在子树中,但对应的分区表是Join的外表,由于外->内的驱动顺序,PartitionSelector没法用内表的数据来驱动外表的pruning,只能推到外表侧,看是否可以进一步递归处理(比如利用外表单表谓词)
  • 在子树中,且对应的分区表在内表侧,但join条件本身和partKey无关,则这个join条件对分区消除无帮助,可以推入内表侧,看是否可以进一步递归处理
  • 在子树中,对应的分区表在内表侧,且join条件和partKey相关,则可以推入外表侧,其将join条件中partkey相关的部分融入partPredicate。

image.png

对join放置PartSelector

join的处理流程要更复杂些,主要受限于PartitionSelector->DynamicScan的这种先后依赖顺序,因此如果想根据join condition做动态pruning(如上图),必须要求分区表在被驱动侧(如NL join的内表,HashJoin的probe表)。上图示例中,由于date_dim是驱动侧(build侧),1,2两个PartitionSelector都推下来。

image.png

一个完整的示例包含了针对单表条件的PartitionSelector和针对Join条件的PartitionSelector,可以看到他们都放到了正确的最低位置。尤其注意,2号PartitionSelector所对应的DynamicScan表甚至不是同层的join table,这也充分显示了这种方法的灵活性,可以在更大的子树范围内做pruning。

Greenplum中实现

这套算法实现在了Greenplum的大数据查询优化器Orca中。注意从本质上,distribution和partition是两个正交的概念,在Greenplum这种share-nothing的MPP系统中,每个segment上都可以有对应的多个partitions。

  1. 基于Orca已有Physical Property概念,将partition扩展为新一维的physical property来实现,信息用PartSpec来描述,这样就从<order, distribution>扩展到了<order, distribution, partition>三元组。
  2. DynamicScan则是针对分区表的一种physical operator。
  3. PartitionSelector实现为enforcer,放置在合适的group中,某些group expr(scan..)上方,来满足PartSpec的属性要求,具体放置算法如上节所述。

image.png

上图给出了R HJ S时的4种可能的PartitionSelector enforcer放置方式(灰色是group expression,表示物理算法实现,黑色表示enforcer,用来施加特定物理属性,例如Replicate表示要广播数据)。其中R是probe侧,S是build侧。

Plan 1 表示用PartitionSelector基于R.PK做裁剪,但由于R是被驱动侧,PartitionSelector没法用join condition对R做pruning,因此没有partPredicate。

Plan 2/3 也是类似的处理,只是后续的join方式不同

Plan 4 则使用了不同的hash join顺序,S变为驱动侧(build),R变为被驱动侧(probe),因此这时可以将PartitionSelector推入到S上方,并基于partPredicate: R.PK = S.a对R做过滤!

对于Plan 4可能大家有疑问,为什么不是先做PartitionSelector再做Replicate呢?不是应该尽量下推吗?这是由于为了避免多一次的网络交互,在Greenplum的实现中这种PartitionSelector->DynamicScan的信息传递是不跨segment的,因此是在Replicate后,基于分发后的数据,和R在各个segment内部做局部的partition pruning。

image.png

如上图所示,上方是无效plan因为PartitionSelector和DynamicScan在不同process中(PG的多进程模型),而下图则有效。为了保证这一点,在考虑每个group中的多种可能enforcer组合时(Motion/PartitionSelector),需要约束Motion不能在PartitionSelector上方。

总结

虽然本篇写了不少,但基本思路还是非常简单的,partition pruning是一种性价比非常高的优化策略,一般实现不会太复杂,但却可以大幅度提升查询质量。因此各类系统提出了不同的pruning方案,例如Snowflake,由于其IO/事务的基本单元都是micro-partition,每个micro-partition是immutable的,因此可以维护精确统计信息,利用这种精确统计信息可以更好的实现静/动态的pruning。

对于PolarDB来说,除了优化阶段基于sargable filter的静态剪枝外,也实现了基于hash join的bloom filter做动态pruning,本篇提到的方法实际上是一种补充,不仅适用于hash join,也可用于nest loop join。

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
3月前
|
存储 关系型数据库 MySQL
Optimization and Indexes
MySQL通过索引快速定位具有特定列值的行,避免全表扫描,提高查询效率。常用的索引如PRIMARY KEY、UNIQUE等大多存储在B树中,特殊情况使用R树或哈希索引。索引帮助快速匹配WHERE子句条件的行,减少候选行数,并在多列索引和表连接操作中优化查询。具体特性如B树和哈希索引的比较见特定章节。
sbs
|
SQL 存储 监控
One SQL to Rule Them All: An Efficient and Syntactically Idiomatic Approach to Management of Streams and Tables 论文翻译
One SQL to Rule Them All: An Efficient and Syntactically Idiomatic Approach to Management of Streams and Tables[文件: One SQL to Rule Them All- An Efficient and Syntactically Idiomatic Approach to Manag
sbs
211 0
One SQL to Rule Them All: An Efficient and Syntactically Idiomatic Approach to Management of Streams and Tables 论文翻译
|
SQL 存储 算法
《Optimization of Common Table Expressions in MPP Database Systems》论文导读
Optimization of Common Table Expressions in MPP Database Systems
《Optimization of Common Table Expressions in MPP Database Systems》论文导读
|
SQL 算法 Java
《Speedy Transactions in Multicore In-Memory Databases》
Speedy Transactions in Multicore In-Memory Databases
《Speedy Transactions in Multicore In-Memory Databases》
|
SQL 存储 算法
The MemSQL Query Optimizer: A modern optimizer for real-time analytics in a distributed database
今天我们要介绍的MemSQL就采用这样一种新的形态(Oracle也变为了这种方式 ):即在做transformation时,要基于cost确定其是否可应用。 当然,本篇paper不止讲解了CBQT,还包括一些MemSQL优化器其他方面的介绍,包括一个有意思的heurstic based bushy join的方案。
404 0
The MemSQL Query Optimizer: A modern optimizer for real-time analytics in a distributed database
|
SQL Oracle 算法
Cost-based query transformation in Oracle
这篇paper主要介绍了Oracle从10g开始引入的CBQT(Cost Based Query Transformation)框架。虽然以Oracle历来的风格,无法期待它在paper中讨论很多细节,不过这篇还是可以让我们一窥Oracle对于query rewrite的处理思路和很多非常实用的query rewrite模式,对于开发优化器的同学很有参考意义。 值得一提的是,PolarDB目前也在做这方面的工作,而主要的参考正是这篇paper。此外这篇paper的思路和MemSQL optimizer中对query rewrite的处理思路非常接近,关于MemSQL optimizer的介绍可
331 0
Cost-based query transformation in Oracle
|
算法 关系型数据库 MySQL
Fundamental Techniques for Order Optimization
这是一篇1996年的老paper了,主要讲解了IBM DB2如何针对query当中的有序性进行优化。但对于后续physical property的优化有较为深远的影响,由于DB2的优化器起源于System-R以及其后续演进的starburst,因此延续了system-R中的interesting order和order property的概念。关于system-R的介绍请看之前的文章。 order这种physical property并不只限于order by算子,基于有序的group by/distinct等,都会利用到数据的排序操作,而排序本身就是比较昂贵的计算,因此应该对其做尽可能的优化
227 0
Fundamental Techniques for Order Optimization
|
SQL 关系型数据库 MySQL
Accelerating Queries with Group-By and Join By Groupjoin
这篇paper介绍了HyPer中引入的groupjoin算子,针对 join + group by这种query,可以在某些前提条件下,在join的过程中同时完成grouping+agg的计算。 比如用hash table来实现hash join和group by,就可以避免再创建一个hash table,尤其当join的数据量很大,产生的group结果又较少时,可以很好的提升执行效率。
353 0
Accelerating Queries with Group-By and Join By Groupjoin
|
关系型数据库 PostgreSQL C++