Hive 配置动态分区

insert into table xxx partition(xxxx)
select ...

使用动态分区时首先需要的一些配置:

  • 是否开启动态分区 hive.exec.dynamic.partition
  • 动态分区是否使用严格模式 hive.exec.dynamic.partition.mode
  • MR总共可创建最大分区数 hive.exec.max.dynamic.partition.partitions(默认1000)
  • 当前节点可创建的最大分区数 hive.exec.max.dynamic.partition.partitions.pernode(默认100)

动态分区产生大量小文件

我这里分区表的存储格式是Parquet,insert select方式动态插入动态分区中,按天分区数据量非常大,一天几十G的文件
因为数据量大,而且分区数多,就会起大量map,从而产生大量小文件(map数x分区数)。小文件过多不仅影响查询,NameNode中的元数据信息管理也会受很大影响。

  • 参考:
insert into table xxx partition(pcol)
select ...
from ...
distribute by(pcol)

这样做分区数决定了reduce数,reduce数也决定了文件数,虽然可以减少文件数,但会导致数据倾斜的产生

insert into table xxx partition(pcol)
select ...
from ...
distribute by rand()

通过rand()结果hash编码后对reducer数取余,可以把数据均分到reducer上去。数据量很大的话能保证每个reduce上都有和pcol分区数相等的文件数。所以文件数可以由reducer个数限制,hive.exec.reducers.max

顺便提一句,distribute by rand()也可以用来随机抽样,因为随机分发到reducers上去了,也就等同于随机抽取了

我尝试这样做了后,又出现了OOM错误,google了一番找到了下面这个配置参数

  • hive.optimize.sort.dynamic.partition

官网解释是:When enabled, dynamic partitioning column will be globally sorted.
This way we can keep only one record writer open for each partition value in the reducer thereby reducing the memory pressure on reducers.

Hive 0.13加入并默认true,Hive 0.14后默认为false,如果为true的话,这个参数可以使得每个分区只产生一个文件,可以解决动态分区时的OOM问题,但会严重降低reduce处理并写入一个分区的速度。
这里设置为true后成功了。但这个参数的后果就是慢,这篇文章有谈及要综合业务考虑是否开启:https://hdinsight.github.io/hive/hive-slow-reducer.html

动态分区插入时OOM问题的产生

还是上面 hive.optimize.sort.dynamic.partition这个参数。产生OOM问题的原因是同时打开了太多写分区的record writer同时写入文件,开启该参数的话,分区列会全局排序,使得reduce端每个分区只有一个文件写入,降低reduce的内存压力。

具体OOM的原因我在这里看到了:https://cloud.tencent.com/developer/article/1079007
Parquet和ORC是列式批处理文件格式。这些格式要求在写入文件之前将批次的行(batches of rows)缓存在内存中。在执行INSERT语句时,动态分区目前的实现是:至少为每个动态分区目录打开一个文件写入器(file writer)。由于这些缓冲区是按分区维护的,因此在运行时所需的内存量随着分区数量的增加而增加。所以经常会导致mappers或reducers的OOM,具体取决于打开的文件写入器(file writer)的数量。

通过INSERT语句插入数据到动态分区表中,也可能会超过HDFS同时打开文件数的限制。
如果没有join或聚合,INSERT … SELECT语句会被转换为只有map任务的作业。mapper任务会读取输入记录然后将它们发送到目标分区目录。在这种情况下,每个mapper必须为遇到的每个动态分区创建一个新的文件写入器(file writer)。mapper在运行时所需的内存量随着它遇到的分区数量的增加而增加。

https://community.hortonworks.com/content/supportkb/171090/hive-query-with-dynamic-partition-takes-long-time.htmlhttps://community.hortonworks.com/articles/89522/hive-insert-to-dynamic-partition-query-generating.html

要是还报OOM问题,进一步可以考虑调整map、reduce的内存大小, mapreduce.map.memory.mbmapreduce.reduce.memory.mb参数

对于这个异常,我们建议有以下三种方式来处理:

  1. 启用hive.optimize.sort.dynamic.partition,将其设置为true。通过这个优化,这个只有map任务的mapreduce会引入reduce过程,这样动态分区的那个字段比如日期在传到reducer时会被排序。由于分区字段是排序的,因此每个reducer只需要保持一个文件写入器(file writer)随时处于打开状态,在收到来自特定分区的所有行后,关闭记录写入器(record writer),从而减小内存压力。这种优化方式在写parquet文件时使用的内存要相对少一些,但代价是要对分区字段进行排序。
  2. 第二种方式就是增加每个mapper的内存分配,即增大mapreduce.map.memory.mb和mapreduce.map.java.opts,这样所有文件写入器(filewriter)缓冲区对应的内存会更充沛。
  3. 将查询分解为几个较小的查询,以减少每个查询创建的分区数量。这样可以让每个mapper打开较少的文件写入器(file writer)。

备注:

默认情况下,Hive为每个打开的Parquet文件缓冲区(file buffer)分配128MB。这个buffer大小由参数parquet.block.size控制。为获得最佳性能,parquet的buffer size需要与HDFS的block size保持对齐(比如相等),从而使每个parquet文件在单个HDFS的块中,以便每个I/O请求都可以读取整个数据文件,而无需通过网络传输访问后续的block。

Hive 控制文件数过多的一些参数

这个是搜索上面遇到的问题时遇见的,觉得有价值,也谈及了上面提到的distribute by,所以再次记录下

参考:https://www.jianshu.com/p/e76ddc4b19c5

设置 mapper输入文件合并的参数

set mapred.max.split.size=256000000;  #每个Map最大输入大小
set mapred.min.split.size.per.node=100000000; #一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000; #一个交换机下split的至少的大小(这个值决定了该机架下的文件是否需要合并)
set hive.input.format=org.apache.Hadoop.hive.ql.io.CombineHiveInputFormat;  # 执行Map前进行小文件合并

在开启了org.apache.hadoop.hive.ql.io.CombineHiveInputFormat后,一个data node节点上多个小文件会进行合并,合并文件数由mapred.max.split.size限制的大小决定。
mapred.min.split.size.per.node决定了多个DataNode上的文件是否需要合并
mapred.min.split.size.per.rack决定了多个交换机上的文件是否需要合并

设置 map输出和reduce输出进行合并的参数

hive.merge.mapfiles= true    #设置 map输出和reduce输出进行合并的相关参数
hive.merge.mapredfiles= true 设置reduce端输出进行合并,默认为false
hive.merge.size.per.task= 256 *1000 * 1000  设置合并文件的大小
hive.merge.smallfiles.avgsize=16000000   输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge