Trino-查询流程(上)

593 阅读22分钟

查询流程

01、Query生命周期

图1所示:在QUEUED阶段会完成Statement的语法解析和语义判断,生成Analysis对象。在Planning阶段,会根据Analysis生成执行计划,让调度到满足的节点后会将任务发送到Worker上进行,从而进入到Running阶段,下面会详细描述Statement是如何生成物理执行计划在Worker上进行执行的。
![avatar][base64_Query生命周期]

02、语法解析及语义校验

语法规则使用Antlr4来描述,收到SQL后会按照Antlr4的模式来进行词法解析和语法解析。这一阶段发生在Query生命周期的初始化阶段,在经过语法解析和语义校验成功后,才会为Query分配资源。

02.1 时序图

1、SQL通过Client使用restful接口提交到Coordinator。QueuedStatementResource类负责接收请求,并将请求封装成Query对象放入到查询队列中;
2、QueuedStatementResource绑定了DispatchManager对象来调度队列中的Query对象,并将其进行SQL解析转化成PreparQuery对象;
3、DispatchFactory会将PreparQuery转换成SQL对应的语法树,同时生成Analysis对象;
4、这一步完成后,请求的后续调度会从QueuedStatementResource转移到ExecutionStatementResource中。SqlQueryExecution会维护一个Statemachine描述其当前的状态;
5、ExecutionStatementResource中的SqlQueryManager来负责处理请求的后续操作,它会调用SqlQueryExecution自身的start方法来实现后续的分布式执行计划生成和Stage中(task、split)的调度;
6、具体Antlr4语法解析这里不进行描述

02.2 核心功能分析

功能:将输入的查询Statement按照Antlr语法解数的格式进行解析,将Statement字符串先进行分词再按照语法构造出g4文件中描述的各个节点,按照规则构成一个Node树。
查询类的Statement是Query,在经过词法分析、语法解析后会生成的对应的Statement如下:

02.2.1 SqlQueryExecution

使用SqlQueryExecutionFactory来创建SqlQueryExecution对象。SqlQueryExecution对象主要是构造Sql查询运行的上下文信息,如集群元数据,数据源管理类,集群节点管理类。在构建此对象时Query仍然处于状态机的Queued阶段。 1、类的成员

2、功能--SQL语义校验(Analysis) 在构造SqlQueryExecution对象时,会对收到的PrepareQuery中的Statement进行语义的校验。具体校验的流程是:

03、逻辑执行计划

在生成执行计划和调度时,要依赖LogicalPlanner,PlanFragmenter,DistributedExecutionPlanner和SqlQueryScheduler这几个类,下面是简单介绍一下这几个类: LogicalPlanner:负责生成逻辑执行计划(包含逻辑执行计划优化PlanOptimizers); PlanFragmenter:将 LogicalPlanner 生成的逻辑执行计划,拆分为多个子计划; DistributedExecutionPlanner:将 PlanFragmenter 拆分好的子计划,进一步拆分成可以分配到不同 Worker 节点上运行的 Stage; SqlQueryScheduler:将 Stage 调度到不同的 Worker 节点上运行;

03.1 生成逻辑执行计划Plan得UML

03.2 PlanNode介绍

这个部分的功能是:在语法解析后,将查询转化成逻辑计划。在analysis种,已经将SQL中的(select信息,from-table表信息,where信息,聚合函数信息,limit信息,order by,group by等)解析出来,将SQL中的不同部分映射到对应的PlanNode即可(在RelationPlan使用visit模式来生成PlanNode)。在查询中核心的PlanNode枚举如下:

节点名称 含义
AggregationNode 聚合操作的节点,有Final、partial、single三种,表示最终聚合、局部聚合和单点聚合,在执行计划优化前,聚合类型都是单点聚合,在优化器中会拆成局部聚合和最终聚合
ExchangeNode 逻辑执行计划中,不同Stage之间交换数据的节点
FilterNode 进行Filter过滤操作的节点
JoinNode 执行Join操作的节点
LimitNode 执行limit操作的节点
MarkDistinctNode 处理count distinct
OutputNode 输出Node
RemoteSourceNode 类似于ExchangeNode,在分布式执行计划中,不同Stage之间交换数据的节点
ProjectNode 将下层的节点输出列映射成上层节点 例如:select a + 1 from b将TableScanNode的 a 列 +1 映射到OutputNode
SampleNode 抽样函数Node
RowNumberNode 处理窗函数RowNumber
SortNode 排序Node
TableScanNode 读取表的数据
TopNNode order by ... limit 会使用效率更高的TopNNode
UnionNode 处理Union操作
WindowNode 处理窗口函数

RelationPlan在构造PlanNode时的visit操作如下,其中QueryPlanner负责解析select,order by,limit等部分的PlanNode。

visit操作 作用
visitTable 生成TableScanNode
visitAliasedRelation 处理有别名的Relation
visitSampledRelation 添加一个SampleNode,主要抽样函数
visitJoin 根据不同的join类型,生成不同的节点结构,
一般来说是将左右两边生成对应的queryPlan,
然后左右各添加一个JoinNode相连,
让上层添加一个FilterNode,FilterNode为join条件
visitQuery 使用QueryPlanner处理Query,并返回生成的执行计划
visitQuerySpecification 使用QueryPlanner处理QueryBody,并返回生成的执行计划
visitUnion 处理union操作
核心处理:执行计划的核心在RelationPlanner的visitQuerySpecification中---->QueryPlanner的plan(QuerySpecification node)

总结这一步的流程如下: a、使用visitor模式遍历完analysis中各个部分后,进行语义判断;如果校验成功则可以生成逻辑执行计划;否则会抛出异常,查询结束。visit的类型和生成的PlanNode类型如上表格枚举; b、在生成逻辑执行计划后,会一次应用PlanOptimizer中的各个规则(规则有优先级顺序)来优化执行计划。在优化过程中,优化器会在Plan中插入Exchange结点。之后的PlanFragmenter会根据这些Exchange结点将Plan切分成SubPlan。 c、优化完成后,最终生成Plan对象。

03.3 PlanOptimizer的规则

优化策略 作用
AddExchanges 委托在StatsRecordingPlanOptimizer优化器中进行优化。优化的策略如下:
a、对于AggregationNode,将其拆分成局部聚合和最终聚合(Final、partial、single),在两个聚合之间增加ExchangeNode;
b、对于三种类型的Join,根据数据表特性会出现(有group by字段时增加一个ExchangeNode,无左右分别增加ExchangeNode);
c、对于窗口函数下增加ExchangeNode;
d、对于count-distinct对增加ExchangeNode。
CanonicalizeExpressions 将执行计划中的表达式标准化,比如将 is not null 改写为not(is null),将if语句改写为case when
CountConstantOptimizer 将count(a)改写为count(*)提高不同数据源的兼容性
HashGenerationOptimizer 提前进行hash计算
IndexJoinOptimizer 将Join优化为IndexJoin,获取Join表的索引,提升速度
IterativeOptimizer LimitPushDown limit条件下推,减少下层节点的数据量
MetadataQueryOptimizer 将对表的分区字段进行的聚合操作,改写为针对表元数据的查询,减少读取表的操作
PickLayout PredicatePushDown 谓词(过滤条件)下推,减少下层节点的数据量
ProjectionPushDown ProjectNode下推,减少Union节点的数据量
PruneUnreferencedOutputs 去除ProjectNode不在最终输出中的列,减小计算量
PruneRedundantProjections 去除多余的ProjectNode,如果上下节点全都直接映射,则去掉该层ProjectNode
SetFlatteningOptimizer 合并能够合并的Union语句
SimplifyExpressions 对执行计划中涉及到的表达式进行简化和优化
UnaliasSymbolReferences 去除执行计划中ProjectNode无意义的映射,如果直接相对二没有带表达式,则直接映射到上层节点
WindowFilterPushDown 窗口函数过滤条件下推

