天翼云代理,天翼云代理商,北京代理商
天翼云折扣专线:400-150-1900(全国市话)

更高效的Cascades优化器 - Columbia Query Optimizer

2022-02-21 03:04:28
简介: 在较早的文章中介绍了些Volcano/Cascades优化器结构的规划理念和完结思路,根本是依据论文的解读:VolcanoCascades虽然cascades号称现在最为先进的优化器查找结构,但不得不说这2篇paper的内容,实在是让人看起来有些费劲。尤其是后篇,说是从工程完结的视点来描绘,但解说的不行翔实,并且有些当地既含糊又笼统。此外工业界并没有一款优化器是彻底依据paper的结构去完结的,这

在较早的文章中介绍了些Volcano/Cascades优化器结构的规划理念和完结思路,根本是依据论文的解读:

Volcano

Cascades

虽然cascades号称现在最为先进的优化器查找结构,但不得不说这2篇paper的内容,实在是让人看起来有些费劲。尤其是后篇,说是从工程完结的视点来描绘,但解说的不行翔实,并且有些当地既含糊又笼统。

此外工业界并没有一款优化器是彻底依据paper的结构去完结的,这让我们按图索骥变得愈加困难,不过今天介绍的这篇paper 《EFFICIENCY IN THE COLUMBIA DATABASE QUERY OPTIMIZER》,为理解cascades供给了很好的参阅,并且供给了开源完结

归纳来说,这篇paper详细解说了columbia optimizer的规划和完结,它彻底参阅了cascades中的概念和top-down的查找战略,并做了一系列优化来改善cascades的优化功率。

本文将分为以下几个方面进行介绍:

  1. Cascades优化器的根本概念
  2. Columbia优化器的规划和完结
  3. Calcite中关于Cascades完结的参阅

Cascades优化器的根本概念

查询优化的本质,是

给定query和价值模型等元信息的前提下,查找计划空间的一切或许,得到价值最低的履行计划。

这是一个NP hard问题,人们通常会运用dynamic programming的思维,将其转化为一个problem和其包括的一系列嵌套的subproblems。经过递归获取子问题的最优解并向父问题延伸,终究得到根问题的最优解。

cascades的优化结构也遵从了这个思路,只不过与system-R等不同,它不选用子problem -> 父problem的直观道路,而是从父problem -> 子problem深度优先递归办法,也便是所谓的top-down search strategy,并在进程中运用memorization防止对相同子problem的重复核算。深度优先的好处是能够尽早获取到一个可行的物理计划和对应cost,并以该cost作为上界在后续search中做剪枝。

关于cascades这儿只大约给出一些根本概念,便利理解文章后续的内容,总的来说仍是需求读者有cascades的根本理论知识,详细的内容能够参见前面的文章或读原paper。

operator

描绘某个特定的algebra运算,关于联系代数便是例如join / aggregation等。分为logical/physical两类

  1. logical operator描绘算子的逻辑语义,与详细完结无关。
  2. physical operator描绘算子的详细完结算法,例如对join有hash join/nested loop join等。

expression

在计划空间中描绘一个详细的algebra运算内容的数据结构,假如是联系代数那就标明一个联系运算表达式。内部会包括对应该运算的operator,依据operator的不同类型分为logical expression/physical expression。一般一个logical expression会对应多个详细完结的physical expressions。

logical property

运算成果所具有的逻辑特点,这类特点与运算的语义相关,与详细完结无关,例如数据的cardinality/schema等。

physical property

运算成果所具有的物理特点,这类特点与详细完结办法相关,例如数据的collation(次序)/distribution等。

group

上面提到了优化的进程是对problem/subproblems的求解,由于问题的杂乱性,dynamic programming会对problem做必定的“拆分”,而group便是按照逻辑等价性进行拆分的成果,不同group对应不同的subproblem,然后把问题规划不断减小。

详细到完结中,group是一切逻辑上等价的expression的调集,其间一切expression(包括logical expression + physical expression)的逻辑特点相同,构成逻辑等价类。

search space

寄存优化进程中一切枚举的计划的数据结构,跟着优化进程的进行会生成若干潜在的候选plan tree,内部会运用memorization来防止生成重复的计划内容,削减无谓空间/时刻浪费。

