hive任务优化经验总结

一、读数据

1.1 通过调整maptask摄入数据量来减少task个数

我们在一次做检索需求的时候,需要查近30天的数据,发现任务起来4w多个task,但是从spark ui上看,每个task摄入的数据量的表现是行数不多且record size不大,产生这种现象的原因是spark或者mr在切分split的时候,都是依据文件大小切分的,文件中最大的是kv字段,占了存储的80%以上,但是我们在查询的时候并没有查kv,所以优化思路是手动指定maptask的数据数据量为1g或者2g。

hive on spark参数:
set mapreduce.input.fileinputformat.split.maxsize = 67108864;
set mapreduce.input.fileinputformat.split.minsize = 10485760;

spark sql参数:
set spark.hadoop.mapred.min.split.size = 268435456;
set spark.hadoop.mapred.max.split.size = 536870912;

1.2 利用min / max索引

在列式存储文件orc和parquet中,都会记录每一个字段的min / max索引,那么我们就可以在写数据的时候,就把经常查询的字段排序,这样在查数据的时候,就可以先查文件头中的min / max来判断这个字段是否在这个范围内,如果不在就不需要扫描文件内容了。

但是这种只能做到在读文件的时候少读入数据,并不能做到在切分task的时候,就按照符合min / max范围的切分,所以如果底层数据量大,依然会启动很多task。这个问题在数据湖hudi/iceberg中得到了解决,数据湖将每个文件的min / max以及文件大小都存在了元数据表里,这种在生成task的时候,可以先读元数据表来匹配出要读哪些文件,然后再根据这些文件的大小生成task。

二、计算

2.1 数据倾斜

数据倾斜可以参考这里:数据倾斜总结

2.2 内存优化

通过指定一个excutor上运行多个cores,多加一个cores,就可以减少Spark程序本身固定的300M开销。

2.3 避免重复读表

  1. 有些时候为了省事让逻辑看起来简单,在计算不同指标的时候,会多次从一个表里面查数,然后计算完做union操作,这种是很浪费性能的,我们可以使用grouping sets、cube等聚合手段来产出不同聚合维度的指标。

2.4 避免重复解析相同字段

  1. hive中提供了get_json_obejct方法,可以让我们从json中解析出自己想要的字段,但是如果一个json中有很多我们想要的字段,这个时候就要多次调用这个方法,这个方法的实现就是首先把json解析成map,然后再从map中获取key,由字符串到map的过程很浪费性能,所以我们可以先用一个udf把json解析成map,然后在后面直接查这个map就可以了。

三、存储

3.1 选用合适的文件格式

  1. orc和parquet是我们最常用的两种列式存储格式,在生产中经过对比发现orc压缩性能更好,它占用的存储一般是parquet的1/3,但是相对应的,在做查询的时候,也会比parquet慢。所以我们需要在存储与查询效率之间做折中,如果表是经常被用于即席查询的,那可能用parquet更合适,其他情况下用orc更合适。

3.2 充分利用列式存储的压缩方式来提升压缩率

  1. 我们都知道,把相同的数据放到一起可以提升压缩率,所以一般会在写入数据的时候加一个排序,那么问题来了,我们如果写入的有多列数据,该按照哪一列做排序呢?

    一个很自然的想法就是字符数最多的那一列做排序,比如我们有一个表中存了 (cuid, func_name, action_time) 这几个字段,func_time是中文,可能会有50个字符,而cuid只有30个字符,这个时候很自然的想法是我按照func_name排序,压缩性能是不是更好呢?这个想法是错误的

    在orc文件中,存储字符串的时候是按照字典编码的,下面简单介绍下字典编码:

    字典编码(解决重复字符串的问题):

    记录三个流:

    DATA,DICTIONARY_DATA,LENGTH

    如 [“Nevada”, “California”, “Nevada”, “California”, and “Florida”]

    DICTIONARY_DATA 是 CaliforniaFloridaNevada”

    LENGTH 是 [10, 7, 6]

    DATA 是 [2, 0, 2, 0, 1]

    2,0,1都是是代表字典中的位置

    回到我们(cuid, func_name, action_time)的场景,我们的数据里,func_name一般是有限可枚举的,所以它在存到字典中以后占用会非常小,这个时候,我们是否按照它排序其实对于存储的影响并不大。相反,cuid是不可枚举的,我们尽量把相同的cuid放到orc文件中的一个row_group中或者parquet的一个page中(orc和parquet组织数据的最小单元),可以提高字典的压缩效率。

  2. 如果有kv结构,那么kv一般都是表中占用存储最大的列,但是直接按照kv排序不太好,可以按照cuid+log_timestamp做排序,因为相同的cuid在相近的时间,kv里的公参大部分都是一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值