Presto查询优化

Presto是一个开源的分布式SQL查询引擎,适用于交互式分析查询,数据量支持GB到PB字节。查询语言是类ANSI SQL语句。笔者在多个项目中用到Presto作即席查询,总结了一些优化措施。算法


1、数据存储

  1. 合理设置分区
    与Hive相似,Presto会根据元信息读取分区数据,合理的分区能减小Presto数据读取量,提高查询性能。
  2. 使用列式存储
    Presto对ORC文件读取作了特定优化,所以在Hive中建立Presto使用的表时,建议采用ORC格式存储。相对于Parquet,Presto对ORC支持更好。
  3. 使用压缩
    数据压缩能够减小节点间数据传输对IO带宽压力,对于即席查询须要快速解压,建议采用snappy压缩
  4. 预先排序
    对于已经排序的数据,在查询的数据过滤阶段,ORC格式支持跳过读取没必要要的数据。好比对于常常须要过滤的字段能够预先排序。
INSERT INTO table nation_orc partition(p) SELECT * FROM nation SORT BY n_name;

若是须要过滤n_name字段,则性能将提高。网络

SELECT count(*) FROM nation_orc WHERE n_name=’AUSTRALIA’;

2、查询SQL优化

  1. 只选择使用必要的字段
    因为采用列式存储,选择须要的字段可加快字段的读取、减小数据量。避免采用*读取全部字段。
[GOOD]: SELECT time,user,host FROM tbl
[BAD]:  SELECT * FROM tbl
  1. 过滤条件必须加上分区字段
    对于有分区的表,where语句中优先使用分区字段进行过滤。acct_day是分区字段,visit_time是具体访问时间
[GOOD]: SELECT time,user,host FROM tbl where acct_day=20171101
[BAD]:  SELECT * FROM tbl where visit_time=20171101
  1. Group By语句优化
    合理安排Group by语句中字段顺序对性能有必定提高。将Group By语句中字段按照每一个字段distinct数据多少进行降序排列。示例中uid是用户id,比性别数据大不少。
[GOOD]: SELECT GROUP BY uid, gender
[BAD]:  SELECT GROUP BY gender, uid
  1. Order by时使用Limit
    Order by须要扫描数据到单个worker节点进行排序,致使单个worker须要大量内存。若是是查询Top N或者Bottom N,使用limit可减小排序计算和内存压力。
[GOOD]: SELECT * FROM tbl ORDER BY time LIMIT 100
[BAD]:  SELECT * FROM tbl ORDER BY time

还有尽可能将排序的字段减小些能加快计算。session

  1. 使用近似聚合函数
    Presto有一些近似聚合函数,对于容许有少许偏差的查询场景,使用这些函数对查询性能有大幅提高。好比使用approx_distinct() 函数比Count(distinct x)有大概2.3%的偏差。
SELECT approx_distinct(user_id) FROM access

若是非要精确去重,请用Count+Group 语句代替app

  1. 用regexp_like代替多个like语句
    Presto查询优化器没有对多个like语句进行优化,使用regexp_like对性能有较大提高
[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 '%DELETE%'
  1. 使用Join语句时将大表放在左边
    Presto中join的默认算法是broadcast join,即将join左边的表分割到多个worker,而后将join右边的表数据整个复制一份发送到每一个worker进行计算。若是右边的表数据量太大,则可能会报内存溢出错误。
[GOOD] SELECT ... FROM large_table l join small_table s on l.id = s.id
[BAD] SELECT ... FROM small_table s join large_table l on l.id = s.id

若是左表和右表都比较大怎么办?为了防止内存报错
1)修改配置distributed-joins-enabled (presto version >=0.196)
2)在每次查询开始使用distributed_join的session选项分布式

-- set session distributed_join = 'true'
SELECT ... FROM large_table1 join large_table2
on large_table1.id = large_table2.id

核心点就是使用distributed join. Presto的这种配置类型会将左表和右表同时以join key的hash value为分区字段进行分区. 因此即便右表也是大表,也会被拆分.
缺点是会增长不少网络数据传输, 因此会比broadcast join的效率慢.函数

  1. 使用Rank函数代替row_number函数来获取Top N
    在进行一些分组排序场景时,使用rank函数性能更好
[GOOD]
SELECT checksum(rnk)
FROM (
  SELECT rank() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
  FROM lineitem
) t
WHERE rnk = 1

[BAD]
SELECT checksum(rnk)
FROM (
  SELECT row_number() OVER (PARTITION BY l_orderkey, l_partkey ORDER BY l_shipdate DESC) AS rnk
  FROM lineitem
) t
WHERE rnk = 1

9.多用with语句
使用Presto分析统计数据时,可考虑把屡次查询合并为一次查询,用Presto提供的子查询完成。
这点和咱们熟知的MySQL的使用不是很同样。注意下列子查询中的逗号。性能

WITH subquery_1 AS (
    SELECT a1, a2, a3 
    FROM Table_1 
    WHERE a3 between 20180101 and 20180131
),              
subquery_2 AS (
    SELECT b1, b2, b3
    FROM Table_2
    WHERE b3 between 20180101 and 20180131
)               
SELECT 
    subquery_1.a1, subquery_1.a2, 
    subquery_2.b1, subquery_2.b2
FROM subquery_1
    JOIN subquery_2
    ON subquery_1.a3 = subquery_2.b3;
  1. 尽可能用UNION ALL代替UNION
    和distinct的缘由相似, UNION有去重的功能, 因此会引起内存使用的问题.
    若是你只是拼接两个或者多个SQL查询的结果, 考虑用UNION ALL

3、无缝替换Hive表

若是以前的hive表没有用到ORC和snappy,那么怎么无缝替换而不影响线上的应用:
好比以下一个hive表:优化

CREATE TABLE bdc_dm.res_category(
channel_id1 int comment '1级渠道id',
province string COMMENT '省',
city string comment '市', 
uv int comment 'uv'
)
comment 'example'
partitioned by (landing_date int COMMENT '日期:yyyymmdd')
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' COLLECTION ITEMS TERMINATED BY ',' MAP KEYS TERMINATED BY ':' LINES TERMINATED BY '\n';

创建对应的orc表ui

CREATE TABLE bdc_dm.res_category_orc(
channel_id1 int comment '1级渠道id',
province string COMMENT '省',
city string comment '市', 
uv int comment 'uv'
)
comment 'example'
partitioned by (landing_date int COMMENT '日期:yyyymmdd')
row format delimited fields terminated by '\t'
stored as orc 
TBLPROPERTIES ("orc.compress"="SNAPPY");

先将数据灌入orc表,而后更换表名rest

insert overwrite table bdc_dm.res_category_orc partition(landing_date)
select * from bdc_dm.res_category where landing_date >= 20171001;

ALTER TABLE bdc_dm.res_category RENAME TO bdc_dm.res_category_tmp;
ALTER TABLE bdc_dm.res_category_orc RENAME TO bdc_dm.res_category;

其中res_category_tmp是一个备份表,若线上运行一段时间后没有出现问题,则能够删除该表。

4、注意事项

  1. ORC和Parquet都支持列式存储,可是ORC对Presto支持更好(Parquet对Impala支持更好)
  2. 对于列式存储而言,存储文件为二进制的,对于常常增删字段的表,建议不要使用列式存储(修改文件元数据代价大)。对比数据仓库,dwd层建议不要使用ORC,而dm层则建议使用

做者:叫我小名 连接:https://www.jianshu.com/p/f435ce79c966 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。