search space是依据group来组织的,一切各类expression包括在所属group内部。所以组织层次上是

search space -> group -> m-expr

rule

rule是查找算法中最为中心的部分,各种计划(节点)的生成是经过rule来完结的,rule总是运用到一个logical expression上,将其转化为另一种logical expression或许一个physical expression。

logical -> logical的转化,称为transformation rule。

logical -> physical的转化,称为implementation rule。

无论是哪种rule,都包括两个最首要的结构:

  1. Pattern: 描绘rule能够运用到的表达式形状。
  2. Substitute : 描绘转化之后的表达式形状。

因而一个rule是否能够运用,要判别Pattern是否和现有的expression tree能match上,能够的话,就把substitute对应的新expression tree树立起来,参加search space中,作为候选计划的一部分,然后完结对计划空间的扩展。例如,一个transformation rule是predicate pushdown,那它所要求的pattern就必须是

   

假如expression tree上是join -> join,则不满意这个pattern而无法运用这个rule。转化后的substitute为右图

enforcer

由于cascades的查找进程是top-down的,这意味着对当时的expression tree优化时,总是先处理root expression,一旦确认了其物理履行算法,会向children做递归优化,这时root expression的物理完结或许会对children的输出物理特点有所要求,例如top join假如运用sort merge join,则要求2个input在对应的join key上是有序的,这种对物理特点的要求构成了optimization goal的一部分。

enforcer是一个physical operator,作用是改动input group的物理特点,来满意上层算子的要求。


Columbia的规划与完结

这节参阅了paper的首要内容,侧重介绍search engine的规划完结和相对cascades关于查找功率的改善。

三大组件:

这儿的根本思路和cascades一样,经过不同类型的task来驱动整个优化流程,扩展seach space,task的中心是对rule的运用。

Search Space

首要介绍一个columbia optimizer中的重要概念,multi-expression

一个query在经过parser后会被转化为由expression(缩写expr) 组成的AST,而multi-expression(缩写m-expr)则是expr的"紧凑"版别,两者同享了描绘运算语义的operator方针,但expr的输入是子expr,而m-expr的输入则是子group,其内部是恣意满意该运算语义的m-expr调集,例如关于表达式:

(t1 inner_join t2) inner_join (t3)

其left input是 (t1 inner_join t2),但这仅是一个逻辑运算符,内部或许对应

t1 inner_join t2 / t2 inner_join t1 / t1 hash inner join t2 / t2 nested loop inner join t1 .....

等许多的详细表达式(logical/physical),但都属于同一个group。

m-expr是group中的首要组成成员,优化进程也首要依据m-expr进行。

在构建初始search space时,会把parser生成expr tree CopyIn search space中,形成一系列basic groups,这些group中只要一个initial logical m-expr,与AST中的expr一一对应。后续优化中,group中的m-expr会不断添加,或许search space中发生新的group。

SSP (Search Space,对应Memo)
|-> groups (array)
       |-> m-exprs
              |-> operator (logical/physical)
              |-> input groups (array)

代码中几个首要结构类和内部一些重要成员:

class SSP	//Search Space
{
public:
	M_EXPR ** HashTbl;	// To identify duplicate MExprs
	// Convert the EXPR into a Mexpr. 
	// If Mexpr is not already in the search space, then copy Mexpr into the 
	// search space and return the new Mexpr.  
	// If Mexpr is already in the search space, then either throw it away or
	// merge groups.
	// GrpID is the ID of the group where Mexpr will be put.  If GrpID is 
	// NEW_GRPID(-1), make a new group with that ID and return its value in GrpID.
	M_EXPR * CopyIn(EXPR * Expr, GRP_ID & GrpID);	
	
	//If another expression in the search space is identical to MExpr, return 
	// it, else return NULL. 
	// Identical means operators and arguments, and input groups are the same.
	M_EXPR * FindDup (M_EXPR & MExpr);		
private:
	GRP_ID	RootGID;	//ID of the oldest, root group.  Set to NEW_GRPID in SSP""Init then
        //Collection of Groups, indexed by GRP_ID
        CArray Groups;
}; // class SSP

