基于Tez引擎的 Hive SQL 性能优化
Hive 执行过程分析
1.优化之前首先了解 Hive 是如何执行 SQL 的,生产环境基于 Tez 引擎,这一点在分析问题时也需要考虑。从大的方向上看 Hive 由以下执行过程组成:
- Compile Query 编译 SQL 为逻辑计划
- Prepare Plan 生成物理执行计划
- Get Query Coordinator 向 YARN 申请 ApplicationMaster (AM)
- Submit Plan 提交 DAG 到 AM
- Start DAG DAG 初始化启动
- Run DAG Tez 并行执行 DAG 中每个节点的task
根据这个过程再结合运行日志就可以获取到每个过程运行的耗时:
Query_Execution_Summary
从截图日志看 6. Run DAG 占用绝大部分的时间,这点是正常的,毕竟这一步就是在真正执行计算任务。为什么要看这里的信息,有时候集群资源紧张向 YARN 申请资源时耗时过长,会让人误以为 SQL 性能问题,看总体的查询信息避免无效的精力投入。
2.接下来看 SQL 构成,这个脚本主要由一张事实表关联多张维表分几部分 union 合并数据后写入另一张表,先从每段独立的查询语句分别看下日志运行情况。
explain_execution_plan
explain 获取 SQL 的执行计划,看到 union 的每段查询分别对应 MAP1 / Reducer16 和 Reducer8,再从运行日志查看这三段的任务数和耗时。
vertices_status_detail
Task_Execution_Detail
从这两组数据可以明显看出 MAP1 的任务耗时最长(439.748秒),但并发task数量仅仅8个,SQL 脚本的瓶颈显而易见,接下来就是找出 MAP1 的对应代码展开优化分析。
节点 task数量 耗时(s)
MAP1 8 439.748
Reducer8 9 4.597
Reducer16 13 3.577
3.MAP1 的task数量只有8个,显然没有充分利用集群资源,那么下一步便是分析表信息:表是否分区和 where 语句有没有使用分区字段、分区数量多少、文件数量多少、数据量多大,带着这些信息尝试增加task数量,提高任务并行度充分利用集群资源。其中分区字段的使用尤为关键,如果 where 条件未命中分区字段,数据扫描量会成倍增加,后续优化措施的效果也将大打折扣。
使用 desc formatted
语句查看主表信息,得到下面关键信息:主表是分区表,分区字段是 source 和 period。89个分区一共有522个文件,总行数21709392。| # Partition Information | NULL | NULL |
| # col_name | data_type | comment |
| source | string | |
| period | string | |
| | numFiles | 522 |
| | numPartitions | 89 |
| | numRows | 21709392 |
| | rawDataSize | 1736751360 |
| | totalSize | 4589198547 |
从表信息看出主表有很多小文件,平均每个文件大小约为 4589198547 / 522 / 1024 / 1024 = 8MB,虽然CombineHiveInputFormat会自动合并小文件,但是 Tez 会重新打包数据切片动态调节task数量。利用 tez.grouping.min-size 和 tez.grouping.max-size 两个参数可以调节每个切片的数据量从而调节task数。
配置项 优化值 默认值 说明
tez.grouping.min-size 67108864 (64MB) 16777216 (16MB) 控制单个分组切片的最小字节数,Tez 会将小文件合并到这个大小
tez.grouping.max-size 134217728 (128MB) 1073741824 (1GB) 控制单个分组切片的最大字节数,文件超过这个字节数会被拆分
4.分析 MAP1 的 DURATION 和 CPU_TIME 可以看出这个节点完整运行耗时439秒,节点内8个task总计CPU计算耗时5402秒,task是并发运行而CPU_TIME是所有任务的CPU时间累加,故CPU_TIME远大于节点完整耗时。
为了降低单个task的CPU耗时进而减少总体运行时间,可以利用 Hive 的向量化计算让task内部批量处理数据,提高运算效率,从而达到缩短CPU_TIME的目的。向量化计算默认每批处理1024行,通常不用调节这个参数,主要配置参数有下面几个:
配置项 参数值 说明
hive.vectorized.execution.enabled true 总开关,开启MAP端向量化批处理
注:这里不开启的话 *.execution.reduce.enabled 不会生效
hive.vectorized.execution.reduce.enabled true 开启REDUCER端向量化批处理
hive.vectorized.adaptor.usage.mode all 没有对应原生向量化版本的函数适配模式
启用 Hive 向量化有一些前提条件:数据表格式需为列式存储格式(例如 ORC/PARQUET),数据类型需在特定支持范围之内(例如 Int/BigInt/Double/String)。hive.vectorized.adaptor.usage.mode 有 none / chosen / all 三种配置,设置为 all 表示对不支持原生向量化的函数使用适配器包装,使其能在向量化框架内处理,避免因不支持的函数导致整个执行链路回退为行模式。使用 all 可以减少向量模式与行模式切换带来的额外开销。
优化点分析验证
分析再多终究是纸上谈兵,确定好方法后便开始测试验证。根据上面的分析结论需要在脚本中添加配置项:
set hive.vectorized.execution.enabled=true;
set hive.vectorized.execution.reduce.enabled=true;
set hive.vectorized.adaptor.usage.mode=all;
set tez.grouping.min-size=67108864;
set tez.grouping.max-size=134217728;
1.从执行计划观察向量化启用是否生效
before:
map_5_has_not_vectorized
after:
map_5_has_vectorized
对比向量化配置前后的MAP5执行计划,可以看到多了 vectorized 标识,这意味着MAP5这个节点会启用向量化。整个执行链路从之前的"向量模式 -> 行模式 -> 向量模式"反复切换变为全向量模式,避免了模式切换带来的额外开销。至于MAP5为何没有默认开启向量模式,是因为 instr 函数不支持原生向量化。
注:对于 instr 函数不支持向量化的情况,还可以修改 SQL 语句,例如用 col01 like ‘%something%’ 替代原有逻辑,因为 like 有原生对应的向量化版本,这也是一种优化方式。需要注意,仅当 instr 用于判断子串是否存在(> 0)时,才可等价替换为 like。
2.数据切片的大小因环境而异,没有固定的最佳值。这里不用系统默认值,选择 [64MB, 128MB] 的区间是经过测试的结果。数据切分太小会导致 task 数量过多,占用太多 YARN 容器资源,且每个容器未得到充分利用,造成资源浪费。此外,生产环境通常不止一个任务在运行,过多占用集群资源会挤占其他任务的资源,因此这个区间需根据自身集群情况调整合适的范围。
vertices_status_detail_after
打印运行日志看到 MAP1 task数从 8个 增加到 94 个,说明数据切片配置已经生效。
3.验证 SQL 执行性能的提升效果
before:
Query_Execution_Summary
更多推荐



所有评论(0)