作者 | 士心先生
来源 | 程序员的读书故事 (公众号:pg_reading)
查询执行模型
Presto通常部署为包含一个协调器和多个工作节点的集群。
在协调器内部,SQL语句首先以文本形式提交到协调器,协调器解析和分析这条语句,之后创建一个由Presto内部数据结构表示的执行计划,叫做查询计划。
查询计划生成过程利用了元数据SPI和数据统计SPI来创建查询计划。也就是说,协调器会使用SPI直接连接到数据源,以收集有关表和其他元数据的信息。
协调器通过元数据SPI获取表、列和数据类型的信息。这些信息用于对查询进行语义校验、类型检查和安全检查。
统计SPI用于获取行数和表大小的信息,从而在计划期间进行基于代价的查询优化。
在创建分布式查询计划时会利用数据位置SPI来生成表内容的逻辑切片。切片是任务分配和并行的最小单位。
分布式查询计划是简单查询计划的一个扩展,它包含一个或多个stage。简单查询计划被切分为多个计划片段。Stage是在运行时的计划片段,它包含对应计划片段所描述的所有任务。
协调器将查询计划切分成Stage,从而分配给集群中的多个工作节点进行并行处理,从而加快整体查询的执行速度。多个Stage会被组织成一棵依赖树。Stage的数量依赖于查询的复杂度。例如,查询的表、返回的列、JOIN语句、Where条件、Group by操作和其他SQL语句都可能影响Stage的数量。
分布式执行计划定义了Stage和查询在Presto集群上执行的方式。协调器使用它在工作节点上进一步计划和调度任务。一个Stage通常包含一个或多个任务,每个任务则负责处理一小部分数据。
一个任务处理数据的单位是切片。切片代表一个工作节点可以抽取并处理的一段底层数据,它是并行和任务分配的单位。
源Stage的任务以page的形式生产数据,每个page都是以列式存储格式表示的一系列行。这些page传输到下游的中间Stage。Exchange算子从上游Stage中读取数据,从而在不同Stage之间传输page。
在连接器的帮助下,源任务使用数据源SPI从底层数据源获取数据。这些数据以
page的形式在Presto的查询引擎之中传送。算子根据它们的语义处理接收到的page并产生新page。
包含在一个任务里的一串算子叫做流水线。流水线中的最后一个算子通常会将它输出的page放置在任务的输出缓冲区中。下游任务的Exchange算子会从上游任务的输出缓冲区中消费page。所有这些操作都在不同的工作节点上并行运行。
因此,任务是运行时分配给一个工作节点的计划片段。在任务创建之后,它会为每个切片初始化一个驱动。每个驱动都是包含多个算子的流水线的一个实例,并且负责处理切片中的数据。根据Presto配置和环境,一个任务可以使用一个或多个驱动。当所有驱动都执行完且数据被传送到下一个切片时,驱动和任务的工作就结束了,它们之后会被销毁。
算子处理输入数据并为下游算子生产输出数据。常见的算子包括TableScan(表扫描)、Filter(过滤)、Join和Aggregate(聚合)。一系列相连的算子组成一套算子流水线。例如,你可以拥有一条流水线,它先扫描并读入数据,再过滤数据,最后在数据上执行局部聚合。
要处理一条查询,协调器首先根据来自连接器的元数据创建切片列标。使用该切片列表,协调器开始在工作节点上调度任务,以获取其中的数据。在查询执行期间,协调器跟踪所有可用于处理的切片和任务在工作节点上执行的位置。一些任务完成了处理,并产生了很多供下游处理的切片,协调器就会继续调度更多的任务来处理它们,直到没有待处理的切片为止。
一旦工作节点处理完了所有切片,全部数据就可用了。此时协调器会将结果返回给客户端。