class GROUP      
{	
public:
	GROUP(M_EXPR * MExpr); //Create a new Group containing just this MExpression
	inline GRP_ID GetGroupID() {return(GroupID); };
	
	//Return winner for this property, null if there is none
	WINNER * GetWinner(PHYS_PROP * PhysProp); 
	//Create a new winner for the property ReqdProp, with these parameters.
	//Used when beginning the first search for ReqdProp.
	void NewWinner(PHYS_PROP *ReqdProp, M_EXPR * MExpr, COST * TotalCost, bool done);
private	: 
	GRP_ID	GroupID;	        //ID of this group
	M_EXPR * FirstLogMExpr;	        //first log M_EXPR in  the GROUP
	M_EXPR * FirstPhysMExpr;	//first phys M_EXPR in  the GROUP
	LOG_PROP *	 LogProp;       //Logical properties of this GROUP
	COST * LowerBd;			// lower bound of cost of fetching cucard tuples from disc
	// Winner's circle
	CArray < WINNER *, WINNER * > Winners;
};  // class GROUP

class M_EXPR 
{
private:
	M_EXPR * HashPtr;			// list within hash bucket
	BIT_VECTOR 		RuleMask;	//If 1, do not fire rule with that index
	OP*		Op;					//Operator
	GRP_ID*	Inputs;
	GRP_ID 	GrpID;				//I reside in this group

	inline int GetArity() {return (Op -> GetArity()); } ;
	//We just fired this rule, so update dont_fire bit vector
	inline void  fire_rule(int rule_no) { bit_on( RuleMask , rule_no); };	
}; // class M_EXPR

class WINNER    
{
private:
	M_EXPR    *   MPlan;
	PHYS_PROP *  PhysProp;       //PhysProp and Cost typically represent the context of
	COST      *  Cost;           //the most recent search which generated this winner. 
	bool		 Done;	     //Is this a real winner; is the current search complete?
}; //class WINNER

search space的总体规划与cascades没什么不同,但做了如下改善

GROUP

为了进步查找功率,针对group做了2点改善

  1. 引进lower bound cost,做更积极的group pruning

在top-down的优化中,详细到某个group时,会有对应的search context(cascades中的optimization goal),context中包括2个部分:

[required physical property , cost upper bound]

也便是在向下查找时,要求当时group输出的物理特点,一起整个子计划的价值要小于cost上界。

子计划的价值是当时group的physical m-expr价值 + 各个input group中最优physical m-expr的价值。有没有或许在优化input group之前,就能判别其价值现已过大了,然后完结pruning呢?

columbia为每个group核算一个cost lower bound,并确保group枚举的任一physical m-expr对应的子plan,其cost(m-expr) > lower bound。这个lower bound是依据group的logical property核算的,和详细算子完结办法无关,在group创立时核算完结。

剪枝进程如下图,top group的physical m-expr已生成并可得到该算子的local_cost,在顺次优化基层input group时,假定input group1已优化完得到subplan cost1,而input group 2/3没有优化,但此刻

top local cost + input cost1 + input_group2's lower bound cost > Cost Limit

标明无法找到满意context的最优解,然后防止了对input group 2/3的无意义优化。

lower bound cost的详细算法如下:

|G| = group输出的cardinality

cucard(Ai.X) = Ai.X这列在G的输出成果中具有的最大NDV值

cucard(Ai) = max(cucard(Ai.X)) 标明关于一切在G输出成果中的Ai表的列,具有的最大NDV值

touchcopy() = 对一行join成果做拼接并copy出去的价值

Fetch() = 经过IO读取一行数据的价值

这个公式给出了一个join plan的总价值:

LowerBound(join sequence) = cost(top join) + cost(non-top join) + cost(base table fetch)

关于cost(non-top join)的部分,我们考虑t1 join t2 ... join tn 这个序列,且以left-deep tree为例:

non top join = t1 join t2 ... join tn-1

假如t2在G中具有cucard(t2)个NDV值,阐明t1 join t2至少发生了cucard(t2)行数据,对t3...tn-1也同理类推,由于核算的是下界,这是安全的。paper中证明了即便不是left-deep tree这个推导也树立,这儿不再胪陈。

