Trino简介
Trono文档地址https://trino.io/docs/current/
Trino
一、简介
-
Trino是通过分布式查询,高效处理大量数据的工具。要处理TB或PB级别的数据,一般是使用能够与Hadoop和HDFS进行交互的工具。Trino的设计目标就是取代这些工具,如Hive或Pig,
-
但是除了访问HDFS。Trino也可以操作其他数据源,包括传统关系数据库和其他数据源,如Cassandra。
-
Trino旨在处理数据仓库和分析工作:数据分析、聚合大量数据并生成报告。这些工作负载通常被归类为在线分析处理(OLAP)。
二、基础概念
2.1 架构
- Trino是一个分布式查询引擎,在多个服务器上并行处理数据。
- Trino由两种server类型:
Coordinator
和Worker
。
Cluster
- Trino集群由一个
Coordinator
和多个Worker
组成 - 用户发送SQL到
Coordinator
,coordinator
与Worker
协同工作。访问已连接的数据源,数据源在catalogs
中配置 Coordinator
负责解析SQL,分配工作到Worker
,然后并行查询
Coordinator
- 每个集群都必须有一个
Coordinator
,可以由Worker
兼任 - 负责解析语句、制定查询计划和管理
Worker
- 跟踪每个
Worker
,协调查询的执行。 - 创建查询的逻辑模型,分解为一系列
Stage
,然后将其转换为在一组Worker
上运行的一系列连接Task
。 - 使用REST API与
Worker
和client
进行通信。
Worker
- 负责执行
Task
和处理数据 - 从连接器中获取数据,并与其他
Worker
交换中间数据。Coordinator
从Worker
获取结果,并将结果返回给客户端。 - 使用REST API与其他
Worker
和Coordinator
进行通信。 - 启动时,会主动向
Coordinator
注册自己。
2.2 Data sources
Connector
-
用于连接数据源,支持各种数据源:传统数据库,NoSQL,Hive等。
-
每个
Catalog
都与一个Connector
关联。查看Catalog
配置文件,会发现每个文件都包含一个必填属性connector.name
,用于指定Connector
。 -
可以同时连接多个数据源。例如,有两个Hive集群,就可以配置两个
catalog
,都使用Hive Connector
,这样就可以在一个SQL查询中同时查询两个Hive集群的数据。
Catalog(目录)
-
一个
Catalog
,包含Schemas
和通过Connector
连接的数据源 -
当引用表时,表名始终以
Catalog
为根。例如,hive.test_data.test
的指的是hive Catalog
中test_data schema
中的test table
。 -
Catalog
存储在Trino配置目录中,通过properties
文件定义。
Schema(模式)
- Schema是用于组织表的一种概念。定义了表的命名空间,帮助对表进行分类和组织。一个Schema可以包含多个表,并在逻辑上将这些表进行分组。
- 不同的
Connector
可以以不同的方式组织表和模式。- 对于关系型数据库连接器,Schema通常对应于数据库中的Schema或命名空间,帮助将表进行逻辑上的划分和组织。
- 对于Hive连接器,Schema可以对应于Hive数据库中的一个数据库或一个文件夹,用于将表组织在一起。
- 通过使用Schema,可以更好地组织和管理Trino中的表,提供更清晰的命名空间和上下文。有助于在查询中指定表的完全限定名称,并提供更好的可读性和维护性。
Table
- 表示实际的数据表或数据集,是存储和组织实际数据的实体。
- 每个表都属于一个Schema,每个Schema又属于一个Catalog。
- 表定义了数据的结构(列名、数据类型等)以及存储在其中的实际数据。
- 通过在Trino中查询表,可以对数据进行检索、过滤、转换和分析等操作。
2.3查询执行模型
Trino将SQL语句转换为查询,然后在集群上执行
Statement(语句)
Query(查询)
- Query是用户提交给Trino执行的查询。
- 包含了从数据源中检索、转换、过滤和分析数据的指令。
- Trino接收查询并将其分解为一系列的阶段
(Stage)
来执行。
Stage
-
Stage
是查询执行计划中的一个单元,表示查询的一个并行处理阶段。 -
每个
Stage
包含一个或多个任务(Tasks)
,用于并行处理数据。Stage
可以并行的,也可以串行,取决于查询执行计划。 -
组成查询的
Stage
层次结构类似于一棵树。每个查询都有一个根Stage
,负责聚合其他Stage
的输出。 -
Stage是一个逻辑概念,包含了多个任务(Tasks),因此
Stage
本身是不运行的。
Task
- Task是在Trino中执行的基本工作单元。
- 每个
Task
负责处理一部分的数据或计算,并生成部分结果。 Task
在不同的工作节点上并行执行,以加速查询处理。- 一个
Stage
可以由多个Task
组成,每个Task
处理不同的数据分片(Splits)。
Split
- Split是数据的逻辑切片或分块,可以根据数据的位置、大小或其他标准进行划分。
- 每个Split代表了查询要处理的数据的一个子集。
Task
通过处理不同的Split来并行执行查询操作。
Driver
当用户提交一个查询给Trino时,Driver首先接收到这个查询,并负责解析查询语句、生成查询执行计划和执行该计划。Driver会将查询拆分成一系列的阶段(Stages)和任务(Tasks),并将任务分发到不同的工作节点上并行执行。
Driver
是Trino执行计划的主要组件之一- 一个
Task
包含一个或多个并行的Driver
。 Driver
对数据进行操作,组合Operator
以生成输出,然后由Task
进行聚合,之后交付给另一个Stage
的另一个Task
。Driver
是一系列操作实例,是Trino中最低级别的可并行组件。Driver
有一个输入和一个输出。- Driver负责监控和调度各个任务的执行,收集和合并任务的结果,并最终将最终结果返回给客户端。它还负责处理查询中的错误和异常情况,并提供查询的元数据信息。
Operator
- Trino查询计划中的执行单元,用于实现不同的操作和转换。
- 扫描表、过滤数据、连接数据等都是通过
Operator
来完成的。 - 操作符接收输入数据,经过计算和转换后产生输出数据。
Exchange
Exchange用于在不同任务之间进行数据传输和交换。当需要与其他任务交换数据时,就插入Exchange到执行计划中,用于发送和接收数据。
三、查询优化
3.1 基于查询成本的优化
Join策略枚举
-
在查询中,Join的执行顺序对性能有着重要的影响,修改Join顺序,优化查询,就能减少所需的时间和资源。
-
Join中对性能影响最大的因素是处理和传输数据的大小,如果在早期执行了会产生大量数据的Join,那么随后的阶段就需要处理大量数据,增加了所需的时间和资源。
-
Trino使用连接器提供的表统计信息来估算不同Join顺序的成本,并自动选择最低成本的Join顺序。
-
Join枚举策略由
join_reordering_strategy
属性控制- 取值有:
- AUTOMATIC(默认值): 启用完全自动的Join枚举
- ELIMINATE_CROSS_JOINS: 消除不必要的交叉Join
- NONE: 查询语句指定的Join顺序
- 取值有:
-
默认使用
AUTOMATIC
策略,但是如果不能从连接器获得任何可用的统计信息,或者由于一些原因,无法计算成本,则会切换到ELIMINATE_CROSS_JOINS
策略。
Join distribution selection
-
Trino使用基于哈希的Join算法。
-
对于每个Join操作,对连接输入方(称为构建方)构建一个哈希表。然后对另一个输入方(称为探测方)的数据进行枚举。对于探测方的每一行数据,都会查询构建方的哈希表以找到匹配的行。
-
join distribution
有两种类型:-
分区
Partitioned
:参与查询的每个节点只对数据的一部分构建哈希表。 -
广播
Broadcast
:参与查询的每个节点都对所有数据构建哈希表。数据会被复制到每个节点上。
-
-
两种类型各有优缺点。
分区Join
需要使用Join的Key的哈希值分配两个表的数据到不同分区的节点。因此分区Join可能比广播Join慢很多,但它们允许更大数据量的Join。- 如果构建方远小于探测方,
广播Join
速度更快。 广播Join
会将构建方的表复制到每个节点,需要每个节点都有足够的内存来容纳数据。分区Join
,表的数据分布在多个节点上,每个节点只存储部分数据。因此,只需构建侧的表小于整个集群内存的总和即可。
-
通过基于成本的
join distribution selection
,Trino自动选择使用分区
或广播Join
。通过基于成本的Join策略枚举,Trino自动选择构建方和探测方。 -
Join distribution
策略由join_distribution_type
属性控制- 可选值包括:
AUTOMATIC(默认值):
策略由系统自动确定BROADCAST:
所有连接使用广播连接PARTITIONED:
所有连接使用分区连接
- 可选值包括:
限制复制表大小
"复制表"指的是在广播连接(Broadcast Join)中将数据复制到每个参与连接的节点上的表。
广播连接中,通常将小表的数据复制到所有节点上,以确保每个节点都具有完整的数据副本。这样可以避免在连接过程中进行网络通信和数据传输,提高查询的执行速度。因此,复制表是指被复制到每个节点上的表,使得每个节点都能够独立地执行连接操作,而无需通过网络传输数据。
当join reordering strategy
或join distribution type
设置为AUTOMATIC
时,会自动选择join distribution type
。在这两种情况下,可以通过join-max-broadcast-table-size
或join_max_broadcast_table_size
属性来限制复制表的最大值。提高集群并发性,并防止成本优化器错误估计连接表大小时出现错误计划。
默认情况下,复制表的大小限制为100MB。
Syntactic join order
如果不使用优化,Trino默认将使用语法的Join顺序。虽然没有正式的方法来优化此类查询,但可以利用Trino实现Join
的方式来提高性能。
Trino使用基于内存的哈希Join。在处理Join语句时,Trino将Join
最右侧的表加载到内存中作为构建侧,然后将第二右的表作为探测方进行Join
操作。
若有多个Join
,则第一个Join
的结果将保留在内存中作为构建侧,然后第三右的表作为探测方,之后以此类推。当连接顺序变得更复杂,例如使用括号指定连接的父级时,Trino可能会同时执行多个较低级别的Join
,但每个步骤的逻辑都是相同的,最终将结果Join
在一起时也是如此。
基于上述分析,SQL最佳的写法是,大表到小表从左到右依次Join
,即大表放左边,小表放右边,以减少内存使用量。
SELECT
*
FROM
large_table l
LEFT JOIN medium_table m ON l.user_id = m.user_id
LEFT JOIN small_table s ON s.user_id = l.user_id
这种优化方式并不是Trino提供的特性,而是基于Join操作的底层实现方式,所产生的优化方法,因此这种行为在之后的版本可能会发生变化。
连接器实现
为了让Trino优化器使用基于成本的策略,连接器必须能提供表统计信息。
3.2 Pushdown
"Pushdown"是一种优化技术,指的是将数据处理操作(如过滤、聚合、排序等)推送到数据存储或计算引擎中进行处理,而不是在应用程序或上层框架中进行。
比如在从数据库取数据的阶段就执行部分where条件进行过滤,减少数据量。
条件下推
Predicate pushdown
将过滤操作推送到数据存储引擎中,在数据检索阶段进行过滤,减少需要传输和处理的数据量。例如,在关系型数据库中,将WHERE条件下推到数据库引擎中进行过滤。
投影下推
Projection Pushdown
投影操作用于选择查询结果中所需的列(字段),过滤掉不需要的列,以减少传输和处理的数据量。而投影下推技术将这个投影操作下推到数据源层面,使数据源能够在查询执行过程中只返回所需的列,而不返回所有列,减少需要传输和处理的列数。减少网络开销和计算资源的消耗。
如果投影下推成功,查询的EXPLAIN计划仅访问表扫描操作中相关的列。
解引用下推
Dereference pushdown
用于减少数据的传输和处理开销。
在数据查询过程中,有时需要对复杂的数据结构进行解引用,即从一个复杂对象中提取特定的字段或属性。这种解引用操作可能涉及多个数据项和嵌套的结构,如果在上层应用程序或查询处理层中进行解引用,可能需要将整个数据结构加载到内存中,再进行解析和处理,增加了计算和传输的开销。
例如,考虑在Hive连接器中有一个包含多个字段的ROW类型列的表格。如果查询只访问一个字段,解引用下推允许文件读取器仅读取该行中的单个字段。对于嵌套在顶层行中的行的字段,也是同样的情况。这可以在从存储系统读取的数据量上实现显著的节省。
聚合下推
Aggregation Pushdown
将聚合操作(如SUM、COUNT、AVG等)推送到数据存储引擎中进行计算,减少数据传输和处理的工作量。
只有在满足以下条件的情况下,聚合下推才能进行:
-
如果连接器通常支持聚合下推。
-
如果连接器支持特定函数或多个函数的下推。
-
如果查询结构允许进行下推。
可以通过EXPLAIN来检查是否对特定查询进行了下推。如果聚合函数成功下推到连接器,则EXPLAIN将不显示聚合运算符。解释计划仅显示Trino执行的操作。
Join下推
Join pushdown
连接下推允许连接器将Join操作委托给底层数据源。这可能会带来性能提升,并使Trino可以在更少的数据上进行处理。
对于支持连接下推的具体规则,每个数据源和连接器可能会有所不同。
然而,有一些通用条件必须满足才能进行连接下推:
-
所有作为
Join
的谓词必须可以下推执行 -
连接的表必须来自同一个
catalog
可以通过查看Explain
来验证是否进行了下推。如果Join被连接器下推到数据源,则Explain
不会显示Join运算符:
Limit pushdown
在查询过程中,Limit
用于限制查询结果集的大小,通常是指返回前N条记录或跳过前N条记录。而限制下推技术将Limit
下推到数据源层面,使数据源能够在查询执行过程中仅返回满足限制条件的记录,而不返回整个结果集。
Top-N pushdown
LIMIT或FETCH FIRST与ORDER BY组合,在大型有序数据集中创建了一个要返回的小集合。依赖于顺序来确定需要返回哪些记录。
这种查询的pushdown被称为Top-N pushdown,该操作返回前N行。使连接器能够将此类查询的处理推送到底层数据源,从而显著减少传输到Trino并处理的数据量。
比如
SELECT id, name
FROM postgresql.public.company
ORDER BY id
LIMIT 5;
如果Connector支持,那就会在查询阶段进行下推
// TableScan进行了下推
Fragment 1 [SOURCE]
Output layout: [id, name]
Output partitioning: SINGLE []
Stage Execution Strategy: UNGROUPED_EXECUTION
TableScan[postgresql:public.company public.company sortOrder=[id:integer:int4 ASC NULLS LAST] limit=5, grouped = false]
Layout: [id:integer, name:varchar]
Estimates: {rows: ? (?), cpu: ?, memory: 0B, network: 0B}
name := name:varchar:text
id := id:integer:int4
如果不支持,就必须由Trino进行Top-N查询
// TopNPartial,没有下推
Fragment 1 [SOURCE]
Output layout: [custkey, name]
Output partitioning: SINGLE []
Stage Execution Strategy: UNGROUPED_EXECUTION
TopNPartial[5 by (custkey ASC NULLS LAST)]
│ Layout: [custkey:bigint, name:varchar(25)]
└─ TableScan[tpch:customer:sf1.0, grouped = false]
Layout: [custkey:bigint, name:varchar(25)]
Estimates: {rows: 150000 (4.58MB), cpu: 4.58M, memory: 0B, network: 0B}
custkey := tpch:custkey
name := tpch:name