文章目录
9 模式设计
hive看上去与实际操作都像一个关系型数据库,但是事实上Hive是反模式。
9.1 按天划分的表
对于数据集增长很快的情况,可以使用这种方式,在表名中加入一个时间戳,例如upply_2020_05_20、upply_2020_05_21等。
当然对于hive,这种情况应该使用分区表。Hive通过WHERE子句来选择需要查询的分区。
9.2 关于分区
通过过滤分区可以优化Hive的查询,避免全表扫描。
每一个分区一般会对应着包含多个文件的文件夹,如果表使用了动态分区,可能会创建非常多分区这又可能会导致产生很多的小文件,最终导致NameNode挂掉。
HDFS文件系统NameNode必须将所有系统文件的元数据信息保存在内存中,每个文件的元数据信息大约150个字节。MapR和Amazon S3就没有这个限制。
MapReduce会将一个任务(job)转换成多个任务(task)。默认情况下,每个task都是一个新的JVM实例,都需要开启和销毁。对于小文件,每个文件都会对应一个task。在某些情况下,JVM开启和销毁的时间中销毁可能比实际处理数据的时间还长!所以必要的时候开启JVM重用,也是有效的调优方式。
理想情况下分区方案不应该导致产生太多的分区和文件夹目录,并且每个目录下的文件应该足够大,应该是文件系统中块打下的若干倍。
在设计分区表时,尽量考虑分区数量的增长是“均匀的”,而且每个分区下包含的文件大小至少是文件系统中块的大小至少是文件系统中块的大小或块大小的数倍。同时有必要考虑这种粒度级别在未来是否是适用的,必要的情况可以建立多级分区。例如一张按照时间和地区进行划分。
终极目标就是优化查询性能
9.3 唯一键和标准化
Hive没有主键或基于序列秘钥生成的自增键的概念。
在很多大数据场景,星型架构类型设计并非最优的。其主要原因是为了最小化磁盘寻道时间,我们可以避免标准化,例如传统数据库需要使用外键的关系的情况。
标准化:值数据库组织数据的过程。
原则:
- 根据设计规则创建表并建立表之间的关系
- 取消冗余度与不一致相关性
非标准化数据允许被扫描或写入到大的、连续的磁盘存储区域,从而优化磁盘驱动器的***I/O***性能。当然标准化数据可能会导致数据重复,更严重的场景是,导致数据不一直的风险。加大了数据管理的难度。
9.4 同一份数据多种处理
Hive本身提供了一个独特的语法,它可以从一个数据源产生多个数据聚合,而无需每次聚合都要重新扫描一次。对于大的数据输入集来说,这个优化可以节省非常可观的时间。具体示例如下:
hive> INSERT OVERWRITE TABLE sales
> SELECT * FROM history WHERE action='purchased';
hive> INSERT OVERWRITE TABLE sales
> SELECT * FROM history WHERE action='returned';
使用下面这种方式,只需要扫描history表一次即可:
hive> FROM history
> INSERT OVERWRITE sales SELECT * WHERE action='purchased'
> INSERT OVERWRITE credits SELECT * WHERE action='returned'
9.5 对于每个表的分区
很多ETL处理过程会涉及多个处理步骤,每个步骤又会产生一个或多个临时表,这些表仅供下一个job使用。对临时表进行分区也是很有必要的,比如某一天的数据有问题,就不用从数据源重跑所有数据,也不会因为下一次的任务而OVERWRITE临时表。同时运行两个实例,只要指定不同的分区即可。
一个更鲁棒性的处理方法就是整个过程中使用分区。这样就不会存在同步问题,同时也允许用户对中间数据按日期进行比较。缺点就是需要管理中间表,以及对旧的分区数据的删除。
9.6 分桶表数据存储
分区提供了一个隔离数据和优化查询的便利方式。不过并非数据集都能形成合理的分区,就像前面提到的动态分区场景。
分桶是将数据集分解成更容易管理的若干部分的另一个技术。
例如:
hive> CREATE TABLE weblog (user_id INT, url STRING, source_ip STRING)
> PARTITIONED BY (dt STRING)
> CLUSTERED BY (user_id) INTO 96 BUCKETS;
将user_id作为分桶字段,则会根据user_id的值进行哈希分发到桶中。分桶之后具体在使用的时候,有两种方式:
-- 设置:自动按照分桶表的bucket 进行分桶
SET hive.enforce.bucketing = true
-- 设置reduce task为96,并在插入语句中添加CLUSTER BY [filed_name]
SET mapred.reduce.tasks=96
警告
对于所以表的元数据,指定了分桶并不能保证表可以正确地填充。用户可根据以上示例来确保是否正确地填充了表。
分桶的几个优点:
- 因为桶的数量是固定的,所以它没有数据波动
- 方便分桶抽样
- 有利于执行高效的map-site JOIN
9.7 为表增加列
使用ALTER [table_name] ADD COLUMNS (field_name type)
命令,可以在表的末尾添加一个字段。之前缺失新添加的字段的数据,就补null。
实际生产中,随着时间的推移,很有可能会为底层数据增加一个新的字段。
9.8 使用列式存储
Hive通常使用行式存储,不过Hive也提供了一个列式SerDe来以混合列式格式存储信息。
9.8.1 重复数据
stat | uid | age |
---|---|---|
NY | Bob | 30 |
NJ | Sara | 40 |
NY | Peter | 14 |
NY | Sandra | 5 |
如果以上数据量很大,那么state字段与age字段这样的列会有很多重复数据。这种场景使用列式存储是非常适用的。例如RCFile采用游程编码,相同的数据不会重复存储,很大程度上节约了存储空间。
9.8.2 宽表
一般生产中只会取宽表的部分字段,使用列式存储,避免整行数据的读取与过滤。后续会详细介绍如何使用格式。
另外的:
列式存储由于相同列的字段类型相同,可以使用高压缩率的算法对文件进行压缩,而行式数据库由于一行各个字段类型可能不同,在压缩方式上不容易获得一个高的压缩比(也就是空间利用率不高)
列式数据库的每一列都可以作为索引,而行式数据库并非所有的字段都适合作为索引。
9.9 (几乎)总是使用压缩
几乎所有的场景都可以使用压缩,使磁盘上存储的数据量变小,这样可以通过降低I/O来提高查询执行速度。Hive可以无缝使用很多压缩类型。
唯一不使用压缩的理由是,数据要直接要提供给外部系统使用,或者使用非压缩格式(例如文本格式)是最最兼容的。
压缩和解压缩都会消耗CPU资源。MapReduce任务往往是I/O密集型的,因此CPU开销往往不是问题。当然对于工作流这样的CPU密集型的场景,例如一些机器学习算法,压缩实际上可能会从更多必要的操作中获取宝贵的CPU资源,从而降低性能。