Lower bound pruning是columbia对优化功率的一个重要改善,能够很好的进步剪枝功率,缩短优化时刻,一起由于是依据价值模型核算的cost下界,这种pruning并不影响成果的最优性。

2. 将logical m-expr和physical m-expr别离到2个链表中

在cascades中,一切m-expr是保持在一个link list中,而columbia则分为了logical/physical两个list,这在2种情况下有益:

2.1 优化一个group时,会对一切logical m-expr做rule binding的操作,假如是一个list则要跳过physical m-expr,考虑到group中的m-expr或许许多,并且physical m-expr数量会是logical m-expr的N倍,因而或许引发较多memory page fault。而rule binding是一个密集操作,因而微小的改善也有意义。

2.2 一个group或许会依据不同的search context(例如不同required physical property)重复被优化,因而将之前生成的physical m-expr保存在一起,当下次优化时能够先检查是否现已有physical m-expr能够满意新context的要求,假如能够则直接进入针对该m-expr的优化流程。而在cascades中,每个group并不特别处理physical m-expr,总是要对一切logical m-expr重新优化来生成physical m-expr,这显然不行高效且浪费空间。

WINNER

由于group会依据不同search context重复优化,因而每个context应该都会找到一个最优plan,这些plan就保存在group的winners数组中,以备后续复用。

在columbia中,WINNER结构被简化,只包括

[当时group中最优的(physical) m-expr + 最优plan cost + required physical property]

在依据当时search context查找进程中得到的最优解(临时)也会保存在winner结构中。此外,假如最后无法找到满意要求的plan,这个winner方针依然会创立出来,只不过其间的m-expr是null pointer,标明无法找到context的最优解,这也是一种成果,能够保存下来被复用。

M_EXPR

前面现已提到了EXPR和M_EXPR,以及何时从EXPR生成M_EXPR。相关于cascades中用来描绘该结构的EXPR_LIST,columbia的M_EXPR要愈加精简。

留意在上面代码中M_EXPR类的这个字段

BIT_VECTOR 		RuleMask;  // 每个rule对应一个bit,假如fire过则设为1

RuleMask完结了columbia中的unique rule set原则,也便是关于一个m-expr,任一个rule只会对其fire(测验apply)一次,不会做重复的核算,这关于进步查找功率很重要,一起能够确保不会生成重复的physical m-expr。但留意logical m-expr的仅有性只经过unique rule set是无法确保的,由于或许有不同的logical m-expr经过不同的transformation rule生成相同的logical m-expr。因而需求在整个search space中树立logical m-expr的去重机制。这经过SSP的

M_EXPR ** HashTbl;	// To identify duplicate MExprs

这个成员完结,这是一个针对logical m-expr的hash table

其key是[operator name, operator parameters, input group numbers],value便是对应的m-expr pointer。

hash table的完结依然是静态的buckets + linked list,仅仅做了2点改善:

  1. 运用了愈加简略高效的hash function lookup2.
  2. buckets的数量不再是一个质数而是2的幂,mod操作更高效

考虑到logical m-expr的生成是个十分频繁的操作,结构的精简+查找功率进步是不错的空间/时刻优化。

Rules

rule是对m-expr进行优化的首要东西,rule和search space的界说、search strategy的界说是正交的,因而能够独立的进行扩展。

在columbia中,rule的基类界说如下:

class RULE
{
private : 
	EXPR    *  original;     // original pattern to match
	EXPR	*  substitute;   // replacement for original pattern
				 //"substitute" is used ONLY to tell if the rule is logical or physical,
				 // and, by check(), for consistency checks.  Its pattern is represented 
				 // in the method next_substitute()
	CString    name;
	BIT_VECTOR mask;	 //Which rules to turn off in "after" expression
	int index;		 // index in the rule set
	
public :
	bool top_match (OP *op_arg)
	{
		assert(op_arg->is_logical()); // to make sure never O_EXPR a physcial mexpr 
		
		// if original is a leaf, it represents a group, so it always matches
		if( original->GetOp()->is_leaf()) return true;  
		
		// otherwise, the original pattern should have a logical root op
		return ( ((LOG_OP *)(original->GetOp()))->OpMatch((LOG_OP *)op_arg) ); 
	};
	
