ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hive性能测试实战:hive-testbench编译、数据生成与压测避坑指南

Hive性能测试实战:hive-testbench编译、数据生成与压测避坑指南

简介:本资源为基于 hortonworks/hive-testbench 编译的 Hive 性能测试程序,面向大数据开发工程师、数据仓库从业者及需要评估 Hive 性能的测试人员。它提供了一套完整的测试用例与脚本,用于模拟数据加载、查询执行等操作,帮助定位 Hive 在稳定性、资源消耗与查询效率上的瓶颈,为调优提供量化依据。压缩包共约 2000 个文件,整体 64.25MB,以 class 编译产物、log 运行日志、sql 测试脚本、ans 预期结果、tpl 模板及 jar 依赖为主,另含少量 sh 启动脚本、properties 配置与 xml 描述文件,覆盖从构建到执行的完整链路。目前已有 395 人学习下载。通过该资源,读者可获取可直接运行的测试程序与配套脚本,结合日志与结果文件对比不同配置下的性能差异,理解 HiveQL、Metastore 与执行引擎的协作机制,为实际集群调优积累可复用的测试数据与排错思路。

1. 从一次压测翻车说起:Hive 性能测试程序到底测什么

很多人第一次接触 Hive 性能测试,都是被一个很朴素的问题逼出来的:集群刚扩容完,老板问“现在到底能扛多少并发”,或者调参之后想验证“这次改动是不是真的有效”。没有一套标准化的压测程序,你只能靠拍脑袋,或者拿几条自己写的 SQL 跑一跑,结果既不可复现,也没法横向对比。Hive 性能测试程序(hive-testbench)就是为解决这个问题而生的——它提供了一套从数据生成到查询执行的完整基准测试框架,核心是 TPC-DS 和 TPC-H 两套标准查询集,配合可配置的数据规模,让你能在自己的集群上跑出可对比、可复现的性能数据。

这套东西适合谁?一是负责集群调优的运维或平台工程师,需要量化验证参数改动的效果;二是做数仓选型或迁移的团队,想对比不同版本、不同引擎(比如 Hive on Tez 和 Hive on Spark)的实际表现;三是准备面试或学习 Hive 执行计划的开发者,想拿真实的大查询练手。它不解决业务问题,但能帮你把“性能”这件事从玄学变成可测量的工程指标。

2. 编译前的环境准备:从源码到可执行包的完整链路

hive-testbench 本质上是一组 Shell 脚本加 Hive SQL 模板,外加一个用于生成 TPC-DS/TPC-H 数据的工具链。它不依赖复杂的构建系统,但对环境有比较明确的要求。很多人卡在第一步不是因为难,而是因为没搞清楚它到底需要什么。

2.1 依赖组件与版本匹配

这套程序的核心依赖是 Hadoop 和 Hive 的客户端环境,因为它最终是通过hive或beeline命令提交查询的。你需要确保集群的 Hive 版本与 testbench 脚本中引用的 SQL 语法兼容。常见做法是:先确认集群 Hive 版本,然后选择对应分支的 testbench 源码。比如 Hive 2.x 和 Hive 3.x 在 Tez 引擎配置上有差异,如果混用,查询可能直接报错。

另外,数据生成阶段依赖dsdgen和dbgen这两个工具,它们来自 TPC 官方工具包,testbench 的脚本里已经包含了编译逻辑。你需要的是 GCC 编译环境、make 工具,以及足够的本地磁盘空间来存放生成的数据文件。我一般会预留至少 100GB 的临时空间,因为 1TB 规模的数据生成过程中,中间文件会占用大量空间。

提示:不要试图在 Hive 服务端节点上直接跑数据生成,最好找一台有客户端环境、磁盘充足的边缘节点,生成后再上传到 HDFS。

2.2 获取源码与目录结构说明

