标签:style blog http color os 使用 io ar for
一个Hive查询生成多个map reduce job,一个map reduce job又有map,reduce,spill,shuffle,sort等多个阶段,所以针对hive查询的优化可以大致分为针对MR中单个步骤的优化(其中又会有细分),针对MR全局的优化,和针对整个查询(多MR job)的优化,下文会分别阐述。
在开始之前,先把MR的流程图帖出来(摘自Hadoop权威指南),方便后面对照。另外要说明的是,这个优化只是针对Hive 0.9版本,而不是后来Hortonwork发起Stinger项目之后的版本。相对应的Hadoop版本是1.x而非2.x。
Map阶段的优化,主要是确定合适的map数。那么首先要了解map数的计算公式:
num_map_tasks = max[${mapred.min.split.size}, min(${dfs.block.size}, ${mapred.max.split.size})]
hive> set dfs.block.size; dfs.block.size is undefined所以实际上只有mapred.min.split.size和mapred.max.split.size这两个参数(本节内容后面就以min和max指代这两个参数)来决定map数量。在hive中min的默认值是1B,max的默认值是256MB:
hive> set mapred.min.split.size; mapred.min.split.size=1 hive> set mapred.max.split.size; mapred.max.split.size=256000000所以如果不做修改的话,就是1个map task处理256MB数据,我们就以调整max为主。通过调整max可以起到调整map数的作用,减小max可以增加map数,增大max可以减少map数。需要提醒的是,直接调整mapred.map.tasks这个参数是没有效果的。
num_reduce_tasks = min[${hive.exec.reducers.max}, (${input.size} / ${ hive.exec.reducers.bytes.per.reducer})]也就是说,根据输入的数据量大小来决定reduce的个数,默认hive.exec.reducers.bytes.per.reducer为1G,而且reduce个数不能超过一个上限参数值,这个参数的默认取值为999。所以我们可以调整hive.exec.reducers.bytes.per.reducer来设置reduce个数。
create table rc_file_test( col int ) stored as rcfile; set hive.exec.compress.output = true; insert overwrite table rc_file_test select * from source_table;另外时也可以指定输出格式,也可以通过hive.default.fileformat来设定输出格式,适用于create table as select的情况:
set hive.default.fileformat = SequenceFile; set hive.exec.compress.output = true; /*对于sequencefile,有record和block两种压缩方式可选,block压缩比更高*/ set mapred.output.compression.type = BLOCK; create table seq_file_test as select * from source_table;上面的文件格式转换,其实是由hive完成的(也就是插入动作)。对于textfile和sequence file,其实也可以在外部生成好对应的文件,然后导入hive表。而RCfile 据说 是不支持外部生成后导入的,没有亲自试过。
set mapred.job.reuse.jvm.num.tasks = 5;他的作用是让一个jvm运行多次任务之后再退出。这样一来也能节约不少JVM启动时间。
/*在index_test_table表的id字段上创建索引*/ create index idx on table index_test_table(id) as ‘org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler‘ with deferred rebuild; alter index idx on index_test_table rebuild; /*索引的剪裁。找到上面建的索引表,根据你最终要用的查询条件剪裁一下。*/ /*如果你想跟RDBMS一样建完索引就用,那是不行的,会直接报错,这也是其麻烦的地方*/ create table my_index as select _bucketname, `_offsets` from default__index_test_table_idx__ where id = 10; /*现在可以用索引了,注意最终查询条件跟上面的剪裁条件一致*/ set hive.index.compact.file = /user/hive/warehouse/my_index; set hive.input.format = org.apache.hadoop.hive.ql.index.compact.HiveCompactIndexInputFormat; select count(*) from index_test_table where id = 10;
create table map_join_test(id int) clustered by (id) sorted by (id) into 32 buckets stored as textfile;然后插入我们准备好的800万行数据,注意要强制划分成bucket(也就是用reduce划分hash值相同的数据到相同的文件):
set hive.enforce.bucketing = true; insert overwrite table map_join_test select * from map_join_source_data;这样这个表就有了800万id值(且里面没有重复值,所以可以做sort merge),占用80MB左右。
select /*+mapjoin(a) */count(*) from map_join_test a join map_join_test b on a.id = b.id;然后就会看到分发hash table的过程:
2013-08-31 09:08:43 Starting to launch local task to process map join; maximum memory = 1004929024 2013-08-31 09:08:45 Processing rows: 200000 Hashtable size: 199999 Memory usage: 38823016 rate: 0.039 2013-08-31 09:08:46 Processing rows: 300000 Hashtable size: 299999 Memory usage: 56166968 rate: 0.056 …… 2013-08-31 09:12:39 Processing rows: 4900000 Hashtable size: 4899999 Memory usage: 896968104 rate: 0.893 2013-08-31 09:12:47 Processing rows: 5000000 Hashtable size: 4999999 Memory usage: 922733048 rate: 0.918 Execution failed with exit status: 2 Obtaining error information Task failed! Task ID: Stage-4不幸的是,居然内存不够了,直接做map join失败了。但是80MB的大小为何用1G的heap size都放不下?观察整个过程就会发现,平均一条记录需要用到200字节的存储空间,这个overhead太大了,对于map join的小表size一定要好好评估,如果有几十万记录数就要小心了。虽然不太清楚其中的构造原理,但是在互联网上也能找到其他的例证,比如 这里 和 这里 ,平均一行500字节左右。这个明显比一般的表一行占用的数据量要大。不过hive也在做这方面的改进,争取缩小hash table,比如 HIVE-6430 。
set hive.optimize.bucketmapjoin = true;然后还是会看到hash table分发:
2013-08-31 09:20:39 Starting to launch local task to process map join; maximum memory = 1004929024 2013-08-31 09:20:41 Processing rows: 200000 Hashtable size: 199999 Memory usage: 38844832 rate: 0.039 2013-08-31 09:20:42 Processing rows: 275567 Hashtable size: 275567 Memory usage: 51873632 rate: 0.052 2013-08-31 09:20:42 Dump the hashtable into file: file:/tmp/hadoop/hive_2013-08-31_21-20-37_444_1135806892100127714/-local-10003/HashTable-Stage-1/MapJoin-a-10-000000_0.hashtable 2013-08-31 09:20:46 Upload 1 File to: file:/tmp/hadoop/hive_2013-08-31_21-20-37_444_1135806892100127714/-local-10003/HashTable-Stage-1/MapJoin-a-10-000000_0.hashtable File size: 11022975 2013-08-31 09:20:47 Processing rows: 300000 Hashtable size: 24432 Memory usage: 8470976 rate: 0.008 2013-08-31 09:20:47 Processing rows: 400000 Hashtable size: 124432 Memory usage: 25368080 rate: 0.025 2013-08-31 09:20:48 Processing rows: 500000 Hashtable size: 224432 Memory usage: 42968080 rate: 0.043 2013-08-31 09:20:49 Processing rows: 551527 Hashtable size: 275960 Memory usage: 52022488 rate: 0.052 2013-08-31 09:20:49 Dump the hashtable into file: file:/tmp/hadoop/hive_2013-08-31_21-20-37_444_1135806892100127714/-local-10003/HashTable-Stage-1/MapJoin-a-10-000001_0.hashtable ……这次就会看到每次构建完一个hash table(也就是所对应的对应一个bucket),会把这个hash table写入文件,重新构建新的hash table。这样一来由于每个hash table的量比较小,也就不会有内存不足的问题,整个sql也能成功运行。不过光光是这个复制动作就要花去3分半的时间,所以如果整个job本来就花不了多少时间的,那这个时间就不可小视。
set hive.optimize.bucketmapjoin.sortedmerge = true; set hive.input.format = org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;sort merge bucket map join是不会产生hash table复制的步骤的,直接开始做实际map端join操作了,数据在join的时候边做边读。跳过复制的步骤,外加join算法的改进,使得sort merge bucket map join的效率要明显好于bucket map join。
还是拿网站的访问日志说事吧。假设网站访问日志中会记录用户的user_id,并且对于注册用户使用其用户表的user_id,对于非注册用户使用一个user_id=0代表。那么鉴于大多数用户是非注册用户(只看不写),所以user_id=0占据了绝大多数。而如果进行计算的时候如果以user_id作为group by的维度或者是join key,那么个别reduce会收到比其他reduce多得多的数据——因为它要接收所有user_id=0的记录进行处理,使得其处理效果会非常差,其他reduce都跑完很久了它还在运行。
倾斜分成group by造成的倾斜和join造成的倾斜,需要分开看。
group by造成的倾斜有两个参数可以解决,一个是hive.map.aggr,默认值已经为true,意思是会做map端的combiner。所以如果你的group by查询只是做count(*)的话,其实是看不出倾斜效果的,但是如果你做的是count(distinct),那么还是会看出一点倾斜效果。另一个参数是hive.groupby.skewindata。这个参数的意思是做reduce操作的时候,拿到的key并不是所有相同值给同一个reduce,而是随机分发,然后reduce做聚合,做完之后再做一轮MR,拿前面聚合过的数据再算结果。所以这个参数其实跟hive.map.aggr做的是类似的事情,只是拿到reduce端来做,而且要额外启动一轮job,所以其实不怎么推荐用,效果不明显。
如果说要改写SQL来优化的话,可以按照下面这么做:
/*改写前*/ select a, count(distinct b) as c from tbl group by a; /*改写后*/ select a, count(*) as c from (select distinct a, b from tbl) group by a;join造成的倾斜,就比如上面描述的网站访问日志和用户表两个表join:
select a.* from logs a join users b on a.user_id = b.user_id;hive给出的解决方案叫skew join,其原理把这种user_id = 0的特殊值先不在reduce端计算掉,而是先写入hdfs,然后启动一轮map join专门做这个特殊值的计算,期望能提高计算这部分值的处理速度。当然你要告诉hive这个join是个skew join,即:
set hive.optimize.skewjoin = true;还有要告诉hive如何判断特殊值,根据hive.skewjoin.key设置的数量hive可以知道,比如默认值是100000,那么超过100000条记录的值就是特殊值。
另外对于特殊值的处理往往跟业务有关系,所以也可以从业务角度重写sql解决。比如前面这种倾斜join,可以把特殊值隔离开来(从业务角度说,users表应该不存在user_id = 0的情况,但是这里还是假设有这个值,使得这个写法更加具有通用性):
SELECT a.* FROM (SELECT a.* FROM (SELECT * FROM logs WHERE user_id = 0) a JOIN (SELECT * FROM users WHERE user_id = 0) b ON a.user_id = b.user_id UNION ALL SELECT a.* FROM logs a JOIN users b ON a.user_id <> 0 AND a.user_id = b.user_id)t;数据倾斜不仅仅是hive的问题,其实是share nothing架构下必然会碰到的数据分布问题,对此学界也有专门的研究,比如 skewtune 。
select * from ( select count(*) from logs where log_date = 20130801 and item_id = 1 union all select count(*) from logs where log_date = 20130802 and item_id = 2 union all select count(*) from logs where log_date = 20130803 and item_id = 3 )t设置job间并行的参数是hive.exec.parallel,将其设为true即可。默认的并行度为8,也就是最多允许sql中8个job并行。如果想要更高的并行度,可以通过hive.exec.parallel. thread.number参数进行设置,但要避免设置过大而占用过多资源。
SELECT count(*) FROM (SELECT DISTINCT user_id FROM logs WHERE page_name = ‘a‘) a JOIN (SELECT DISTINCT user_id FROM logs WHERE blog_owner = ‘b‘) b ON a.user_id = b.user_id;这样一来,就要产生2个求子查询的job,一个用于关联的job,还有一个计数的job,一共有4个job。
select count(*) from logs group by user_id having (count(case when page_name = ‘a‘ then 1 end) > 0 and count(case when page_name = ‘b‘ then 1 end) > 0)
第一种查询方法符合思考问题的直觉,是工程师和分析师在实际查数据中最先想到的写法,但是如果在目前hive的query planner不是那么智能的情况下,想要更加快速的跑出结果,懂一点工具的内部机理也是必须的。
当然了,也有同学有其它的思路,只是没有上面那么高效:
select count(*) from ( select user_id, count(case when blog_owner = ‘a‘ then 1 end) as visit_z, count(case when blog_owner = ‘b‘ then 1 end) as visit_l from cnblogs_visit_20130801 group by user_id ) t where visit_z > 0 and visit_l > 0;这种实现方式转换成job就只会有2个:内层的子查询和外层的统计,所以对 SQL 和原理都比较熟悉才能在 HIVE 中游刃有余~
[1] 数据仓库中的SQL性能优化(Hive篇)
标签:style blog http color os 使用 io ar for
原文地址:http://my.oschina.net/leejun2005/blog/308427