	virtual int promise ( OP * op_arg, int ContextID)
	{	return ( substitute->GetOp()->is_physical() ? PHYS_PROMISE : LOG_PROMISE); };
	
	virtual bool condition ( EXPR * before, M_EXPR *mexpr, int ContextID)
	{ return true; };
	
	//Given an expression which is a binding (before), this
	// returns the next substitute form (after) of the rule.
	virtual EXPR * next_substitute (EXPR * before, PHYS_PROP * ReqdProp)=0;
}; // RULE

其间最重要的结构便是original(pattern)和substitute,他们都是用EXPR来构建的tree(logical operator的tree)。

在其间有一种特别的operator: leaf operator,他是expr tree的叶子节点,起到通配作用,能够match恣意子表达式。例如inner join的结合律: 

RULE EQJOIN_LTOR
Pattern:     ( L(1) join L(2) ) join L(3)
Substitute:  L(1) join ( L(2) join L(3) )

其间L便是leaf operator,在APPLY_RULE这个task中,会进行pattern match,树立rule和top m-expr的绑定联系(rule binding),然后经过substitute来生成新的m-expr/group方针,参加search space中。详细包括2个首要函数:

  1. 创立BINDERY方针,递归做pattern matching,进程中或许发生许多rule binding
  2. 对每个binding,RULE::next_substitue创立对应的新m-expr tree,并经过SSP::copy_in参加search space中

此外,RULE方针还有些办法辅佐这个apply进程:

  1. top_match() 用来比较rule的top operator和当时root m-expr的top operator,相当于预检查,假如不匹配能够快速skip。
  2. promise() 依据当时的search context对rule进行打分,分数越高标明rule关于优化越有意义,一起promise值能够决议rule是否会测验apply,promise <= 0时,不做apply。默认implementation rule的promise = 2,而transformation rule的promise = 1,因而implementation具有更高的优先级,总是会优先生成physical m-expr并递归优化下去,这样有助于尽快找到尽量优的physical plan tree,确保深度优先的机制,也有利于后续优化做依据cost limit的剪枝。

Rule Binding

依然以上面的EQJOIN_LTOR结合律为例

Pattern:     ( L(1) join L(2) ) join L(3)
Substitute:  L(1) join ( L(2) join L(3) )
Target m-expr: (G7 join G4) join G10

针对每个group,会创立一个BINDERY方针,担任将pattern与该group中一切logical m-expr做binding。

本例中

  1. 一个expression bindery方针会用来匹配top expr和top logical m-expr。
  2. 一个group bindery方针对left input group的一切logical m-expr做检查,树立多个的binding:

L(1) join L(2) <=> G7 join G4

L(1) join L(2) <=> G4 join G7

3. 另一个group bindery方针对right input group做检查,但只会树立一个binding

L(3) <=> G10

详细绑定算法用一个依据3个状况的有限状况机来完结,并经过BINDERY::advance()办法来驱动,不断寻觅binding:

typedef enum BINDERY_STATE
{
        start,          // This is a new MExpression
	valid_binding,  // A binding was found.
	finished,       // Finished with this expression
} BINDERY_STATE;

advance的伪代码如下:

根本思路便是深度优先的办法,不断找input group中的binding,再回来上层group找,直到top m-expr,详细到m-expr的匹配则是比较operator的id和arity,可参阅完结源码。

Enforcer Rule

前面现已提到enforcer是一种physical operator,因而enforcer rule也算是implementation rule,会生成对应的enforcer m-expr,属于physical m-expr,其input group便是原来的group,仅仅改动了physical property。例如SORT_RULE是一个enforcer rule,在substitute中会以QSORT这个operator作为top operator,即:

SORT_RULE
Pattern: L(1)
Substitute: QSORT L(1)
L(1) 是恣意leaf operator

只且仅当group的search context中有physical property requirement时,enforcer rule才会被考虑(promise > 0)。

关于enforcer rule的处理在columbia和cascades中十分不同:

  1. 没有excluded property

在cascades中,search context中会包括额定一项:excluded properties,即制止生成某种物理特点,这是为防止重复apply enforcer rule而发生多个冗余的enforcers,但不同的search context下依然会生成多个对应physical property要求的enforcer。