03.3.1 增加ExchangeNode的规则

如上图表格中AddExchanges的优化策略的描述,在聚合,窗口函数等情况下,涉及到数据计算的节点会增加ExchangeNode。举一个样例SQL:

SELECT c1.rank, COUNT(*) FROM dim.city c1 JOIN dim.city c2 ON c1.id = c2.id WHERE c1.id > 10 GROUP BY c1.rank LIMIT 10

它对应的逻辑执行计划如下:

03.4 逻辑执行计划划分SubPlan

整体流程: a、第一步:PlanFragmenter调用createSubPlans()来进行SubPlan的切割。依赖MetaData,NodePartitionManager和QueryManagerConfig对象; b、第二步:构造此查询的Fragmenter(切分器)和FragmentProperties。构造的时候需要依赖生成的逻辑执行计划Plan,查询Session和底层数据源的PartitionSchema和PartitionHandler; c、第三步:SimplePlanRewriter对执行计划Plan进行一次节点的访问(根节点往下遍历),当发现ExchangeNode则进行一次Fragment的切割(ExchangeNode保存了其下source的PlanNode节点集合,即根据ExchangeNode首次创建的FragmentID为 1 ,第一个ExchangeNode上还有一个Root Fragment),一旦访问到最后一个ExchangeNode,整个执行计划已经切割完成。同时自上而下切割时会按照栈的模式来存储每一个Fragment的上下文信息。完后遍历这个栈结构,即开始从上往下来进行PlanFragment和SubPlan的构造,构造出来的SubPlan存储到FragmentProperties中。在访问完ExchangeNode时,将ExchangeNode转化成RemoteSourceCode,PlanFragment会存储下游RemoteSourceNodes,以便后续根据这个信息生成RemoteSplit信息。一直到访问Root的OutPutNode节点,因为OutPutNode不是ExchangeNode,Root的SubPlan没有被创建。故在SimplePlanRewriter执行完成后,要执行一次buildRootFragment(); d、第四步:完成对Root SubPLan的PlanFragment的构造。 SubPlan:逻辑执行计划Plan(PlanNodes)会根据ExchangeNode将其拆分成多个SubPlan,一个SubPlan中包含多个节点PlanNode。 Fragment:将SQL语句拆解为一个一个的PlanNodes,比如join-node,exchange-node,projection-node,filter-node等等,每个SubPlan会包含其中若干个PlanNodes,这些PlanNodes封装到一个Fragment对象中。也就是说,SubPlan主要负责上下游依赖的其他SubPlan的关系,Fragment负责维护和管理本SubPlan中要执行的PlanNode。

03.4.1 RemoteSourceNode和ExchangeNode

逻辑执行计划中的PlanNode没有RemoteSourceNode这个类型,它是一个虚拟的PlanNode。这个PlanNode是在SimplePlanRewriter对执行计划进行visit时,当遇到ExchangeNode节点时才生成的。 用前面的例子来说:

一个SubPlan中绑定一个FlanFragment,每个FlanFragment中要存储直接下游所有的RemoteSourceNode信息。

03.4.2 Fragment对象

这个对象里有两个属性会在后续为SubPlan配置调度时频繁用到:PartitioningHandle和PartitionSchema(这两个属性都维护在MetaData中的TableMeta中)。每个PlanFrament中的PartitioningHandle中的部分属性会在切割执行计划visit每一个PlanNode中进行修改(connectorHandle属性)。PartitioningHandle的类型有source、fixed、single、coordinator_only等。具体每一种PlanNode的设置策略可以看PlanFragment中的各个visit函数。

03.4.3 PlanRoot

根据Analysis对象生成执行计划,并根据所提供的优化器对其进行优化。同时根据执行计划中的各个PlanNode,对执行计划进行划分,生成SubPLan,且每个SubPlan绑定一个PlanFragment(子执行计划的上下文)

上图中展示了经过执行计划优化后PlanRoot的组成: 1、PlanRoot包括经过PlanFragment划分后的一个SubPlan树(一个SubPlan称为一个Stage) 2、PlanRoot包括本次查询需要依赖的CataLogHandle SubPlan称为一个Stage,它包含其依赖的SubPlan集合。每一个SubPlan绑定一个PlanFragment,PlanFragment维护了一个SubPlan运行时所有依赖的信息。如上图所示PlanFragment是每一个SubPlan的核心。 PlanFragment包含的数据源的信息,Stage运行时描述信息是Stage调度和执行的重要组成,后续会重点描述每一个类的构造原理和具体取值的含义。

04 分布式执行计划(生成调度策略)

04.1 分布式可执行计划时序图

在PlanRoot构造完成后,开始进行分布式执行计划的生成,即将每一个SubPlan转换成可以执行的StageExecutionPlan,同时根据上下文信息构造Stage调度的SqlQueryScheduler来实现Stage的调度。下图展示了分布式执行计划和调度策略生成的时序图。

上述时序图中显示的类是分布式执行计划和调度的核心类,需要了解每一个类的成员和函数才能更好的了解一个Query是如何被划分成Stage,以及Stage是如何被调度的。

04.2 分布式可执行计划核心UML类图

04.3 SqlQueryExecution

Sql进行语法解析,语义分析,执行计划,调度的入口类

04.3.1 类成员变量

成员变量 功能描述
preparedQuery 描述一个查询Statement,以及参数表达式
stateMachine 管理一次完整Query的状态机
slug 描述查询当前所在的位置,是Queued还是Executing
metaData 维护集群元数据,如支持的数据类型,支持的函数,连接点数据源和表、列元数据等
accessControl 实现类是:AccessControlManager维护每个Catalog的访问控制权限
sqlParser 进行SQL语法解析的实现类,将statement按照语法树解析出各个关键字段,并进行语法校验
splitManager 维护Catalog表数据的分片管理,主要是按照表元数据将其进行分片
nodePartitionManager 维护Catalog表数据分布管理,即表的每个partitioning可以通过那些node节点进行读取,维护当前集群节点的调度策略:1、TopologyAwareNodeSelector,2、UniformNodeSelector
nodeScheduler 当前集群节点的调度策略:1、TopologyAwareNodeSelector,2、UniformNodeSelector
planOptimizers 所提供的逻辑执行计划优化器
planFragment 将一次完成的执行计划分成多个SubPlan的切割器。划分SubPlan的规则是:存在数据交换的PlanNode之间增加ExchangeNode,按照ExchangeNode进行划分
remoteTaskFactory 实现类是MemoryTrackingRemoteFactory,功能:将每一个stage转化成HttpRemoteTask对象发送到worker上进行执行
locationFactory 实现类是HttpLocationFactory,负责创建具体的URI地址。如:1、查询Query URI:/v1/query,2、查询Task URI:/v1/task,3、查询Memory URI:/v1/memory
scheduleSplitBatchSize 此参数是系统级配置参数,每个Query共享同一分配置,这个参数默认是1000。参数的含义:The size of single data chunk expressed in rows that will be processed as single split. Higher value may be used if system works in reliable environment and there the responsiveness is less important then average answer time. Decreasing this value may have a positive effect if there are lots of nodes in system and calculations are relatively heavy for each of rows. Other scenario may be if there are many nodes with poor stability - lowering this number will allow to react faster and for that reason the lost computation time will be potentially lower.
queryExecutor 此对象是用于构造SQLStageExecution的内部StageTaskListener,当TaskStatus发生变更时,用此线程来更新内存账本和Stage对应的Task进度信息。
schedulerExecutor 此对象在SqlQueryScheduler针对Stage的PartitioningHandle是scaled_writer_distribution时,进行ScaledWriterScheduler的构造。ScaledWriterScheduler类功能待整理。
failureDetector 实现类是HeartbeatFailureDectector,功能:每个5秒对当前集群的所有服务进行检测,去除失效的监控任务,禁止失效的节点服务,为新服务增加新的监控任务等。总体是及时发现集群服务变更,保持集群信息的正确性。
queryScheduler AtomicReference类提供了一个可以原子读写的对象引用变量。 原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。 AtomicReference甚至有一个先进的compareAndSet()方法,它可以将引用与预期值(引用)进行比较,如果它们相等,则在AtomicReference对象内设置一个新的引用。SqlQueryScheduler: 将SqlQueryExecution经过执行计划后生成的SubPlan转化成SqlStageExecution进行调度。
queryPlan 一个SqlQueryExecution在执行语法解析和语义校验后,经过逻辑计划器之后会生成Plan对象
nodeTaskMap 存储Worker上每个节点的Task负载,供SqlQueryScheduler在调度Split和Task时进行
executionPolicy 提供两种Stage调度策略:1、AllAtOnceExecutionPolicy(集群默认使用),2、PhasedExecutionPolicy
schedulerStats 此对象存储在Split调度时的统计信息
analysis 一个Statement在经过SqlParser后,再经过语义校验和元数据绑定后生成的对象。此对象是执行计划生成的输入
statsCalculator 实现类:ComposableStatsCalculator,统计右图中的各个PlanNode的统计信息
costCalculator 实现类:CostCalculatorUsingExchanges,预估每个节点的花费信息,TableScan会使用的CPU时间等。具体原理还没看。

