自SparkSQL横空出世以来,受到了广大大数据开发同学的热捧。如果说Hive是数仓领域任劳任怨的一头老牛,那Spark SQL好比是一匹枣红快马。我们也于今年(2020)年初启动了离线计算提速专项项目,通过近乎透明的方式实现了一套Hive SQL至Spark SQL的迁移工具。
我们在迁移过程中发现一个比较特殊的案例:
1 | 该任务由Hive迁至Spark之后, 性能显著提升约 10+ 倍, 历史批次 Hive 运行约 80+ 分钟, 迁移 Spark 后约耗时 7分钟(10分钟以内). |
作为一名离线存储计算引擎Hive的自研人员,不禁反问Hive真的如此不堪么?于是针对该案例探究一番,聊以此文抛转引玉,致敬Hive,拥抱Spark。 本文旨在通过一个线上实际案例来说明 Hive SQL 任务问题排查的方法论:如何从成百上千行的 sql 脚本中定位到存在问题的 sql 片段,以及如何进行优化,希望能够对大数据开发同学尤其是数仓同学有一定的借鉴意义。
案例解析
本文案例SQL脚本有数百行,从历史批次运行日志中发现,任务运行约80+分钟。摘录一段历史批次日志如下:
1 | [2020-09-14 12:10:06] 开始环境准备... |
从日志可以看出,任务落地文件1个,落地数据条数326,如此小的数据量,但任务从 12:10 开始运行,直至 13:36 运行结束,该任务为什么这么慢?虽然该任务运行缓慢并没有影响线上业务线的整体产出,但作为有技术洁癖的引擎开发人员而言,这个问题得解。
哪里慢?
遇到调优,用户往往一筹莫展,无从下手。我们的抓手是什么呢?日志、日志、日志,重要的事情说三遍。
1 | Tips: 任何问题我们都可以从日志中发现蛛丝马迹,所以排查问题的第一抓手便是日志,如果 info 日志不够的时候,我们还应该考虑临时开启 debug 日志。 |
仔细研读运行日志我们发现,任务运行87分钟,而 Stage-20 是瓶颈点,耗时约 1 个小时。那么 Stage-20 是做什么呢?此时便是第二抓手—— SQL 利器 EXPLAIN ,我们通过分析执行计划来定位异常 SQL 片段。
1 | Tips: Hive中一个完整的MapReduce阶段代表一个stage。通常当某个stage出现异常时,我们可以通过Explain查看执行计划,来和具体的SQL脚本片段对应起来。 |
下面是当前案例的执行计划摘要:
1 | OK |
为何慢?
前面知道了哪里慢,下面需要追根溯源,去探究为何慢?
根据 Stage-20 对应的 tracking url,查看对应 RM 日志如下:
可以看出 hang 在数据读取上,但线索貌似中断了。往往在这时用户会选择放弃,但作为有技术洁癖的引擎开发人员而言,这个问题得解。
此时轮到另一个抓手 jstack 隆重出场了。
1 | Tips: 当任务卡住不动时,我们可以通过jstack查看当前线程的状态。 |
登录当前stage运行的namenode,通过jobId搜索到对应的进程,然后通过jstack查看该进程的相关线程在干什么。
本案例中线程栈如下:
1 | "main" #1 prio=5 os_prio=0 tid=0x00007f130c011800 nid=0x1b30c runnable [0x00007f1315ecd000] |
可以看出是在做 decimal 数据转换,哪里做转换呢?再回过头来看执行计划:
1 | TableScan |
(((CAST( order_id AS decimal(38,19)) is not null 中 order_id 字段存在 cast 类型转换,通过查看对应表结构发现,该字段在join的两表中类型不一致,分别为 string 和 bigint 。这里要说明一下在 join 中关联条件字段会做 is not null 过滤。
如何改?
至此我们已经知道了哪里慢、为何慢,下面我们来看如何改?也就是如何调优。
上面我们发现 join 条件字段类型不一致是罪魁祸首,由于每条数据都要做类型转换之后在判断 is not null,从而带来了很大的性能开销。那如果字段类型一致是否是打开潘多拉魔盒的一把钥匙呢?我们改造 SQL 如下:
1 | select default.order_decode(trim(oid)) as order_id |
执行验证效果:
1 | hive> select default.order_decode(trim(oid)) as order_id |
我们发现仅需231秒便运行完毕,较优化前性能提升近10倍。
Spark为何快?
我们前面提到,该案例是我们在做离线计算提速专项中,将Hive任务迁移至Spark发现的,那不禁要问,Spark为何快呢?一起来看Spark的执行计划:
可以看出,上图红框中isnotnull过滤条件中并没有做cast类型转换,而是将该阶段放在了读取数据之后。
一些思考
我们从一个 Hive SQL 任务运行缓慢的实际案例出发,阐述了定位,分析以及调优的经过。从整体来看,Hive SQL 的调优繁琐,往往令关注业务的数仓同学望而却步。而 Spark SQL 以天然的底层优化,降低了用户调优的门槛。本文大篇幅阐述了一个Hive SQL调优的经过,旨在抛砖引玉,推荐用户更多地倾向于使用 Spark 引擎,从而带来更大的性能提升和收益。
本文链接: https://stefanxiepj.github.io/archives/83bf2dd4.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
![知识共享许可协议](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)