一、背景
必须前置知识:《Trino权威指南》第12章及周边涉及知识,基于392版本的使用层面入门书,引擎创始人亲自编写:
或者中文翻译版《Presto实战》第12章及周边,基于330版本。
可选知识:
(1)编译原理基础(SQL语法解析token、生成AST抽象语法树、将AST生成执行计划并优化覆盖的过程,如《ANTLR4权威指南》)、《数据库系统内幕》等计算机专业通用底层课程;
(2)《Presto技术内幕》等深入底层原理的Trino源码级书籍或博客。
(3)《大数据技术体系详解:原理、架构与实践》等大数据平台全貌入门书。
二、查看SQL性能瓶颈的方法
执行如下SQL:
explain analyze 要分析的SQL;
完成之后会生成详细的执行计划,执行计划本身是从底部往上倒着看的,可以全部复制下来到sublime text等文本编辑器后,ctrl+f搜索百分号“%”,来查看具体是什么操作的cpu time、schedule time、block time等百分比占据了整个查询的大部分。例如如下的执行计划,可以看到大部分的开销百分比在于kudu表扫描的调度上,有可能会成为查询任务的瓶颈之一:

同时如果能找到当时问题查询的query id,则可以在Trino原生UI中搜索对应的查询历史记录,注意有的query id搜不到可能是过期或者coordinator重启过了,因为目前社区版的查询历史记录都存在coordinator内存中,过段时间会被刷掉:

Presto在业界的主流查询时长分布:

三、常见问题判断与解决方法
具体报错有成百上千种之多,但是其中有一部分是较常见、甚至google报错内容就能搜到解决方法的,下面阐述部分比较常见的问题迹象,和相应的解决方案,将随时间更新。
3.1、数据倾斜导致查询慢
在Trino Query的执行过程中,会遇到时间很长的情况,其中一个可能性就是数据倾斜,判定数据倾斜的标准之一是下图进入具体查询id链接的Overview界面中,展开每个stage的节点信息,可以看到stage26中,某个节点的处理数据量要比其他节点大好几倍,其他节点执行的快也要最终等它执行完才能结束:


数据倾斜往往出现于join或者group by操作(distinct在开启mark distinct特性时会优化成group by那样的预聚合)的位置,正如上面结合Query Overview和Live Plan两个界面的提示(ScanFilterProject的算子之后进行预聚合operator),这样观察SQL之后就能发现数据倾斜语句的位置,并根据具体业务场景进行改写。
另一种判断数据倾斜的方式是通过执行explain analyze sql,在CLI客户端中展示执行计划,如下图中的输入数据量标准差,如果超过3倍以上则需要关注一下倾斜:

除了引擎自动进行优化的hash build join(类似Spark的broadcast join)等技术外,SQL层面可以尝试打散会被join on的字段值:
--打散数据(倾斜字段未知)
select id, value, concat(id, (rand() * 10000) % 3) as new_id from A
select id, value, concat(id, suffix) as new_id
from (
select id, value, suffix
from B CROSS JOIN UNNEST(array(0, 1, 2)) tmp as suffix(array(0, 1, 2))
)
--打散数据(倾斜字段已知)
select t1.id, t1.id_rand, t2.name
from (
select id ,
case when id = null then concat(‘SkewData_’, cast(rand() as string))
else id end as id_rand
from test1
where statis_date = ‘20200703’) t1
left join test2 t2
on t1.id_rand = t2.id
可以通过如下SQL判断某个join on或group by字段是否存在倾斜:
select id, count(id) as cnt from table1
where xxxx
group by id
having cnt > 3
3.2、混布架构抢不到CPU导致查询慢
Trino作为全内存计算引擎,对CPU与内存的需求量很大,如果和其他引擎混布在同一个节点上可能会互相影响。它在对多个候选执行计划进行CBO比较哪个更优时,也是对CPU开销的权重(比较开销时的重视程度)占到了75%,内存占10%(因为它量大相对CPU便宜),网络占15%。
如果是混布架构下出现如下迹象,则Trino可能有点抢不到CPU,即查询Overview界面里,某些stage的调度时间远高于CPU时间:

如果频繁出现这种现象,则可以看具体情况是否切换到存算分离架构,将trino worker等独立出来,避免和其他高耗能组件竞争物理机资源。
3.3、Remote Page is too large
原因是各stage之间进行shuffle exchange时,传输的数据过大超过了默认16M的限制,可能是因为page数据中包含复杂嵌套列,通过Jackson序列化成JSON传输出去的时候容量很大,可以参考如下issue增大config.properties中的参数限制:
3.4、小文件过多导致查询慢
可以用hadoop fs -ls命令查看一下对应目录下的文件情况,一般HDFS处理32MB以内大小的文件会比较慢,因为它为了海量数据高吞吐性能而牺牲了小文件的读取性能。对于hive表来说默认HDFS路径往往是:/user/hive/warehouse/库名.db/表名/分区名1=值/分区名2=值
同时也可以从query overview ui中看到,相关扫描stage的数据量 / split数量得到的单个split数据量很小,也是小文件较多的迹象:

这种情况下可以参考部署英特尔开源的SSM服务进行小文件合并:
3.5、某个stage的算子block住其他stage
由于trino的默认处理模式是流式的,某一环节的算子执行的慢,会block住所有其他各stage的算子,分为2种情况:
(1)表扫描太慢block住下游所有算子:在具体查询的Live Plan界面里,看到各底部scan数据的stage没有block,而下游stage被block:

这种时候就要去对应排查具体涉及的存储引擎是否有性能瓶颈,例如小文件等。
(2)下游算子计算慢,反压block回扫描stage:和上面的情况相反,是执行计划树底部TableScan或者ScanFilterProject所在的stage出现block,而下游stage(也就是Live Plan中上方的stage)的block时间远小于Scan Stage的block时间,则可以判断是下游的计算算子处理慢了,需根据具体SQL来改进书写方式。
3.6、Join时右侧表远大于左侧表导致查询慢
Trino自身会根据对应表存储的统计信息(行数、容量、列的最小最大值等)来自动进行一些执行计划优化,但是当统计信息缺失时,Trino并不确定用什么方式最优,只能用默认行为来执行。例如下图中的事件表与历史标签表进行join,本来Trino在提前得知标签表会更大的前提下,可以自动调换join的左右顺序,即reorder join自动优化。
但是由于事件表量级太大,没有提前进行过analyze命令,Trino并不提前知道事件表的statistics,只好采用了对join的左侧即事件表进行probe,右侧的标签表进行hash build(把它以为的“小表”每一行进行哈希值计算,全部加载到内存,广播到各probe侧的worker里,类似spark的broadcast join)的模式,导致标签表容量大、在广播时的开销很高的现象,本来它更大应该处于左边的probe侧。

这种情况下,可以手动改写SQL调换join的顺序,例如ta_event_3 left join history_tag_3改写为history_tag_3 right join ta_event_3,或者对join两边的表执行如下命令之后再join:
analyze 要统计信息的表名
3.7、Could not communicate with the remote task. The node may have crashed or be under too much load. This is probably a transient issue, so please retry your query in a few minutes.
很多时候这个报错主要由2方面的原因导致:
(1)集群CPU负载高或者出现Full GC,导致worker出现几十秒甚至分钟级的不对外进行HTTP响应的情况。
(2)集群网络带宽有瓶颈或质量抖动。
除了分析当时时刻是否有大查询、集群资源是否充足、网卡是否为千兆以外,缓解的方法是设置如下config.properties参数:
exchange.http-client.request-timeout=30s
node-manager.http-client.request-timeout=30s
# 仅限coordinator
memoryManager.http-client.request-timeout=15s
3.8、Column name not specified at position
一般是因为SQL中某些select的列没有写as别名,例如:
create table t1 as select c1, c2, '2023-07-03 17:55:00' from t2
解决的办法是找到没有取别名的列加上as语句,如下所示:
create table t1 as select c1, c2, '2023-07-03 17:55:00' as stamp1 from t2
3.9、stored view column count does not match column count derived from the view query analysis
大部分情况下是因为使用select *来定义view,例如:
CREATE VIEW hive.test.v_101 SECURITY DEFINER AS
SELECT * FROM t_101
如果底层表t_101增加了列,就会出现view和table的列数对不上的问题,此时应该更新view的定义。
3.10、Corrupted statistics for column xxx in Parquet file
这是对应的parquet文件内容中有损坏的列统计信息,可以在hive.properties或iceberg.properties配置中加参数parquet.ignore-statistics=true跳过statistics读取。
3.11、HDFS内存不足/响应慢导致查询慢
可以用如下命令判断HDFS NameNode是否存在瓶颈:
curl hostname:50070/dfshealth.jsp | grep 'files and directories'
如果命令结果响应慢几秒以上,或者结果显示堆内存占用90%以上,则需要看看是否带有cgroup配置,或者提升namenode堆内存。
3.12、java.lang.NumberFormatException: For input string: "9932128949"
是因为int值超过了最大2147483647的限制,可以排查一下脏数据,或者show create table检查是否有不符合业务数据长度定义的列。
四、不建议的SQL写法
不建议的写法并不代表完全不能写,只是如果具体业务场景可以避免这种写法就尽量减少,如果具体逻辑无法用其他写法替代,必须使用一些复杂且性能较差的写法时,也是可以的。
4.1、过多的连续join
Trino源码中默认只能对9个以内的连续join进行自动调整join顺序以降低计算开销的优化,且过于庞大的SQL往往会消耗更多的plan时间,所以一般建议SQL中连续的join语句最多不超过9个,最好可以不超过4到5个,尽量每几个连续join拆分成一个临时表或者view后再接着join。
4.2、过多的arbitrary()函数
如下issue提到了arbitrary()函数在内存效率上的问题:
4.3、over()等窗口函数不用partitioned by语句等限定窗口范围
这种情况下,如果还不带上where等过滤条件,就会相当于每一行数据都匹配全表范围的窗口,内存等开销会很大。
4.4、全表cross join/笛卡尔积
如果不加过滤条件,左表的每一行数据都交错匹配右表的每一行数据,执行时间和资源消耗将大大增加。
本文介绍了如何通过explainanalyze分析SQL性能瓶颈,如数据倾斜、CPU竞争和RemotePageIsTooLarge等问题,并提供了相应的解决策略,包括调整SQL语句、优化join顺序、处理小文件和管理统计信息。此外,还讨论了不建议的SQL写法,如过多的join、arbitrary()函数和未限定窗口范围的窗口函数。

被折叠的 条评论
为什么被折叠?