04.3.2 类函数功能

函数名称 功能描述
SqlQueryExecution 构造函数
Analysis analyze() 语义校验和元数据绑定
DataSize getUserMemoryReservation()
DataSize getTotalMemoryReservation()
Duration getTotalCpuTime()
BasicQueryInfo getBasicQueryInfo()
void start()
void addStateChangeListener(StateChangeListener`<`QueryState`>` stateChangeListener)
PlanRoot planQuery()
PlanRoot doPlanQuery()
static Multimap`<`CatalogName, ConnectorTableHandle`>` extractTableHandles(Analysis analysis)
void planDistribution(PlanRoot plan)
void closeSplitSources(StageExecutionPlan plan)
void cancelQuery()
void cancelStage(StageId stageId)
void fail(Throwable cause)
QueryInfo getQueryInfo()

04.4 SubPlan

经过执行计划后,按照ExchangeNode进行拆分的Stage的描述类,即SubPlan。一个Query对应一个SubPlan树。

成员 类型 功能描述
fragment Fragment SubPlan包含的PlanNode,依赖的元数据,后续要进行执行和调度的上下文
children List`<`SubPLan`>` SubPlan依赖的叶子SubPlan

04.5 StageExecutionPlan

04.5.1 类成员变量

成员 类型 功能描述
fragment Fragment SubPlan包含的PlanNode,依赖的元数据,后续要进行执行和调度的上下文
splitSources Map`<`PlanNodeId, SplitSource`>` 描述每一个PlanNode的输入SplitSource。SplitSource是一个逻辑概念,描述从哪个表读取哪个ConnectorSplitSource。Presto抽象了一个SplitBatch的概念,即通过系统参数将一定大小的Split合成一个SplitBatch来进行调度。
subStages List`<`StageEcecutionPlan`>` StageExecutionPlan下依赖的一组叶子StageExecutionPlan
fieldNames Optional`<`List`<`String`>``>` 每个StageExecution中的RootPlanNode的输出FieldName信息
tables Map`<`PlanNodeId, TableInfo`>` 存储PlanNode对应的Table信息

04.6 SqlQueryScheduler

04.6.1 类成员变量

成员 类型 功能描述
queryStateMachine QueryStateMachine 查询请求发送到Coordinator后,会给每个查询构造一个sm,来管理其的生命周期。维护一个查询的静态信息和查询中的状态和进度信息
executionPolicy ExecutionPolicy 提供两种Stage调度策略:1、AllAtOnceExecutionPolicy(集群默认使用),2、PhasedExecutionPolicy
stages Map`<`StageId, SqlStageExecution`>` 描述当前一个Query分了几个Stage,每个stage对应的SqlStageExecution
rootStageId StageId 每个Query的Root Stage
stageSchedulers Map`<`StageId, StageScheduler`>` 每个Stage对应的调度策略
stageLinkages Map`<`StageId, StageLinkage`>` Stage之间的调度链条
schedulerStats SplitSchedulerStats 此对象存储在Split调度时的统计信息
summarizeTaskInfo boolean 查询语句如果是(Explain且isAnalyze),则summarizeTaskInfo设置成false。???为什么这么设置???,这个参数只会在后续的TaskInfoFetcher中使用,如果是True,则URI中增加一个summarize的参数
started AtomicBoolean 可以进行开始调度的标示

04.6.2 类函数功能

函数名称 功能描述
static SqlQueryScheduler createSqlQueryScheduler 对外提供的创建对象的入口,创建完成对象后,进行调度的初始化工作:1、调用SqlQueryScheduler构造函数,2、调用initialize函数
SqlQueryScheduler 1、构造局部变量stageSchedulers和stageLinkages、partitioningCache,
2、得到rootBuffer的OutputBufferId,
3、调用createStages,将StageExecutionPlan转换成List`<`SqlStageExecution`>`,
4、root SqlStageExecution绑定OutputBufferId,
5、得到SqlStageExecution列表中的所有stageId,
6、经过createStages步骤,stageSchedulers,stageLinkages已经存储了每个Stage的调度策略和调度链。
initialize 1、给rootStage增加状态监听器,一旦最后一个Stage完成,则Query对应的statemachine会转移到finishing;如果cancel,则statemachine会转移到cancel,
2、遍历每一个stage,增加状态监听器,如果存在任何一个stage的状态是fail,abort则query转移到failed。如果query的状态是starting,stage已经开始构造Task执行,则query转移到running,
3、给QueryStateMachine增加一个状态变更监听器,只要当任何一个Stage完成,都试图去更新query的状态,判断是不是最后一个query完成,
4、给每一个SqlStageExecution增加一个状态监听器,一旦Stage的状态发生变声,去更新queryStateMachine的状态信息
updateQueryOutputLocations 构造获取查询结果的每一个Task的URI地址
createStages 1、首先给每个StageExecutionPlan生成一个StageId,自增
2、将StageExecutionPlan生成SqlStageExecution
3、根据StageExecutionPlan中PlanFragment的PartitioningHandle属性给每个SqlStageExecution设置StageScheduler策略,调度策略会独立一个章节讨论
4、获取StageExecutionPlan下的children,递归调用createStages来构造SqlStageExecution
5、SqlStageExecution中绑定了StageTaskListner监听器,createStages中一旦发现SqlStageExecution对应的执行状态是Done(Terminal State),即会遍历其的children,将子任务都取消
6、为当前StageID构造一个StageLinkage,放入stageLinkages。
7、对partitioningHandle是SCALED_WRITER_DISTRIBUTION的情况特殊处理
(1)构造ScaledWriterScheduler,放入到stageSchedulers中,但是却没有增加对应的调度链条
(2)调用wenAllStages,当所有的自任务完成后,直接执行(coordinator上执行,不需要下发到worker)针对的应该是create as select中的最后一个步骤writer
getBasicStageStats 遍历每一个SqlStageExecution,获取其的StageStats(运行时统计信息),然后汇总。汇总的统计信息如右图
getStageInfo 获取StageInfo
getUserMemoryReservation 汇总每个SqlStageExecution的内存信息(user类型)
getTotalMemoryReservation 汇总每个SqlStageExecution的内存信息(user+system类型)
getTotalCpuTime 汇总每个SqlStageExecution的cpu时常信息
start 使用线程调用SqlQueryScheduler的schedule函数
schedule(详细逻辑在第六章节) 1、根据系统配置,获取到ExcutionPolicy中的调度策略executionSchedule(默认AllAtOnce),
2、如果executionSchedule没有finish,while一直循环执行下述逻辑,
3、遍历每一个SqlStageExecution,获取其对应的StageScheduler(三种调度策略),Stage进入调度状态(Stage对应StageStatemachine,StageStatemachine进入调度状态)
4、执行Stage对应StageScheduler的scheduler,返回调度结果ScheduleResult
cancelStage 按照StageId取消stage,调用SqlStage的cancel函数
abort 遍历SqlStageExecution,一次执行abort
whenAllStages 针对stages进行监听,监听stages是否已经完成