在columbia中,enforcer rule被参加到RuleMask傍边,这样经过unique rule set机制能够防止重复生成enforcer,但也带来了一个问题:group第一次被优化后生成了enforcer m-expr,后续假如依据不同search context再优化,就无法再生成新的enforcer导致计划空间枚举不全。columbia经过复用enforcer m-expr来处理该问题。

2. 复用enforcer m-expr

在cascades中,enforcer的operator包括参数,例如QSORT(A.x) 和 QSORT(A.y)是两个不同的enforcer m-expr,都会生成在group中,应对不同context的物理特点要求。

在columbia中,enforcer的operator不包括参数,仅仅行为描绘,这样一个group只会有一个enforcer m-expr,一起在优化group时,必定要对一切已有的physical m-expr检查是否满意物理特点要求,这时假如遇到enforcer m-expr,则忽略参数以为可满意,然后进入physical m-expr的优化流程,这儿会对enforcer m-expr和其输入重新核算cost,假如是最优plan则记入winner中。后续能够从WINNER类的physical property成员补全对enforcer的完好描绘,弥补parameter的缺失。

这种处理计划有些tricky,但也削减了enforcer m-expr的数量,节约空间。

Tasks

task是查找进程的使命单元,现有task的履行会创立和调度新的task。每个task都对应着当时优化的search context。对task的介绍也是对整个优化流程的介绍,这儿仍会结合代码。

class TASK
{
	friend class PTASKS;
private :
	TASK        * next;         // Used by class PTASK
protected :
	int	 ContextID;      // Index to CONT::vc, the shared set of contexts
	int      ParentTaskNo;   // The task which created me   
public :
	virtual void perform ()=0;	//TaskNo is current task number, which will
}; // TASK

class PTASKS
{
private :
	TASK        * first;        // anchor of PTASKS stack
public :
	void push (TASK * task);
	//##ModelId=3B0C085D016B
	TASK * pop ();
}; // PTASKS

一切详细task都是TASK的子类,和cascades paper中一样,task以stack的办法来调度。有了这种笼统,优化的流程实际很简略:

void SSP::optimize()
{	
	//Create initial context, with no requested properties, infinite upper bound,
	// zero lower bound, not yet done.  Later this may be specified by user.
	PTasks.push (new O_GROUP (RootGID, 0, 0));
		
	// main loop of optimization
	// while there are tasks undone, do one
	while (! PTasks.empty ())
	{
		TaskNo ++;
                TASK * NextTask = PTasks.pop ();
		NextTask -> perform ();
        }
}  // SSP::optimize()

在columbia中,task共分为5种,其相互调用联系如下:

O_GROUP: 优化一个group

这是对plan/subplan做优化的最外层入口,会找到在特定search context下,这个group作为root的最优plan,保存到group的winner数组中。它会生成一切必要的logical/physical m-expr,并核算cost。伪代码如下:

  1. 假如lower bound现已大于upper bound,则没必要优化,无解
  2. 假如现已对这个context得到了winner,直接回来

假如未优化过则分别对logical / physical m-expr做优化:

  1. 对每个logical m-expr,创立O_EXPR task,依据新的search context优化该m-expr
  2. 对每个physical m-expr,判别是否满意新context中的property requirement,假如满意则创立O_INPUTS task,对以该physical m-expr作为root的子表达式核算价值

留意这儿O_INPUTS是后入栈的,因而会先履行,依然是为了尽快发生physical plan

在整个优化流程中,O_GROUP会在2种情况下出现:

  1. 初始状况,basic groups中只会有一个logical m-expr,只会发生一个O_EXPR task并入栈,在其履行中生成更多m-exprs
  2. 后续,group会以不同的search context被再次优化,这时group中或许现已有一些winners/m-exprs,因而需求有对winner的复用检查和依据新context对m-expr重新优化。

E_GROUP: 扩展一个group

