Hive 动态分区一次写了上千个分区就报错
某制造企业数仓里有一张设备运行记录明细表,按天分区,每天凌晨跑增量写入一直很稳。上周业务方要求回补过去一个月的历史数据,开发同学把日期范围一放宽,改成一次 INSERT 写 30 天的分区,结果作业跑了十几分钟后直接失败,日志里只有一行看着很陌生的报错:
org.apache.hadoop.hive.ql.metadata.HiveFatalException: [Error 20004]:
Fatal error occurred when node tried to create too many dynamic partitions.
The maximum number of dynamic partitions is controlled by
hive.exec.max.dynamic.partitions and hive.exec.max.dynamic.partitions.pernode.
Maximum was set to: 100
SQL 一个字没改,昨天还好好的,今天只是多写了几天数据就炸了。问题不在 SQL,在 Hive 动态分区的两个默认上限上。
一、报错拆开看:撞的是哪一道上限
Hive 给动态分区设了两道上限,默认值都写在官方文档里:
hive.exec.max.dynamic.partitions.pernode,默认值 100:单个 mapper 或 reducer 最多创建的动态分区数。hive.exec.max.dynamic.partitions,默认值 1000:一次 DML 总共最多创建的动态分区数。
两道上限的触发时机不一样。单个节点上创建的分区数超过 100,节点直接抛致命错误,整个作业被杀掉,报错里 Maximum was set to: 100 指的就是这一道;如果每个节点都没超 100,但所有节点加起来超过 1000,作业会在最后移动数据之前抛异常。上面那次回补,30 天 × 多条产线的组合早就超过了单节点的 100,所以先撞上的是第一道。
还有一个容易忽略的默认值:hive.exec.dynamic.partition.mode 默认是 strict,严格模式要求 INSERT 时至少指定一个静态分区列。写全动态分区之前,必须先把它设成 nonstrict,否则连跑都跑不起来。
二、为什么偏偏是这张表撞上
动态分区插入时,mapper 拿到的是随机分片的数据。一行数据属于哪个分区,要到写文件时才知道,一个 mapper 手里可能攥着几十个不同分区的数据。分区字段的基数越高,单个 mapper 撞上 100 上限的概率越大。
这张明细表按 dt 分区,一天一个分区,回补 30 天本来只有 30 个分区,怎么会超 100?因为这张表其实是两级分区:PARTITION(dt, line_code),日期乘上十几条产线,30 天下来就是三四百个分区组合,分到同一个 mapper 上的不同组合轻轻松松超过 100。单日增量写入时只有十几个组合,从来碰不到上限,所以一直没暴露。
三、修法一:先把上限调到够用
确认分区设计本身没问题、只是批量回补这一次分区数多,那就直接调大两个上限。注意两个要一起调,只调总量不调单节点,照样会在 100 这里被拦住:
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
SET hive.exec.max.dynamic.partitions=5000;
SET hive.exec.max.dynamic.partitions.pernode=500;
INSERT OVERWRITE TABLE dwd_device_runtime PARTITION(dt, line_code)
SELECT device_id, run_minutes, energy_kwh, dt, line_code
FROM ods_device_runtime
WHERE dt BETWEEN '2026-10-12' AND '2026-11-10';
调多大合适没有标准答案,按实际分区组合数留两三倍余量即可。这次回补大约 400 个组合,总量设 5000、单节点设 500 绰绰有余。调完之后作业一次跑通。
四、修法二:让同一个分区的数据落到同一个 reducer
只调参数治标不治本。更彻底的做法是在 SQL 里加一句 DISTRIBUTE BY,按分区字段把数据先分发到同一个 reducer 再写,这样每个 reducer 只负责少数几个分区,从根上降低单节点创建的分区数:
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
INSERT OVERWRITE TABLE dwd_device_runtime PARTITION(dt, line_code)
SELECT device_id, run_minutes, energy_kwh, dt, line_code
FROM ods_device_runtime
WHERE dt BETWEEN '2026-10-12' AND '2026-11-10'
DISTRIBUTE BY dt, line_code;
这是 Hive 官方教程里推荐的做法:mapper 随机拿数据时不同分区的行混在一起,分区数很容易超;先按分区字段分发,写分区时每个节点手里的分区数就大幅下降。日常的增量作业也可以顺手加上,基本没有额外成本。
五、别只调上限:分区字段选错了,调多大都没用
如果撞上限不是因为回补,而是日常作业天天报,那就别再调参数了,先检查分区字段本身。用设备编号、订单号这种高基数字段做分区字段,一天就能产生几万个分区,上限调到 10 万也撑不住,还会拖垮元数据库和 NameNode。
分区字段应该选低基数、查询时常用来过滤的字段,比如日期、产线、车间。真需要按设备维度分析时,把设备编号放进表里做普通列,查询时用 WHERE 过滤,而不是把它变成一级分区。
六、顺带防一手:小文件还有第三道上限
动态分区写多了,经常顺带踩到第三个默认值:hive.exec.max.created.files,默认值 100000,一次作业所有 mapper 和 reducer 创建的文件总数上限。分区数一多,每个分区再写几个小文件,很容易一起撞线。回补跑完之后可以顺手查一下目标分区的文件数,单个分区几十个小文件就该考虑在写入后做一次合并,或者在 SQL 里控制好每个分区的数据量,别让动态分区一次写出满天小文件。
小结
Hive 动态分区报错 [Error 20004],先别急着改 SQL,对照两个默认值查:单节点上限 hive.exec.max.dynamic.partitions.pernode 默认 100,总量上限 hive.exec.max.dynamic.partitions 默认 1000。一次回补分区数变多,先把两个上限一起调大;想长期稳妥,就在 SQL 里加 DISTRIBUTE BY 分区字段,让同一个分区的数据落到同一个 reducer;如果日常作业也天天撞上限,问题多半出在分区字段选了高基数字段,改分区设计比调参数管用。
- 点赞
- 收藏
- 关注作者
评论(0)