04.7 SqlStageExecution

04.7.1 类成员变量

04.8 StageScheduler

Presto中针对Stage提供了四种类型的StageScheduler,其中对于Source类型的Stage还需要绑定SourceScheduler和SplitPlacementPolicy。Source类型的Stage根据数据源Connector的特性调度策略又分为两种:
1、FixedSourcePartitionedScheduler
(1)、BucketedSplitPlacementPolicy
(2)、AsGroupedSourceScheduler
2、SourcePartitionedScheduler.newSourcePartitionedSchedulerAsStageScheduler
(1)、DynamicSplitPlacementPolicy
(2)、SourcePartitionedScheduler

04.8.1 Stage和StageScheduler的映射规则

需要弄清楚PlanFragment中的各个字段的含义,才能清楚的了解映射规则的设计。

SqlExecutionPlan计划的PartitioningHandle和关联的SplitSources信息决定了其的调度策略。Stage是SqlExecutionPlan一次调度执行的描述,后续将使用stage来描述生成调度和执行调度过程。Stage分为四种类型(从PartitioningHandle属性继承而来)在进行执行计划拆分时,会根据ExchangeNode的类型来给SubPlan中的PlanFrament的PartitioningHandle设置类型。所以Stage一定包含一个PartitioningHandle,PartitioningHandle的类型可以表述为Stage的类型。

类型 描述 备注
Source 一般是TableScanNode、ProjectNode、FilterNode,一般是最下游的取数的Stage 如果splitSource对应的表是unbucket,则为distribution_source。
Fixed 一般在Source之后,将Source阶段获取的数据分散到多个节点上处理,类似于Map端reduce操作,包括局部聚合、局部Join、局部数据写入
Single 一般在Fixed之后,只在一台机器上进行,汇总所有的结果、做最终聚合、全局排序,并将结果传输给Coordinator
Coordinator_only 只在Coordinator上
底层数据表的格式(是否bucket表)会影响直接计划的拆分(如join),即增加ExchangeNode的策略(ExchangeNode同时会维护一个字段Type来描述ExchangeNode下节点的功能)。当根据ExchangeNode将执行计划拆分成分布式执行计划后,会根据ExchangeNode的Type来生成不同的PartitionHandle类型。PartitionHandle的类型和Stage中的SplitSource信息共同决定了配置的调度策略。

StageScheduler调度策略和策略分配规则如下:

1、SourcePartitionedScheduler:凡是包含TableScanNode的执行计划都需要使用SourcePartitionedScheduler来完成SplitSource中每个split到执行节点的分配(assignSplits过程)。如果表是unbucketed,在生成执行计划时会把TableScanNode拆分成一个独立的stage(至多加上FilterNode),且type是source_distribution;为其分配的调度策略是SourcePartitionedScheduler,绑定的split分配node的策略是DynamicSplitPlacementPolicy
2、FixedSourcePartitionedScheduler:包含TableScanNode的执行计划(即splitSource至少有一个是source split,可以包含remote split且source split对应的table是bucket表)。这种stage会配置成FixedSourcePartitionedScheduler调度,但是底层核心split到node的分配操作仍然是底层调用SourcePartitionedScheduler来实现。这个时候为split分配node的策略是BucketedSplitPlacementPolicy
3、FixedCountScheduler:非source类型,全部都是remote split,选择节点直接进行(构建task:scheduleTask)

SourcePartitionedScheduler是处理读取数据stage(source)的调度器。有一个额外的操作即对数据Split进行调度,给需要处理的split分配执行节点。在presto中,stage中task和split的调度是解耦的。为Split分配Node的策略有两种:

1、BucketedSplitPlacementPolicy(底层数据表是bucketed表),在生成执行时会使用NodePartitionManager生成split和node的映射关系,在具体调度时使用映射管理来为split分配node
2、DynamicSplitPlacementPolicy: (底层数据表是unbucketed表)在具体调度执行前为split分配node

source stage对应的tableScan 调度策略 Split分配Node策略
bucket FixedSourcePartitionedScheduler BucketedSplitPlacementPolicy
unbucket SourcePartitionedScheduler DynamicSplitPlacementPolicy

源Stage-unBucket Split

静态策略 计算Split下发的ndoe 调度具体过程
调度器:SourcePartitionedScheduler
split下发策略:DynamicSplitPlacementPolicy
Stage中是否已经计算出Node列表:否,无法计算
入口:DynamicSplitPlacementPolicy
计算方法:
1、根据当前集群拓扑节点来、Split对应的ConnectorSplit中的Host信息得出候选节点集合
2、根据负载等给Split分配Node
3、更新全部拓扑节点中分配Split的数据信息
1、SourcePartitionedScheduler根据DynamicSplitPlacementPolicy获取split到node的静态映射
2、SourcePartitionedScheduler调用assignSplit来开始分配split到node,可能会block
3、为split创建Task。sqlStageExecution来执行schedulerSplit和schedulerTask。更新allTasks等变量
4、创建RemoteTask成功后,StageLinkage获取信息,因为此stage没有RemoteSplit,无额外操作

源Stage-Bucket Split or remoteSplit

静态策略 计算Split下发的ndoe 调度具体过程
入口调度策略:FixSourcePartitionedScheduler
Source调度策略:SourcePartitionedScheduler
Source split下发策略:BucketedSplitPlacementPolicy
Stage中是否已经计算出Node列表:已经计算出NodePartitionMap
入口:BucketedSplitPlacementPolicy
计算方法:根据Split和NodePartitionMap中的bucketToNode信息来为Split来分配Node
1、FixSourcePartitionedScheduler+BucketedSplitPlacementPolicy+NodePartitionMap来构造SourcePartitionedScheduler
2、后续操作类似SourcePartitionedScheduler
3、StageLinkage获取信息,判断当前stage有没有RemoteSplit
(1)、有,根据remoteSourceNode信息来构造RemoteSplit,更新RemoteTask对应的Split信息(增加)调用http接口,更新remoteTask的信息
(2)、无。没有额外操作

非源Stage-all remoteSplit

静态策略 调度具体过程
入口调度策略:FixCountScheduler
Source split下发策略:无,全部是RemoteSplit
Stage中是否已经计算出Node列表:已经计算出NodePartitionMap
1、NodePartitionMap中的partitionToNode描述了Task要被调度的节点信息。
2、节点计算:依赖stage中(single、hash)的个数和集群节点的个数。通过random来获取task和node的映射
3、schedulerTask后,StageLinkage获取信息,因为此Stage的所有split都是remote。所以要根据RemoteSourceNode信息来创建RemoteSplit信息、更新RemoteTask对象

04.9 StageLinkage

04.9.1 StageLinkage的作用

每个StageLinkage包含对应Stage的PlanFragmentId,父Stage需要的ExchangeLocationConsumer,子Stage列表Set<StageId>和每一个子Stage结果的OutputBufferManager。
在进行Stage调度时会调用StageLinkage的processScheduleResults函数。

04.9.2

PartitioningHandle OutputBufferManager 备注
FIXED_BROADCAST_DISTRIBUTION BroadcastOutputBufferManager Stage中ExchanNode的类型有关:Replicate
SCALED_WRITER_DISTRIBUTION ScaledOutputBufferManager
其他 PartitionedOutputBufferManager Partition

04.10 DistributedExecutionPlanner

04.10.1 DistributedExecutionPlanner的作用