cover_image

Presto在中通的优化实践

数据智能部 科技中通
2024年04月09日 07:25
图片

Presto最初由Facebook公司开发,并于2012年开源。早期,Facebook依赖于Hive做数据分析,伴随业务需求的不断提升,Facebook的工程师们发现,hive不能很好地满足交互式查询的数据分析场景。对此,工程师们另辟蹊径,创造性地开发出了Presto这款性能卓越的OLAP分析引擎。Presto查询性能比hive快数十倍,并且提供异构数据源间的关联分析能力,使其在大数据OLAP领域迅速崭露头角。

Presto在中通共有3套集群,日均响应150个应用系统超300w次的查询分析需求。为了更好地满足业务需求,我们对Presto做了大量源码改造和性能调优。并且还自研了Presto监控告警系统、根因分析、查询诊断等,保障服务的稳定可靠。

图片

图片

在中通,Presto主要应用在对Hive数仓的分析场景。离线集群的特点是批处理任务多,集群负载高。为了降低离线集群HDFS抖动对Presto查询带来的影响,我们引入了alluxio,将重点保障的表放到缓存中,加速数据拉取。

为了让Presto稳定可靠地为业务系统提供交互式查询能力,避免集群维护导致的查询失败。我们调研并引入了Presto Gateway,它可以动态路由查询请求到Active的Presto集群中,提供负载均衡功能的同时避免集群维护导致的服务不可用。让我们可以随时随地地对其中一套Presto集群进行维护,做到运维过程用户无感知。

另外,我们还先后自研了Presto监控告警系统,它可以采集并分析历史的查询信息,便于事后复盘。根因分析系统可以对失败的查询进行问题分析,将错误的原因和优化建议第一时间推送给用户。诊断分析系统目前正在建设当中,该系统从集群状况、用户SQL、网络IO、jvm等多个维度综合分析查询慢的原因,并给出优化建议。

图片

Presto拥有极致的交互式查询体验,但在某些场景下,需要进行定制化的调优。我们对Presto做了大量的优化,以下列举出几个效果较好的优化实践:

1. Hive MetaStore缓存

Presto对SQL的解析需要频繁调用Hive Metastore获取hive表的元数据信息。如果Hive Metastore响应慢,会直接影响Presto的查询性能。比如某个查询耗时3s,但planning过程耗时却超过了2s。为了提升执行计划生成的性能,我们开启了Hive Metastore缓存,开启后planning阶段性能提升了数十倍,带来了很可观的收益。

图片

2. 热表自动加速

Presto存在重复拉数据,重复计算的问题。在多数场景下,拉取数据的耗时占整个查询的80%以上。由于Hive数仓所在的离线集群环境复杂,抖动较大,同样的查询耗时差异非常大。为了保障业务系统的查询体验,提升拉取数据环节的性能与稳定性,我们集成了Alluxio。

图片

通过测试,使用Alluxio缓存加速后,Presto查询性能提升了10倍以上,极大地提升了业务系统交互式查询体验。

图片

过去,hive表缓存加速依赖于人工判断与执行,效率低。对此,我们基于表访问热度进行自动cache与uncache,省去人工环节。主要的实现思路是通过修改presto listener源码,将每个查询的metrics数据发送到kafka。下游程序消费kafka中的数据,统计表访问的次数。对于访问频次高,符合加速条件的表,系统将自动将其缓存到Alluxio中。对于访问频次较少的表,会及时从缓存中uncache掉,最大化地提升资源利用率。

图片

图片

Presto服务的稳定性,关系到下游业务系统的使用体验。对于Presto本身来说,需要对接许多第三方组件,外部组件的稳定性直接影响着Presto。如何提升Presto集群稳定性一直是我们的建设重点。

1. JVM调优

作为内存计算引擎的Presto,对JVM优化要求较高。过去,我们通过监控发现,部分worker会发生失联。由于Presto不具备容错性,这就导致跑在失联worker上的查询失败。对于那些需要跑数十分钟的ETL任务来说,因worker失联导致重跑是难以接受且代价昂贵的。

我们通过Presto监控分析发现,worker full gc会长达数分钟,而worker向coordinator发送心跳默认的超时时间是60s。一旦发生full gc,coordinator就会认为该worker挂了。为了解决full gc给查询稳定性带来的影响,我们切换了ZGC,将GC耗时降至0.06ms以内,效果如下图所示:

图片

升级后,我们发现linux的提交内存指标在不断的膨胀,而且膨胀的很快,怀疑有内存泄露:

图片

首先,开启NMT进行堆上的排查。

图片

通过观测发现,on heap内存区相当较稳定,于是怀疑是堆外发生了泄漏。使用google-perftools进行堆外的勘测,结果如下:

图片

其中解压相关的对象在堆外大量的存在,猜测Presto在升级ZGC之后无法及时回收堆外内存中被缓存起来的压缩数据。经过调研,我们尝试了tcmalloc和jemalloc两款内存管理器。对比测试如下:

图片

tcmalloc

图片

对比测试,发现jemalloc可以让内存很稳定地保持在较低的水准,并且jemalloc在多线程环境下有着优良的内存管理水准,让Presto Worker可以更快地申请释放内存。

图片

jemalloc需要手动编译和将动态链接库挂到jvm上。挂载到Presto启动的jvm上需要修改launcher.py脚本。如图所示,动态链接库要挂载在jvm上通过PRELOAD钩子将其挂载在jvm启动之前。至此,堆外内存泄漏问题得到解决。

图片

引入ZGC后的另一个收益是集群整体性能的提升,通过tpch的压测,性能提升超过50%以上。

图片

2. HDFS长尾优化

离线HDFS压力大,抖动频繁,严重影响下游Presto数据拉取的稳定性。过去,每日有近千条查询失败是因为datanode请求超时引起的,异常堆栈如下:

图片

对此,我们调整了hdfs请求超时的参数,极大地降低了hdfs抖动带来的查询失败问题。参数如下:

图片

经过统计对比,将优化前每日近千条的查询失败数降到5个左右,效果显著。

图片

3. 细粒度的资源组

随着业务系统的不断接入,单个查询打满整个Presto计算资源的问题也开始凸显。为了实现账号级别细粒度的资源管控,保障整体查询响应的稳定性,我们开启了resource group,并对每个账号设置了资源使用上限。比如并发数、调度权重、执行超时时间等。开启资源组后,有效保障了Presto整体的平稳性。

图片

4. 查询超时kill

作为面向AP场景的Presto无法承载较高的并发,我们一方面通过开启resource group限制每个账号的并发数。另一方面,对超过阈值的查询进行主动kill,避免大量耗时的查询积压,影响整个集群。

图片

5. Worker优雅下线

对于线上环境,不可避免地进行节点维护、集群扩缩容等常规运维操作。由于Presto不具备容错机制,强制下线worker会导致部分查询失败。为了屏蔽节点维护给用户带来的影响,我们将worker优雅上下线功能集成到Presto运维管理系统中。某个worker一旦开启优雅下线后,coordinator将停止新的task下发,同时该worker上已有的task都执行完成后,才会自动停止进程。实现的原理是调用worker的接口:

图片

图片

图片

随着用户的不断增加,遇到的问题也随之增加。为了更好地服务用户,协助快速解决问题,我们先后建设了根因分析系统以及查询诊断系统

1. 根因分析

图片

实现的思路是将Presto异常的查询信息发送到kafka中,使用flink对异常根因进行解析和处理后回送到中通的根因字典当中,提供给用户解决查询的异常。目前根因分析可覆盖90%以上的查询异常诊断。

2. 查询诊断

图片

诊断分析是根因分析的升级版,根因分析主要是分析查询失败的原因,而查询诊断在这个基础上会更进一步分析查询慢的原因,以及告诉用户该如何去优化SQL。Presto诊断分析系统将从上游HDFS抖动情况、Presto集群自身负载情况、JVM运行时以及用户SQL等多个维度进行全面的分析。

SQL查询诊断会对用户的SQL进行语义解析,当发现count distinct、stage过多等情况,会第一时间通知用户优化SQL。

Presto集群负载维度将会周期性分析当前Presto集群的查询负载、查询队列情况等,给出查询慢的原因。

图片

为了让Presto更好地满足实际需求,过去几年,我们先后对Presto进行了数十处的源码改造,并且还向社区提交了PR。下面列举几处源码改造,供大家参考:

1. 支持用户认证

修改Presto jdbc与main模块的源码,支持用户信息校验与权限隔离。

2. 支持严格模式

严格模式可有效过滤不合理的SQL查询,降低资源开销。可惜的是,我们所使用的Presto不支持严格模式,对此,我们修改了Presto执行计划,支持严格模式:

图片

3. Hudi分区谓词下推

年初我们引入了hudi connector加速hudi表的查询时效。但hudi connector并没有实现分区下推的功能,导致hudi表的查询在hudi connector中需要扫描全表。为此,我们实现了hudi的分区谓词下推,将hudi查询性能提升了数十倍。

图片

图片

1. 动态集群扩缩容

图片

线上3套Presto集群中包括2个交互式分析集群(app)和1个离线ETL集群。白天app集群负载高,ETL集群负载低,夜间正好反过来。为了解决集群间的资源错配问题,后续将支持worker级别的动态扩缩容技术。结合worker优化下线功能,动态地将负载低的集群部分worker扩容到负载高的集群,提升资源的利用率。

2. 拥抱Trino

近些年,我们发现Trino的社区活跃度更高,第三方组件的支持也更丰富。Hudi 和clickhouse的connector源码就是从Trino移植过来的。未来,我们将全面拥抱Trino,进一步提升OLAP分析的能力。


图片

图片

继续滑动看下一个
科技中通
向上滑动看下一个