从 GitHub 获取源码后,你会看到几个关键目录:tpcds、tpch、bin、sbin。tpcds和tpch下分别有gen_data、run_query等脚本,bin里是通用的工具脚本。整个项目的设计思路是:先生成数据到本地或 HDFS,再创建 Hive 表并加载数据,最后执行查询集并记录耗时。

# 克隆源码到本地 git clone https://github.com/hortonworks/hive-testbench.git cd hive-testbench # 查看目录结构,确认脚本存在 ls -l tpcds/ tpch/ bin/ sbin/

这段命令只是把代码拉下来,但要注意:不要直接在生产集群的 Hive 服务节点上执行克隆和编译,避免污染环境。我习惯在独立的客户端机器上操作,编译完成后再把生成的 jar 包和脚本分发到需要执行查询的节点。

2.3 编译数据生成工具

testbench 的数据生成工具需要现场编译,因为不同平台的二进制不通用。编译入口在tpcds/gen_data和tpch/gen_data目录下,脚本会自动调用make。但这里有个坑:默认的 Makefile 可能针对特定平台做了优化,如果你的 GCC 版本较新,可能会报一些警告甚至错误。

# 进入 tpcds 数据生成目录 cd tpcds/gen_data # 执行编译,生成 dsdgen 可执行文件 make clean && make # 检查是否生成成功 ls -l dsdgen

编译逻辑说明:make clean先清理旧对象文件,避免残留导致链接错误;make会根据 Makefile 中的规则编译 C 源码。参数方面,如果你需要调整生成数据的并行度,可以修改 Makefile 中的CFLAGS,加上-O2优化编译速度。编译成功后,当前目录会出现dsdgen可执行文件,这就是后续生成 TPC-DS 数据的核心工具。

对于 TPC-H,操作类似,只是工具名变成dbgen。两个工具的参数体系不同,TPC-DS 的dsdgen支持按比例因子(scale factor)生成,比如-SCALE 1000表示生成 1TB 数据;TPC-H 的dbgen用-s参数指定规模,-s 100表示 100GB。

3. 数据生成与建表:把 TPC-DS 数据集灌进 Hive

编译只是热身,真正耗时间的是数据生成和加载。这一步的细节决定了你后续查询测试的成败,因为数据分布不对,查询性能就失去参考意义。

3.1 用 dsdgen 生成指定规模的数据

TPC-DS 的数据模型包含 24 张表,其中事实表有store_sales、catalog_sales、web_sales等,维度表有date_dim、customer、item等。生成数据时,dsdgen会按照比例因子自动计算各表行数。比如比例因子 1000 对应约 1TB 原始数据,其中store_sales表会有数十亿行。

# 在 tpcds/gen_data 目录下执行,生成 100GB 规模的数据 ./dsdgen -SCALE 100 -DIR /data/tpcds_100g -TERMINATE N -FORCE # 参数解释: # -SCALE 100:比例因子,100 约等于 100GB # -DIR:指定输出目录,必须存在且可写 # -TERMINATE N:不在每行末尾加分隔符,保持默认格式 # -FORCE:覆盖已存在的文件

生成过程可能持续几十分钟到数小时,取决于磁盘 IO 和 CPU。我一般会开多个终端并行生成不同的表,但要注意dsdgen默认是单线程的,并行需要手动拆分表名。一个取巧的做法是用-CHILD和-PARALLEL参数,让多个进程分别生成数据分片,最后合并。但合并时要注意文件顺序,否则加载到 Hive 后数据会错乱。

注意:生成过程中如果中断,重新执行时一定要加-FORCE,否则会提示文件已存在而跳过,导致数据不完整。

3.2 创建 Hive 表并加载数据

数据生成到本地后,需要上传到 HDFS,然后创建对应的 Hive 外部表。testbench 提供了create_table.sql模板,但直接执行往往会因为路径、分隔符不匹配而失败。我一般会先检查模板中的LOCATION和ROW FORMAT设置。

-- 以 store_sales 表为例,创建外部表并指定分隔符 CREATE EXTERNAL TABLE IF NOT EXISTS store_sales ( ss_sold_date_sk INT, ss_sold_time_sk INT, ss_item_sk INT, ss_customer_sk INT, ss_cdemo_sk INT, -- 省略其余字段 ss_ext_sales_price DECIMAL(7,2) ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '|' STORED AS TEXTFILE LOCATION '/tpcds/store_sales';

这段 SQL 的关键在于FIELDS TERMINATED BY '|',因为dsdgen默认用竖线分隔字段。如果你生成时改了分隔符,这里必须同步修改。另外,LOCATION指向 HDFS 上的目录,需要提前把本地数据文件上传上去。

# 上传数据到 HDFS hdfs dfs -mkdir -p /tpcds/store_sales hdfs dfs -put /data/tpcds_100g/store_sales.dat /tpcds/store_sales/ # 刷新表分区(如果是分区表) hive -e "MSCK REPAIR TABLE store_sales;"

加载完成后,建议先跑一个COUNT(*)验证行数是否与预期一致。如果行数偏差很大,通常是分隔符或换行符问题。TPC-DS 生成的数据中,某些字段可能包含换行符,导致 Hive 读取时行数膨胀。解决办法是在建表时加上ESCAPED BY '\\',并在生成数据时用-TERMINATE N避免多余分隔符。

3.3 查询集执行与耗时采集

testbench 的查询集在tpcds/run_query目录下,每个查询是一个独立的.sql文件。执行方式很简单,但采集耗时需要自己封装。

# 执行单个查询并记录耗时 start_time=$(date +%s) hive -f tpcds/run_query/query1.sql > /dev/null 2>&1 end_time=$(date +%s) echo "Query1 耗时: $((end_time - start_time)) 秒"

如果要批量执行所有查询,可以写一个循环脚本,把每个查询的耗时输出到 CSV 文件。这里有个细节:Hive 的查询编译时间也计入总耗时,如果你只想测执行时间,可以用EXPLAIN先预热,或者用beeline的--outputformat=csv参数只取结果。我一般会跑三轮取平均值,第一轮预热,后两轮取稳定值。

4. 避坑与排查:那些让你白跑一整天的细节

这套程序看起来只是脚本加 SQL,但实际跑起来,坑一个接一个。下面这几条是我和同行踩过的血泪经验,按现象、原因、解决三段式整理。

4.1 数据生成报错“Cannot open file”

现象:执行dsdgen时提示无法打开输出文件,进程直接退出。 原因:-DIR指定的目录不存在,或者当前用户没有写权限。dsdgen不会自动创建目录。 解决:提前用mkdir -p创建目录,并确认chmod 755权限。如果目录在 NFS 上,还要检查挂载参数是否支持大文件写入。

4.2 查询执行报“Vertex failed”或内存溢出

现象:跑 TPC-DS 的复杂查询时,Tez 任务失败,日志显示容器内存超限。 原因:TPC-DS 的某些查询(如 query 72、query 88)会产生巨大的中间结果,默认的容器内存配置不够。 解决:调整hive.tez.container.size和hive.tez.java.opts,一般建议容器内存不低于 4GB,Java 堆设为容器大小的 80%。同时检查tez.runtime.io.sort.mb是否过小,导致频繁溢写。

4.3 表行数与预期严重不符

现象:COUNT(*)结果比 TPC-DS 官方文档给出的行数少很多或多很多。 原因:数据文件中的换行符与 Hive 默认行分隔符冲突,或者字段分隔符设置错误。 解决:用head查看原始数据文件,确认分隔符和换行符。建表时显式指定LINES TERMINATED BY '\n',并加上ESCAPED BY '\\'。如果已经建表,可以重建外部表并重新加载。

4.4 查询耗时波动极大

现象:同一个查询,两次执行耗时相差数倍。 原因:Hive 的查询结果缓存、Tez 会话复用、集群其他任务干扰都会影响耗时。 解决:每次测试前清理缓存(hive -e "CLEAR CACHE"),关闭 Tez 会话复用(set hive.tez.container.reuse=false),并确保集群没有其他大任务在跑。最好在固定时间段测试,记录多次结果取中位数。

4.5 编译 dsdgen 时链接失败

现象:make报错“undefined reference topthread_create”。 原因:Makefile 中缺少-lpthread链接选项,或者 GCC 版本对 POSIX 线程库的默认链接行为有变化。 解决:手动修改 Makefile,在LDFLAGS中加上-lpthread,然后重新make clean && make。如果还不行,检查系统是否安装了glibc-static和libstdc++-static。

5. 进阶技巧:让压测结果真正可对比

跑通一遍不算本事,能让结果在不同时间、不同集群之间横向对比,才算把这套工具用到位。下面这几个习惯,是我从多次翻车中总结出来的。

5.1 固定测试基线与环境快照

每次压测前,记录集群的节点数、内存、磁盘类型、Hive 版本、Tez 版本、关键参数(如hive.exec.dynamic.partition.mode、tez.am.resource.memory.mb)。这些信息不记录,后面看到耗时差异你根本不知道是参数改了还是机器换了。我一般会写一个env_snapshot.sh,在测试前自动采集这些信息并输出到日志。

#!/bin/bash # 采集环境快照 echo "=== 集群节点 ===" > env_snapshot.log yarn node -list 2>/dev/null | grep -c "RUNNING" >> env_snapshot.log echo "=== Hive 版本 ===" >> env_snapshot.log hive --version 2>&1 | head -1 >> env_snapshot.log echo "=== 关键参数 ===" >> env_snapshot.log hive -e "set hive.tez.container.size; set tez.am.resource.memory.mb;" >> env_snapshot.log 2>&1

这个脚本输出的信息虽然简单,但在对比两次测试结果时,能帮你快速排除环境差异。比如某次耗时突然增加 30%,一看快照发现节点数从 10 变成了 8,那就不用纠结参数了。

5.2 用查询子集做快速回归

TPC-DS 完整跑一遍 99 个查询,在 1TB 规模下可能要几个小时。日常调参不需要每次都全跑,可以挑 10 个代表性查询组成子集,覆盖不同的执行计划特征:有的走 MapJoin,有的走 Common Join,有的产生大量 shuffle。我常用的子集是 query 3、query 7、query 26、query 42、query 55、query 68、query 72、query 88、query 96、query 99。这 10 个查询跑下来大约 20 分钟,足够判断参数改动是否有效。

# 快速回归脚本,只跑选定的查询 for q in 3 7 26 42 55 68 72 88 96 99; do start=$(date +%s) hive -f tpcds/run_query/query${q}.sql > /dev/null 2>&1 end=$(date +%s) echo "query${q},$((end - start))" >> quick_regression.csv done

跑完后把 CSV 导入表格,对比上次结果。如果某个查询耗时变化超过 15%,就重点排查该查询的执行计划。这种快速回归能让你在半小时内验证一个参数猜想,而不是等一整天。

5.3 结果验证:别只看耗时

耗时短不代表结果对。有一次我调了一个参数,查询快了 40%,后来发现是因为结果集被截断了。所以每次压测后,必须验证关键查询的结果行数和校验和。TPC-DS 官方提供了结果校验工具,但用起来麻烦。我的做法是:对每个查询,记录COUNT(*)和SUM()某个数值字段,和基线对比。如果数值不一致,说明查询逻辑或数据加载有问题,耗时再短也没有意义。

-- 以 query 3 为例,验证结果 SELECT COUNT(*) AS cnt, SUM(ss_ext_sales_price) AS total FROM store_sales WHERE ss_sold_date_sk BETWEEN 2451149 AND 2451179;

把这条 SQL 的结果和上次对比,如果cnt和total完全一致,才认为查询执行正确。这个习惯让我避免了好几次“假优化”的尴尬。

从那以后,我每次跑压测都强制走一遍环境快照、快速回归、结果校验这三步,哪怕再急也不跳过。希望这些经验能帮到你,少走点弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进