前面介绍rule pattern match时看到,假如对应input group的sub-pattern是一个leaf operator,那总是能够匹配,但假如不是(包括其他operator),则假如想在对应的input group中获取一切binding,需求获取其间的logical m-expr进行比较,这便是E_GROUP task要完结的作业。这儿其实是cascades paper中讲述最含糊的当地,但思维很简略,便是在有需求做pattern match时,为了尽或许找到一切rule binding,对input group做exploration,生成一切logical m-exprs。

这儿经过符号完结memorization,确保group只被explore一次。另外在代码中,不需求对每个logical m-expr做O_EXPR,只需求对第一个logical m-expr即可。

在cascades paper中,E_GROUP task会创立E_EXPR task,标明对已有logical m-expr运用一切transformation rules,但由于和O_EXPR的作业有重合,columbia中去掉了E_EXPR兼并为O_EXPR,经过exploring这个符号标明只做tranform不做implement。

O_EXPR: 优化一个logical m-expr

在columbia中,两种场景会树立O_EXPR task:

  1. O_GROUP task中,需求对方针logical m-expr履行一切的rules(按照promise的次序),包括依据transformation rule生成新的logical m-expr和依据implementation rule生成physical m-expr。
  2. E_GROUP task中,对group内的logical m-expr履行transformation rule,生成新的logical m-expr做sub-pattern的匹配。

首要要搜集rule set中能够fire的rules:

  1. 假如现已fire过,不再测验 (unique rule set)
  2. 当时是exploring,不测验implementation rule
  3. top_match做预检查,不匹配的跳过
  4. 依据当时context核算promise,只要promise > 0才测验apply

然后针对排好序后的rules,顺次

  1. 生成APPLY_RULE task,针对特定rule和特定logical m-expr,以及当时优化context,做详细优化
  2. rule pattern中的input不是leaf operator,则生成E_GROUP task做exploration

留意这儿E_GROUP后入栈,因而先深度递归完结了exploration后,再履行APPLY_RULE,task中一切subtree的logical m-exprs已生成,直接查找binding即可。

APPLY_RULE: 详细改换作业

rule总是apply到logical m-expr上,依据rule的类别生成logical/physical m-expr,前面现已讲过依据bindery的根本流程,这儿看下伪代码:

首要经过RuleMask防止重复apply。然后循环调用root BINDERY::advance()获取下一个binding。这儿还会对rule做一下condition的检查,不满意则不apply,不然调用next_substitute获取新的m-expr并参加search space中:

  1. 新生成的是logical m-expr,触发O_EXPR使命,扩展计划空间
  2. 新生成的是physical m-expr,触发O_INPUTS使命,完结后续价值核算

O_INPUTS: 获取一个表达式的最优plan

在implementation rule运用后能够得到top operator的详细物理完结,但优化还要递归到一切inputs来获取整个subtree的物理plan,这便是O_INPUTS的作业。它核算每个input group中,针对context的最优physical plan,把cost不断累加,终究得到整个subtree的plan,假如plan cost是针对该context的现在最小值,则记入到winner中。

O_INPUTS也是columbia中运用pruning战略的当地,首要包括2种:

  1. lower bound pruning,前面已介绍过
  2. global epsilon pruning : 当完好优化出一个plan后,假如其cost小于一个预设的上界GLOBAL_EPS,则以为该plan满足优,结束优化流程。很显着这牺牲了plan最优性来进步优化功率,并且关于GLOBAL_EPS的设定会十分困难。

伪代码:

在代码中经过Pruning / CuCardPruning / GlobepsPruning 3个符号敞开3档剪枝战略:

  1. Pruning便是常规的top-down branch and bounding,用当时累计cost和cost limit比对
  2. CuCardPruning标明敞开lower bound pruning,其和Pruning的区别是对没有优化的group i,InputCost[i]不是0而是group创立时核算的lower bound cost
  3. GlobepsPruning 在完结优化时进行一次判别,假如以为满足优,直接记为winner。

能够看到核算cost的进程是对每个没有优化过的input group,触发O_GROUP task得到基层最优解并记录在InputCost[]数组中,一起累加到CostSoFar,积极判别CostSoFar > upper bound来做pruning。

当以该physical m-expr为root的plan优化完结后,判别是否search context中的当时最优解,是则替换winner中原最优解。

至此整个search流程就整理完了,整体仍是比较明晰的:

  1. 在初始时只要basic groups,每个group中一个initial logical m-expr,和AST中的expr一一对应
  2. 开始对top group履行O_GROUP task,其间会对仅有的m-expr调用O_EXPR,生成logical/physical m-exprs,并优先对生成的physical m-expr生成O_INPUTS task,递归下去得到完结的physical plan,并用其cost更新search context中的cost upper bound,协助后续pruning。
  3. 新生成的logical m-expr会继续优化导致一系列m-expr的生成,然后在已有group中扩展新的m-expr或在search space中参加新的group
  4. 优化进程中会不断有physical m-expr的生成,然后就递归到基层去生成对应的plan/subplan,并得到各个层次上的部分最优解记入winner中,并回来到上层做汇总,终究回到top group得到完好physical plan
  5. 终究top group不再有新的logical / physical m-expr生成时,优化结束,top group winner便是最优plan

假如大家有爱好能够去看下Columbia的代码,由于是实验项目比较简略易懂。


Calcite对Cascades的规划参阅

Calcite是产业界(尤其大数据范畴)运用十分广泛的CBO查询优化器,也是为数不多的依据volcano/cascades的完结。但在粗读其代码和检查一些技术资料后,发现前期版别中的VolcanoPlanner并不是一个严厉依据cascades论文的完结,更像其前期原型Exodus的计划。大致来说,便是查找路径并不具有top-down的规矩,而是“随机”落在search space的某个RelNode上。

从代码上看便是对RuleMatch(rule binding)的apply不是以RelSet/RelSubSet为单位从上到下的进行,而是大局同享一个RuleMatch的优先级队列,向其间插入RuleMatch首要在2个机遇点:

  1. 初始化时调用VolcanoPlanner.setRoot(),设置root RelNode,这会递归触发fireRules,对已有RelNode tree树立一系列RuleMatch,并核算各自的Importance(promise),进入大局RuleQueue。
  2. 在优化进程中生成新的RelNode时,会遍历一切rules,测验为其树立RuleMatch调集并核算importance,参加RuleQueue。
  3. 整个进程继续直到RuleQueue为空或许迭代到方针次数。

这样的好处便是能够经过迭代次数有效约束优化时刻,但害处也十分显着,没有了top-down的路径,无法完结pruning,关于physical property的处理也难以优化。

2020 年 7 月MaxCompute(ODPS)团队向社区兼并了top-down优化的patch,使得Calcite成为一个真正意义上的cascades优化器,其内部完结便是参阅了Columbia的规划,并针对Calcite原有流程和结构进行适配,但并没有引进global epsilon pruning的机制。由于作者并不太熟悉Calcite的代码细节,这儿就浅尝辄止了,更多介绍可参阅这篇文章


总结

跟着大数据的涌现和实时剖析需求变得越来越重要,数据库系统关于杂乱查询的处理才能逐渐成为武器库的根本配备,作为履行计划的生成组件,优化器的功能性会对系统的核算才能发生质的影响。

cascades结构是理论上最为灵活且扩展性最强的优化器完结计划,但惋惜现在业界成熟的完结并不多,开源的更少。

  1. Calcite虽然广为运用但其本身首要针对异构数据源的兼容,在优化规矩的支撑上并不很丰厚
  2. Orca虽然强壮,但由于与Postgres生态的耦合较深,导致无法被广泛运用
  3. CockroachDB的优化器虽然也选用cascades,但首要集中在heuristic上,cost based的部分较少
  4. 最为成熟的应该是G. Graefe主导的SQL Server optimizer,惋惜不开放...

看起来最值得研究的仍是GPORCA,后续笔者争取能写一些关于Orca的规划完结和代码剖析文章,敬请期待。

12年经验 · 提供上云保障

服务热线:132-6161-6125(手机) 400-150-1900(全国市话)

站内导航: 天翼云服务器价格| 天翼云购买流程| 天翼云代理| 北京天翼云代理商| 杭州天翼云代理| 深圳天翼云代理商| 钉钉代理商| 阿里云代理| 公司官网

我公司收款账号| 天翼云备案系统

CopyRight © 2019 天翼云代理商. All Rights Reserved 京ICP备11011846号-15 管理-北京志远天辰科技有限公司