记录一次Hive表清理过程
背景
时间:2020-07-17
在用spark+hive做数仓的过程中往往会产生很多表,过多历史表会很快消耗掉有限的hdfs资源,并且时间过于久远的表一般不会具有利用价值了,如果不及时清理这部分hive表会造成hdfs资源的严重浪费,因此需要有一个类似于HiveClean的定时任务,执行定期清理旧数据的逻辑。
动机
在接手这个功能需求的时候,已经有了一个HiveClean的版本,基本的清理逻辑如下:
- 遍历所有需要清理的hive表
- 执行
ALTER TABLE $cluster%s.$table%s SET TBLPROPERTIES('external'='false')
来把hive外部表转换成hive内部表
ps:如果直接删除外部表,只会从hive metastore中删除外部表的元数据,而对应的物理文件依然存放在hdfs中,没有达成我们的目的
- 筛选出需要删除的表分区,执行
ALTER TABLE $cluster%s.$table%s DROP PARTITION (time_hour='xxx')
来删除对应的分区
现在的需求是改进这个逻辑:注意上面的最后一步,删除分区的方式是一个一个删除,想看看能不能用范围删除的方式来清理旧分区
问题
- 一开始了解到hive是原生支持分区范围删除的:
ALTER TABLE $cluster%s.$table%s DROP PARTITION (time_hour<'xxx')
,但是事情并没有那么简单:在hive执行没有问题,但是Spark SQL引擎并不支持
spark-sql> ALTER TABLE xxx DROP PARTITION (time_hour<2020060110); Error in query: mismatched input '<' expecting {')', ','}(line 1, pos 54)
== SQL == ALTER TABLE xxx DROP PARTITION (time_hour<2020060110) ------------------------------------------------------^^^
spark-sql>
这是Spark SQL自身的局限性
- 虽然这个想法不可行,但是在研究的过程中发现了HiveClean逻辑的错误,问题复现如下:
- 按照参数的设置,HiveClean定时任务会在每天的下午某个时间点执行,并且清理的是45天以前的旧分区
- 但是遍历HDFS文件目录可以发现,有一些Hive表45天前的分区中的物理文件虽然不存在了,但是分区目录还在;还有另一些Hive表干脆连45天前的物理文件还完好无损(后来分析猜测是有些表手工设置为了内部表,而后来新增的表没有设置)
- 在hive中执行
SHOW PARTITIONS xxx
时显示的分区确实只有45天以内的(从后面的分析可以知道这是删除外部表的结果,元数据删除了但是物理文件还在)
- 从上面的问题现象中可以很自然地想到可能是Hive内部表和外部表的原因
- 执行
DESC FORMATTED xxx
可以查看Hive表的属性,例如:shell ... ... Type MANAGED Provider hive ... ... Location hdfs://hadoop-hdfs-nn/topics/cluster/xxx ... ...
上面显示的Type MANAGED
表明Hive表示内部表,如果显示的是Type EXTERNAL
则说明是外部表 - 在执行上述的命令查看Hive表属性的时候发现是外部表,但是奇怪的地方就在于每次HiveClean执行的时候,都会先通过
ALTER TABLE $cluster%s.$table%s SET TBLPROPERTIES('external'='false')
将外部表转换成内部表,然后再执行分区删除逻辑,理论上执行一次之后,Hive表就应该是内部表了,而现在来看却不是这样,问题出在了哪? - 在Stack Overflow上面找到了这样的回答:
,有两个值得关注的回答:
-
- 关于上面外部表转换语句无效的解释
重点在于下面的评论,先看回答中给出的方案,显然跟我们现有的HiveClean中的一样,说是大小写敏感的问题,但是这是网上流传甚广的误导方案,正确的设置语句应该是大写的:ALTER TABLE $cluster%s.$table%s SET TBLPROPERTIES('EXTERNAL'='FALSE')
,但是如果在spark中执行的话会报错(hive中当然一点问题都没有):Error in query: Cannot set or change the preserved property key: 'EXTERNAL';
现在来看答案下面的解释,简单来说就是EXTERNAL是保留字并且大小写敏感,如果设置的是external那么只会给Hive表新增一条自定义属性'external',EXTERNAL依然没有改变(即内外部表的属性没变)
既然改变EXTERNAL的值才行,但Spark中又没法执行,那怎么办呢?总不能手工把所有的表全部设置为内部表吧,万一将来新增了新的表忘记了设置为内部表,又或者希望将内部表转成外部表,每次都手工设置显得很不优雅,有什么好方法吗?别急,也在这个SO的回答中,看下面
-
- Spark中外部表转换内部表的正确姿势
这个无需多说,无脑照抄就行,亲测有效
- 现在还有点小问题,就是一些表被当做外部表删除了,metastore中已经没了45天以前的元数据,因此在后面的分区删除任务中这些分区依然没法被删除,这时候就需要把这些表的分区修复回来,然后在下一次HiveClean定时任务执行时统一把远古分区给删掉
Hive分区修复命令MSCK用法:MSCK REPAIR TABLE xxx
,可以参考这篇文章了解一下MSCK命令:
后续
但是现在在Spark中按范围删除Hive表分区的问题依然没有解决,反倒歪打正着修复了存在已久的bug。。。