Presto介绍及常用查询优化方法总结

2021-05-10 10:43:05 浏览数 (1)

​1、Presto简介

Presto是Facebook开源的MPP(Massive Parallel Processing)SQL引擎,其理念来源于一个叫Volcano的并行数据库,该数据库提出了一个并行执行SQL的模型,它被设计为用来专门进行高速、实时的数据分析。

Presto是一个SQL计算引擎,分离计算层和存储层,其不存储数据,通过Connector SPI实现对各种数据源(Storage)的访问。

1.1 架构

Presto沿用了通用的Master-Slave架构,一个Coordinator,多个Worker。

Coordinator负责解析SQL语句,生成执行计划,分发执行任务给Worker节点执行,Worker节点负责实际执行查询任务。

Presto提供了一套Connector接口,用于读取元信息和原始数据。

Presto 内置有多种数据源,如 Hive、MySQL、Kudu、Kafka 等。

Presto 的扩展机制允许自定义 Connector,从而实现对定制数据源的查询。

假如配置了Hive Connector,需要配置一个Hive MetaStore服务为Presto提供Hive元信息,Worker节点通过Hive Connector与HDFS交互,读取原始数据。

1.2 实现低延时的原理

Presto是一个交互式查询引擎,我们最关心的是Presto实现低延时查询的原理,以下几点是其性能脱颖而出的主要原因:

  • 完全基于内存的并行计算
  • 流水线
  • 本地化计算
  • 动态编译执行计划
  • 小心使用内存和数据结构
  • GC控制
  • 无容错

2、Presto查询优化

2.1 存储优化

① 合理设置分区

与Hive类似,Presto会根据元信息读取分区数据,合理的分区能减少Presto数据读取量,提升查询性能。

② 使用列式存储

Presto对ORC文件读取做了特定优化,因此在Hive中创建Presto使用的表时,建议采用ORC格式存储。相对于Parquet,Presto对ORC支持更好。

③ 使用压缩

数据压缩可以减少节点间数据传输对IO带宽压力,对于即席查询需要快速解压,建议采用snappy压缩

④ 预先排序

有条件的话提前做好排序,对于已经排序的数据,在查询的数据过滤阶段,ORC格式支持跳过读取不必要的数据。

比如对于经常需要过滤的字段可以预先排序。

2.2 查询优化

① select时只选择必要字段,避免使用 * 号

② 过滤条件加上分区字段,减少查询数据量

③ 合理安排Group by语句中字段顺序对性能有一定提升

将Group By语句中字段按照每个字段distinct数据多少进行降序排列。示例中uid是用户id,比性别数据大很多。

代码语言:javascript复制
[GOOD]: SELECT GROUP BY uid, gender 
[BAD]:  SELECT GROUP BY gender, uid

④ Order by时使用Limit

Order by需要扫描数据到单个worker节点进行排序,导致单个worker需要大量内存。如果是查询Top N或者Bottom N,使用limit可减少排序计算和内存压力。

⑤ 用regexp_like代替多个like语句

Presto查询优化器没有对多个like语句进行优化,使用regexp_like对性能有较大提升

代码语言:javascript复制
[GOOD] 
SELECT ... 
FROM access 
WHERE regexp_like(method, 'GET|POST|PUT|DELETE') 
​
[BAD] 
SELECT ... 
FROM access 
WHERE 
method LIKE '%GET%' OR  
method LIKE '%POST%' OR  
method LIKE '%PUT%' OR  
method LIKE '�LETE%'

⑥ 使用Rank函数代替row_number函数来获取Top N

在进行一些分组排序场景时,使用rank函数性能更好

2.3 Join优化

① 使用Join语句时将大表放在左边

Presto中join的默认算法是broadcast join,即将join左边的表分割到多个worker,然后将join右边的表数据整个复制一份发送到每个worker进行计算。

如果右边的表数据量太大,则可能会报内存溢出错误。

② 如果左表和右表都比较大

为防止内存溢出,做如下配置:

1)修改配置distributed-joins-enabled (presto version >=0.196)

2)在每次查询开始使用distributed_join的session选项

代码语言:javascript复制
set session distributed_join = 'true' 
​
SELECT ... 
FROM 
large_table1 
join large_table2 
on large_table1.id = large_table2.id

核心点就是使用distributed join,也就是hash join。

Presto的这种配置类型会将左表和右表同时以join key的hash value为分区字段进行分区。

所以即使右表也是大表,也会被拆分,相比broadcast join,这种join方式的会增加很多网络数据传输,效率慢。

③ 多个join的OR条件使用union代替

代码语言:javascript复制
SELECT ... 
FROM 
t1 
JOIN t2 
ON t1.a1 = t2.a1 OR
t1.a2 = t2.a2 
​
改为
​
SELECT ... 
FROM 
t1 
JOIN t2 
ON t1.a1 = t2.a1 
union 
SELECT ... 
FROM 
t1 
JOIN t2 
ON t1.a2 = t2.a2

④ 使用WITH语句

使用Presto分析统计数据时,可考虑把多次查询合并为一次查询,用Presto提供的子查询完成。

代码语言:javascript复制
WITH tmp AS ( 
  SELECT DISTINCT a1, a2 
  FROM t2
) 
SELECT ... 
FROM t1 
JOIN tmp 
ON t1.a1 = tmp.a1 
union 
SELECT ... 
FROM t1 
JOIN tmp 
ON t1.a2 = tmp.a2;

⑤ 尽量用UNION ALL代替UNION

和distinct类似, UNION有去重的功能, 所以会使用到内存,如果只是拼接两个或者多个SQL查询的结果, 考虑用UNION ALL


​资深数据专家,关注大数据、数仓、Java、Flink/Spark/Hadoop、OLAP等技术,微信搜索【硬核林川】公众号,回复【资料】可领取最新的大数据视频教程和书籍。

0 人点赞