深入学习《Programing Hive》:模式设计(Schema Design)

        对那些使用传统数据库的用户来说,Hive看上去和使用上都很向传统的关系型数据库:有相似的命名法,和传统SQL特别是MySQL方言很相似的HiveQL方言,然而Hive在实现上和使用上都和之前的传统数据库有很大的不同。通常情况下,用户尝试使用关系数据库世界的范式,实际上是Hive反模式。
        本节我们重点介绍一些用户可能已使用过的模式和尽量要避免的反模式。

        Table-by-Day
        Table-by-Day是一个被定义为 “表名 + 日期”的Hive表的模式,比如supply_2013-05-19、supply_2013-05-20等。 Table-by-Day在数据库世界中是一个反模式,但是面对日益增长的数据集,它仍然被广泛使用:
              hive> CREATE TABLE supply_2013_05_18(ind INT,part STRING,quantity INT);
              hive> CREATE TABLE supply_2013_05_19(ind INT,part STRING,quantity INT);
              hive> CREATE TABLE supply_2013_05_20(ind INT,part STRING,quantity INT);

              hive> ... load data ...

              hive> SELECT part,quantity FROM supply_2013_05_18
                  > UNION ALL
                  > SELECT part,quantity FROM supply_2013_05_19
                  > WHERE quantity < 4;
        

        这种情况下,应该使用分区表来替换这种Table-by-Day的表设计,通过日期来将数据分区,然后在查询时指明只查询的具体分区中的数据,这种查询不仅更高效,而且HiveQL语句看上去简洁又情绪:
              hive> CREATE TABLE supply(ind INT,part STRING,quantity INT)
                  > PARTITIONED BY (day INT);
              hive> ALTER TABLE supply ADD PARTITION (day=20130518);
              hive> ALTER TABLE supply ADD PARTITION (day=20130519);
              hive> ALTER TABLE supply ADD PARTITION (day=20130520);

              hive> ... load data ...
    
              hive> SELECT part,quantity FROM supply
                  > WHERE day >= 20130518 AND day < 20130520 AND quantity < 4;   
        

         
        过度分区(Over Partitioning)
        在Hive中,表的分区功能非常有用,因为在通常情况下Hive执行HiveQL查询时是全表扫描的(这里我们先不考虑Hive索引)。然而,如果创建了太多的分区,虽然会优化某些查询,但也会对其他查询不利:
              hive> CREATE TABLE weblogs(url STRING,time long)
                  > PARTITIONED BY(day INT,state STRING,city STRING);

              hive> SELECT * FROM weblogs WHERE day=20130519;
        

        Hadoop HDFS被设计为适合存储数以百万计的大文件,而不是数十亿的小文件。首先,创建太多的跟去也就意味着大量的文件目录和更多的文件是不必要的,因为一个分区目录中通常有很多的文件。如果一个分区表包含上千的分区并且每天都新增很多数据的话,那么这个表就有可能会有每天上千个分区和上万个数据文件,然后每年...,最终会突破Hadoop NameNode的最大内存限制。
        我们知道,HiveQL语句最终被编译为MapReduce程序,被提交到Hadoop上作为job作业执行,然后JobTracker会分解成许多的task,在没有设置JVM重用的情况下,每一个task都会启用一个新的JVM进程,这会产生巨大的JVM的初始化和销毁的开销,从而增大整个作业执行时间。
        因此,一个理想的分区模式应该不会导致太多的分区和目录,每个目录中的文件应该要大——最好是HDFS block块大小的倍数。
        对一个有时间范围分区要求的表,一个比较好的策略就是根据不同的时间段内数据增长的近似大小,燃气分区和数据随着时间“温和”的增长,并且“保证”数据文件的的生成时间顺序和大小大致为HDFS block块大小的数倍。这会保证分区大而均衡,从而达到对一般情况下查询的优化:
              hive> CREATE TABLE weblogs(url STRING,time LONG,state STRING,city STRING)  
              hive> PARTITIONED BY(day INT);

              hive> SELECT * FROM weblogs WHERE day=20130519;
        

        另一个解决方案就是使用两个不同级别的分区。比如,第一级分区是日期,第二级分区可以是一个地理上的地区如:
              hive> CREATE TABLE (url STRING,time LONG,city STRING)
                  >  PARTITIONED BY(day INT,state STRING);
            
              hive> SELECT * FROM weblogs WHERE day=20130519;
        


        唯一键和规范化(Unique Keys and Normalization)
        Hive不像传统的关系型数据库中通常使用唯一键、索引和规范化的数据,它没有主键以及自增长主键的概念,在Jion连接中应尽量避免非规范化的数据。对于Hive的复杂数据类型如Array、Map和Struct,这些一对多的数据可以被保存在Hive的一行中,这并不是说这些复杂类型不应该被使用,但是这种星型结构的设计并非最佳。
        在Hive中使用复杂数据类型设计(避免正规化)的主要原因是尽量减少磁盘的查找时间。

        Making Multiple Passes Over the Same Data
        实在不知道 Making Multiple Passes Over the Same Data怎么翻译,大致意识就是在从一个数据源表中向多个表中倒数据时,最好只扫描一次数据源而将数据导入多个不同的表中。
        比如下例中,每一行都是从数据源表history中倒数据:
              hive> INERT OVERWRITE TABLE sales 
                  > SELECT * FROM history WHERE action='purchased';
              hive> INERT OVERWRITE TABLE credits
                  > SELECT * FROM history WHERE action='returned';
        

        上例中的HiveQLs语句语法是正确的,但是低效,因为每个HiveQL语句都会执行一次全表扫描,最好将上例中的两个HiveQL语句合并成一个HiveQL:
              hive> FROM history
                  > INSERT OVERWRITE sales SELECT * WHERE action='purchased'
                  > INSERT OVERWRITE credits SELECT * WHERE action='returned';
        

        这种写法只会执行一次全表扫描,高效。

        使用桶表做数据存储(Bucketing Table Data Storage)
        Hive的分区功能提供了一种方便的方式来区分数据和优化查询。然而,并不是所有的数据都可以做合理的分区的,特别是在分区中数据不均衡的情况下更是如此。
        将数据分桶是另一项奖大数据集分解成多个便于管理的文件的技术。例如,假设有一个使用日期dt作为一级分区和user_id作为二级分区的表,这种设计会导致很多小的分区,想象一下如果您动态创建这些分区,(默认情况下)Hive会限制创建最大的动态分区的数量,太多的分区有可能会突破NameNode物理内存的最大限制和其他问题,所以线面的HiveQL执行可能失败:
              hive> CREATE TABLE weblog(url STRING,source_ip STRING)
                  > PARTITIONED BY(dt STRING,user_id STRING);

              hive> FROM raw_weblog
                  > INSERT OVERWRITE TABLE weblog PARTITION(dt='2012-06-08',user_id)
                  >SELECT url,source_ip,dt,user_id;
        

        我们可以将上例的二级分区表改为桶表:
              hive> CREATE TABLE weblog(user_id SRTING,url STRING,source_ip STRING)
              hive> PARTITIONED BY(dt STRING)
              hive> CLUSTERED BY(user_id) INTO 98 BUCKETS;

              hive> SET hive.enforce.bucketing=true;
              hive> FROM raw_logs
                  > INSERT OVERWRITE TABLE weblog
                  > PARTITION(dt='2012-08-28')
                  > SELECT user_id,url,source_ip WHERE dt='2012-08-28';
        

        桶表还有一个比较有用的功能,那就是数据取样(Sampling)。

        使用列表
        见 深入学习《Programing Hive》:RCFile

        (Almost) Always use Compresssion

猜你喜欢

转载自flyingdutchman.iteye.com/blog/1871849